edsger 0.69.0 → 0.70.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 (74) hide show
  1. package/dist/api/github.d.ts +1 -1
  2. package/dist/api/github.js +1 -1
  3. package/dist/commands/architecture-diagram/index.d.ts +8 -0
  4. package/dist/commands/architecture-diagram/index.js +10 -0
  5. package/dist/commands/class-diagram/index.d.ts +7 -0
  6. package/dist/commands/class-diagram/index.js +9 -0
  7. package/dist/commands/data-flow/index.d.ts +5 -5
  8. package/dist/commands/data-flow/index.js +8 -8
  9. package/dist/commands/diagram-shared/index.d.ts +21 -0
  10. package/dist/commands/diagram-shared/index.js +37 -0
  11. package/dist/commands/er-diagram/index.d.ts +19 -0
  12. package/dist/commands/er-diagram/index.js +55 -0
  13. package/dist/commands/flowchart/index.d.ts +8 -0
  14. package/dist/commands/flowchart/index.js +10 -0
  15. package/dist/commands/screen-flow/index.d.ts +5 -5
  16. package/dist/commands/screen-flow/index.js +8 -8
  17. package/dist/commands/sequence-diagram/index.d.ts +19 -0
  18. package/dist/commands/sequence-diagram/index.js +55 -0
  19. package/dist/commands/state-diagram/index.d.ts +7 -0
  20. package/dist/commands/state-diagram/index.js +9 -0
  21. package/dist/index.js +117 -5
  22. package/dist/phases/architecture-diagram/index.d.ts +15 -0
  23. package/dist/phases/architecture-diagram/index.js +51 -0
  24. package/dist/phases/class-diagram/index.d.ts +14 -0
  25. package/dist/phases/class-diagram/index.js +76 -0
  26. package/dist/phases/data-flow/index.d.ts +2 -2
  27. package/dist/phases/data-flow/index.js +36 -36
  28. package/dist/phases/data-flow/mcp-server.d.ts +1 -1
  29. package/dist/phases/data-flow/mcp-server.js +2 -2
  30. package/dist/phases/data-flow/types.d.ts +1 -1
  31. package/dist/phases/data-flow/types.js +1 -1
  32. package/dist/phases/diagram-shared/clone-repos.d.ts +63 -0
  33. package/dist/phases/diagram-shared/clone-repos.js +153 -0
  34. package/dist/phases/diagram-shared/generate.d.ts +42 -0
  35. package/dist/phases/diagram-shared/generate.js +162 -0
  36. package/dist/phases/diagram-shared/graph.d.ts +62 -0
  37. package/dist/phases/diagram-shared/graph.js +169 -0
  38. package/dist/phases/diagram-shared/mcp.d.ts +35 -0
  39. package/dist/phases/diagram-shared/mcp.js +68 -0
  40. package/dist/phases/diagram-shared/prompts.d.ts +23 -0
  41. package/dist/phases/diagram-shared/prompts.js +35 -0
  42. package/dist/phases/er-diagram/index.d.ts +28 -0
  43. package/dist/phases/er-diagram/index.js +290 -0
  44. package/dist/phases/er-diagram/mcp-server.d.ts +77 -0
  45. package/dist/phases/er-diagram/mcp-server.js +144 -0
  46. package/dist/phases/er-diagram/prompts.d.ts +14 -0
  47. package/dist/phases/er-diagram/prompts.js +36 -0
  48. package/dist/phases/er-diagram/types.d.ts +76 -0
  49. package/dist/phases/er-diagram/types.js +84 -0
  50. package/dist/phases/flowchart/index.d.ts +15 -0
  51. package/dist/phases/flowchart/index.js +50 -0
  52. package/dist/phases/output-contracts.js +178 -2
  53. package/dist/phases/screen-flow/index.d.ts +3 -3
  54. package/dist/phases/screen-flow/index.js +43 -43
  55. package/dist/phases/screen-flow/mcp-server.js +2 -2
  56. package/dist/phases/sequence-diagram/index.d.ts +30 -0
  57. package/dist/phases/sequence-diagram/index.js +290 -0
  58. package/dist/phases/sequence-diagram/mcp-server.d.ts +64 -0
  59. package/dist/phases/sequence-diagram/mcp-server.js +134 -0
  60. package/dist/phases/sequence-diagram/prompts.d.ts +14 -0
  61. package/dist/phases/sequence-diagram/prompts.js +36 -0
  62. package/dist/phases/sequence-diagram/types.d.ts +52 -0
  63. package/dist/phases/sequence-diagram/types.js +93 -0
  64. package/dist/phases/state-diagram/index.d.ts +15 -0
  65. package/dist/phases/state-diagram/index.js +53 -0
  66. package/dist/skills/phase/architecture-diagram/SKILL.md +41 -0
  67. package/dist/skills/phase/class-diagram/SKILL.md +44 -0
  68. package/dist/skills/phase/er-diagram/SKILL.md +71 -0
  69. package/dist/skills/phase/flowchart/SKILL.md +38 -0
  70. package/dist/skills/phase/sequence-diagram/SKILL.md +67 -0
  71. package/dist/skills/phase/state-diagram/SKILL.md +38 -0
  72. package/dist/workspace/session-workspace.d.ts +2 -2
  73. package/dist/workspace/session-workspace.js +2 -2
  74. package/package.json +1 -1
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Shared diagram generation runner.
3
+ *
4
+ * Encapsulates the common pipeline every node/edge diagram phase uses: clone
5
+ * the repo(s), resolve product/repo basics, build prompts, run the Claude
6
+ * Agent SDK loop with an in-process MCP capture server, fall back to a fenced
7
+ * block if the agent forgot the tool, then persist to the diagrams tables and
8
+ * flip status. Each lean phase just supplies its domain MCP config + prompts.
9
+ */
10
+ import { query } from '@anthropic-ai/claude-agent-sdk';
11
+ import { getRepositoryBasics } from '../../api/github.js';
12
+ import { DEFAULT_MODEL } from '../../constants.js';
13
+ import { getSupabase } from '../../supabase/client.js';
14
+ import { logError, logInfo, logSuccess, logWarning } from '../../utils/logger.js';
15
+ import { cleanupIssueRepo } from '../../workspace/workspace-manager.js';
16
+ import { fetchProductBasics } from '../find-shared/mcp.js';
17
+ import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
18
+ import { cloneDiagramRepos, describeRepoScope } from './clone-repos.js';
19
+ import { getDiagramRepositoryIds, isDiagramExtraction, markDiagramFailed, markDiagramRunning, markDiagramSuccess, persistDiagramGraph, validateGraphConsistency, } from './graph.js';
20
+ import { createDiagramCaptureState, createDiagramMcpServer, } from './mcp.js';
21
+ const MAX_TURNS = 150;
22
+ export async function generateDiagram(options) {
23
+ const { productId, repoId, diagramId, guidance, verbose } = options;
24
+ const repoOnly = !productId && Boolean(repoId);
25
+ const label = options.mcpConfig.name;
26
+ logInfo(productId
27
+ ? `Starting ${label} generation for product ${productId}`
28
+ : `Starting ${label} generation for repository ${repoId}`);
29
+ const supabase = getSupabase();
30
+ await markDiagramRunning(supabase, diagramId);
31
+ const repositoryIds = await getDiagramRepositoryIds(supabase, diagramId);
32
+ const cloneResult = await cloneDiagramRepos({
33
+ productId,
34
+ repoId,
35
+ repositoryIds,
36
+ workspaceKey: options.workspaceKey,
37
+ verbose,
38
+ });
39
+ if (!cloneResult.ok) {
40
+ await markDiagramFailed(supabase, diagramId, cloneResult.message);
41
+ return { status: 'error', message: cloneResult.message };
42
+ }
43
+ const { projectDir, cleanupDir, repos } = cloneResult;
44
+ let succeeded = false;
45
+ try {
46
+ const product = repoOnly
47
+ ? await resolveRepoBasics(repoId, repos)
48
+ : await fetchProductBasics(productId);
49
+ const systemPrompt = await options.buildSystemPrompt({
50
+ projectDir,
51
+ hasCodebase: true,
52
+ });
53
+ const repoScope = describeRepoScope(repos);
54
+ const userPrompt = options.buildUserPrompt({
55
+ productName: product.name,
56
+ productDescription: product.description,
57
+ guidance: [guidance, repoScope].filter(Boolean).join('\n\n') || undefined,
58
+ });
59
+ logInfo(`Running Claude ${label} extraction...`);
60
+ const captureState = createDiagramCaptureState();
61
+ const mcpServer = createDiagramMcpServer(options.mcpConfig, captureState, {
62
+ onProgress: ({ phase, message }) => logInfo(`[${phase}] ${message}`),
63
+ });
64
+ let assistantBuffer = '';
65
+ let extraction = null;
66
+ const stream = query({
67
+ prompt: createPromptGenerator(userPrompt),
68
+ options: {
69
+ systemPrompt: {
70
+ type: 'preset',
71
+ preset: 'claude_code',
72
+ append: systemPrompt,
73
+ },
74
+ model: DEFAULT_MODEL,
75
+ maxTurns: MAX_TURNS,
76
+ permissionMode: 'bypassPermissions',
77
+ cwd: projectDir,
78
+ mcpServers: { [options.mcpConfig.name]: mcpServer },
79
+ },
80
+ });
81
+ for await (const rawMessage of stream) {
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ const message = rawMessage;
84
+ if (message.type === 'assistant') {
85
+ assistantBuffer += extractTextFromContent(message.message?.content ?? [], verbose);
86
+ continue;
87
+ }
88
+ if (message.type === 'user' && verbose) {
89
+ const userContent = message.message?.content;
90
+ if (Array.isArray(userContent)) {
91
+ extractTextFromContent(userContent, verbose);
92
+ }
93
+ continue;
94
+ }
95
+ if (message.type !== 'result') {
96
+ continue;
97
+ }
98
+ if (captureState.captured) {
99
+ extraction = captureState.captured;
100
+ break;
101
+ }
102
+ const fallback = tryFallbackParse(message, assistantBuffer, options.fenceName);
103
+ if (fallback) {
104
+ logWarning(`Agent emitted a fenced ${options.fenceName} block instead of calling the submit tool; using the parsed text as a fallback.`);
105
+ extraction = fallback;
106
+ }
107
+ else if (message.subtype !== 'success') {
108
+ logError(`Extraction incomplete: ${message.subtype}`);
109
+ }
110
+ }
111
+ if (!extraction) {
112
+ const msg = `${label} extraction failed: agent did not call the submit tool and no parseable ${options.fenceName} block was found.`;
113
+ await markDiagramFailed(supabase, diagramId, msg);
114
+ return { status: 'error', message: msg };
115
+ }
116
+ logInfo(`Extraction produced ${extraction.nodes.length} ${options.nounPlural} / ${extraction.edges.length} ${options.edgeNounPlural}`);
117
+ const { nodesCreated, edgesCreated } = await persistDiagramGraph(supabase, diagramId, extraction);
118
+ await markDiagramSuccess(supabase, diagramId, extraction.summary);
119
+ succeeded = true;
120
+ logSuccess(`${label} generated: ${nodesCreated} ${options.nounPlural}, ${edgesCreated} ${options.edgeNounPlural}`);
121
+ return {
122
+ status: 'success',
123
+ message: `${label} generated (${nodesCreated} ${options.nounPlural}, ${edgesCreated} ${options.edgeNounPlural})`,
124
+ nodesCreated,
125
+ edgesCreated,
126
+ summary: extraction.summary,
127
+ };
128
+ }
129
+ catch (error) {
130
+ const errorMessage = error instanceof Error ? error.message : String(error);
131
+ logError(`${label} failed: ${errorMessage}`);
132
+ await markDiagramFailed(supabase, diagramId, errorMessage);
133
+ return { status: 'error', message: errorMessage };
134
+ }
135
+ finally {
136
+ if (succeeded) {
137
+ cleanupIssueRepo(cleanupDir);
138
+ }
139
+ }
140
+ }
141
+ async function resolveRepoBasics(repositoryId, repos) {
142
+ const basics = await getRepositoryBasics(repositoryId).catch(() => null);
143
+ return {
144
+ name: basics?.fullName ?? repos[0]?.fullName ?? repositoryId,
145
+ description: basics?.description ?? undefined,
146
+ };
147
+ }
148
+ function tryFallbackParse(resultMessage, assistantText, fenceName) {
149
+ const responseText = resultMessage.subtype === 'success'
150
+ ? resultMessage.result || assistantText
151
+ : assistantText;
152
+ const parsed = tryExtractResult(responseText, fenceName);
153
+ if (!isDiagramExtraction(parsed)) {
154
+ return null;
155
+ }
156
+ const { error } = validateGraphConsistency(parsed);
157
+ if (error) {
158
+ logWarning(`Fallback extraction failed consistency check: ${error}`);
159
+ return null;
160
+ }
161
+ return parsed;
162
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared graph model + persistence for node/edge diagram types.
3
+ *
4
+ * Every diagram type (screen / data / er / sequence and the lean types built
5
+ * on this framework — state / class / architecture / flowchart) is a graph of
6
+ * nodes + edges stored in the generic `diagrams` / `diagram_nodes` /
7
+ * `diagram_edges` tables, discriminated by `diagrams.type`. This module owns
8
+ * the type-agnostic extraction shape, its consistency validation, and the
9
+ * persistence + status-transition helpers so each phase only has to describe
10
+ * its own domain schema and prompts.
11
+ */
12
+ import type { SupabaseClient } from '@supabase/supabase-js';
13
+ /** One node in a diagram. Domain-specific fields ride along and are stored
14
+ * verbatim in `diagram_nodes.schema`. */
15
+ export interface GraphNode {
16
+ /** Stable slug, unique within the diagram. */
17
+ slug: string;
18
+ name: string;
19
+ kind: string;
20
+ [key: string]: unknown;
21
+ }
22
+ /** One directed edge. fromSlug → toSlug must reference emitted node slugs. */
23
+ export interface GraphEdge {
24
+ fromSlug: string;
25
+ toSlug: string;
26
+ kind: string;
27
+ label?: string;
28
+ /** File (jump-to-code) for the connection, stored as source_anchor. */
29
+ sourceFile?: string;
30
+ /** Extra per-edge data stored in diagram_edges.metadata (order, columns…). */
31
+ metadata?: Record<string, unknown>;
32
+ }
33
+ export interface DiagramExtraction {
34
+ summary: string;
35
+ nodes: GraphNode[];
36
+ edges: GraphEdge[];
37
+ }
38
+ export declare function isGraphNode(value: unknown): value is GraphNode;
39
+ export declare function isGraphEdge(value: unknown): value is GraphEdge;
40
+ export declare function isDiagramExtraction(value: unknown): value is DiagramExtraction;
41
+ /**
42
+ * Cross-field checks: unique node slugs and every edge endpoint references a
43
+ * node that was emitted. Returns a human-readable instruction on failure so
44
+ * the agent can self-correct and re-submit.
45
+ */
46
+ export declare function validateGraphConsistency(extraction: DiagramExtraction): {
47
+ error: string | null;
48
+ };
49
+ export declare function markDiagramRunning(supabase: SupabaseClient, diagramId: string): Promise<void>;
50
+ export declare function markDiagramFailed(supabase: SupabaseClient, diagramId: string, errorMessage: string): Promise<void>;
51
+ export declare function markDiagramSuccess(supabase: SupabaseClient, diagramId: string, summary: string): Promise<void>;
52
+ /** Read the ordered repo set a diagram was scoped to (may be empty). */
53
+ export declare function getDiagramRepositoryIds(supabase: SupabaseClient, diagramId: string): Promise<string[]>;
54
+ /**
55
+ * Replace the diagram's nodes and edges with a freshly extracted graph.
56
+ * Slugs are resolved to node ids for the edge foreign keys; edges whose
57
+ * endpoints weren't emitted are dropped.
58
+ */
59
+ export declare function persistDiagramGraph(supabase: SupabaseClient, diagramId: string, extraction: DiagramExtraction): Promise<{
60
+ nodesCreated: number;
61
+ edgesCreated: number;
62
+ }>;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Shared graph model + persistence for node/edge diagram types.
3
+ *
4
+ * Every diagram type (screen / data / er / sequence and the lean types built
5
+ * on this framework — state / class / architecture / flowchart) is a graph of
6
+ * nodes + edges stored in the generic `diagrams` / `diagram_nodes` /
7
+ * `diagram_edges` tables, discriminated by `diagrams.type`. This module owns
8
+ * the type-agnostic extraction shape, its consistency validation, and the
9
+ * persistence + status-transition helpers so each phase only has to describe
10
+ * its own domain schema and prompts.
11
+ */
12
+ import { logWarning } from '../../utils/logger.js';
13
+ function isRecord(value) {
14
+ return typeof value === 'object' && value !== null;
15
+ }
16
+ export function isGraphNode(value) {
17
+ return (isRecord(value) &&
18
+ typeof value.slug === 'string' &&
19
+ value.slug.length > 0 &&
20
+ typeof value.name === 'string' &&
21
+ value.name.length > 0 &&
22
+ typeof value.kind === 'string' &&
23
+ value.kind.length > 0);
24
+ }
25
+ export function isGraphEdge(value) {
26
+ return (isRecord(value) &&
27
+ typeof value.fromSlug === 'string' &&
28
+ typeof value.toSlug === 'string' &&
29
+ typeof value.kind === 'string');
30
+ }
31
+ export function isDiagramExtraction(value) {
32
+ return (isRecord(value) &&
33
+ typeof value.summary === 'string' &&
34
+ Array.isArray(value.nodes) &&
35
+ Array.isArray(value.edges) &&
36
+ value.nodes.every(isGraphNode) &&
37
+ value.edges.every(isGraphEdge));
38
+ }
39
+ /**
40
+ * Cross-field checks: unique node slugs and every edge endpoint references a
41
+ * node that was emitted. Returns a human-readable instruction on failure so
42
+ * the agent can self-correct and re-submit.
43
+ */
44
+ export function validateGraphConsistency(extraction) {
45
+ const slugs = new Set();
46
+ for (const node of extraction.nodes) {
47
+ if (slugs.has(node.slug)) {
48
+ return {
49
+ error: `Duplicate node slug "${node.slug}". Each node slug MUST be unique within the diagram. Re-submit with deduplicated nodes.`,
50
+ };
51
+ }
52
+ slugs.add(node.slug);
53
+ }
54
+ for (const edge of extraction.edges) {
55
+ if (!slugs.has(edge.fromSlug)) {
56
+ return {
57
+ error: `Edge "${edge.fromSlug}" → "${edge.toSlug}": fromSlug does not match any node slug. Add the missing node or drop the edge, then re-submit.`,
58
+ };
59
+ }
60
+ if (!slugs.has(edge.toSlug)) {
61
+ return {
62
+ error: `Edge "${edge.fromSlug}" → "${edge.toSlug}": toSlug does not match any node slug. Add the missing node or drop the edge, then re-submit.`,
63
+ };
64
+ }
65
+ }
66
+ return { error: null };
67
+ }
68
+ // ============================================================================
69
+ // Persistence
70
+ // ============================================================================
71
+ // Auto-layout grid; users drag afterwards and positions persist.
72
+ const COLUMN_WIDTH = 320;
73
+ const ROW_HEIGHT = 240;
74
+ const COLUMNS = 4;
75
+ export async function markDiagramRunning(supabase, diagramId) {
76
+ const { error } = await supabase
77
+ .from('diagrams')
78
+ .update({ status: 'running', error: null })
79
+ .eq('id', diagramId);
80
+ if (error) {
81
+ logWarning(`Could not mark diagram as running: ${error.message}`);
82
+ }
83
+ }
84
+ export async function markDiagramFailed(supabase, diagramId, errorMessage) {
85
+ await supabase
86
+ .from('diagrams')
87
+ .update({
88
+ status: 'failed',
89
+ error: errorMessage,
90
+ completed_at: new Date().toISOString(),
91
+ })
92
+ .eq('id', diagramId);
93
+ }
94
+ export async function markDiagramSuccess(supabase, diagramId, summary) {
95
+ await supabase
96
+ .from('diagrams')
97
+ .update({
98
+ status: 'success',
99
+ summary,
100
+ error: null,
101
+ completed_at: new Date().toISOString(),
102
+ })
103
+ .eq('id', diagramId);
104
+ }
105
+ /** Read the ordered repo set a diagram was scoped to (may be empty). */
106
+ export async function getDiagramRepositoryIds(supabase, diagramId) {
107
+ const { data } = await supabase
108
+ .from('diagrams')
109
+ .select('repository_ids')
110
+ .eq('id', diagramId)
111
+ .single();
112
+ return (data?.repository_ids ?? []).filter(Boolean);
113
+ }
114
+ /**
115
+ * Replace the diagram's nodes and edges with a freshly extracted graph.
116
+ * Slugs are resolved to node ids for the edge foreign keys; edges whose
117
+ * endpoints weren't emitted are dropped.
118
+ */
119
+ export async function persistDiagramGraph(supabase, diagramId, extraction) {
120
+ await supabase.from('diagram_edges').delete().eq('diagram_id', diagramId);
121
+ await supabase.from('diagram_nodes').delete().eq('diagram_id', diagramId);
122
+ if (extraction.nodes.length === 0) {
123
+ return { nodesCreated: 0, edgesCreated: 0 };
124
+ }
125
+ const nodeRows = extraction.nodes.map((node, i) => ({
126
+ diagram_id: diagramId,
127
+ slug: node.slug,
128
+ name: node.name,
129
+ kind: node.kind,
130
+ schema: node,
131
+ position_x: (i % COLUMNS) * COLUMN_WIDTH,
132
+ position_y: Math.floor(i / COLUMNS) * ROW_HEIGHT,
133
+ }));
134
+ const { data: insertedNodes, error: nodesError } = await supabase
135
+ .from('diagram_nodes')
136
+ .insert(nodeRows)
137
+ .select('id, slug');
138
+ if (nodesError) {
139
+ throw new Error(`Failed to insert nodes: ${nodesError.message}`);
140
+ }
141
+ const slugToId = new Map((insertedNodes ?? []).map((n) => [n.slug, n.id]));
142
+ const edgeRows = extraction.edges
143
+ .map((edge) => {
144
+ const fromId = slugToId.get(edge.fromSlug);
145
+ const toId = slugToId.get(edge.toSlug);
146
+ if (!fromId || !toId) {
147
+ return null;
148
+ }
149
+ return {
150
+ diagram_id: diagramId,
151
+ from_node_id: fromId,
152
+ to_node_id: toId,
153
+ label: edge.label ?? null,
154
+ source_anchor: edge.sourceFile ?? null,
155
+ kind: edge.kind,
156
+ metadata: edge.metadata ?? {},
157
+ };
158
+ })
159
+ .filter((e) => e !== null);
160
+ if (edgeRows.length > 0) {
161
+ const { error: edgesError } = await supabase
162
+ .from('diagram_edges')
163
+ .insert(edgeRows);
164
+ if (edgesError) {
165
+ throw new Error(`Failed to insert edges: ${edgesError.message}`);
166
+ }
167
+ }
168
+ return { nodesCreated: nodeRows.length, edgesCreated: edgeRows.length };
169
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared in-process MCP server builder for graph diagram types.
3
+ *
4
+ * Each lean diagram phase (state / class / architecture / flowchart) supplies
5
+ * its own zod node/edge schemas + tool name; this assembles the standard
6
+ * `submit_<type>` capture tool (validated against the shared graph consistency
7
+ * rules) plus a `record_progress` status tool.
8
+ */
9
+ import { type ZodTypeAny } from 'zod';
10
+ import { type DiagramExtraction } from './graph.js';
11
+ export interface DiagramCaptureState {
12
+ captured: DiagramExtraction | null;
13
+ }
14
+ export declare function createDiagramCaptureState(): DiagramCaptureState;
15
+ export type DiagramProgressSink = (event: {
16
+ phase: 'detection' | 'enumeration' | 'nodes' | 'edges' | 'submission';
17
+ message: string;
18
+ }) => void;
19
+ export interface DiagramMcpConfig {
20
+ /** Server name + submit tool become `submit_<toolName>`. */
21
+ name: string;
22
+ toolName: string;
23
+ summaryDescribe: string;
24
+ nodesSchema: ZodTypeAny;
25
+ nodesDescribe: string;
26
+ edgesSchema: ZodTypeAny;
27
+ edgesDescribe: string;
28
+ }
29
+ /**
30
+ * Build the MCP server for one diagram type. The submit tool captures the
31
+ * extraction into `state` after passing the shared consistency check.
32
+ */
33
+ export declare function createDiagramMcpServer(config: DiagramMcpConfig, state: DiagramCaptureState, options?: {
34
+ onProgress?: DiagramProgressSink;
35
+ }): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared in-process MCP server builder for graph diagram types.
3
+ *
4
+ * Each lean diagram phase (state / class / architecture / flowchart) supplies
5
+ * its own zod node/edge schemas + tool name; this assembles the standard
6
+ * `submit_<type>` capture tool (validated against the shared graph consistency
7
+ * rules) plus a `record_progress` status tool.
8
+ */
9
+ import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
10
+ import { z } from 'zod';
11
+ import { validateGraphConsistency, } from './graph.js';
12
+ export function createDiagramCaptureState() {
13
+ return { captured: null };
14
+ }
15
+ function createRecordProgressTool(sink) {
16
+ return tool('record_progress', 'Send a short status update to the user. Does not affect the extraction. Call it at each phase boundary so the user sees progress.', {
17
+ phase: z
18
+ .enum(['detection', 'enumeration', 'nodes', 'edges', 'submission'])
19
+ .describe('Which phase the message belongs to.'),
20
+ message: z.string().min(1).describe('Human-readable status update.'),
21
+ }, async (args) => {
22
+ sink?.({ phase: args.phase, message: args.message });
23
+ return { content: [{ type: 'text', text: 'ok' }] };
24
+ });
25
+ }
26
+ /**
27
+ * Build the MCP server for one diagram type. The submit tool captures the
28
+ * extraction into `state` after passing the shared consistency check.
29
+ */
30
+ export function createDiagramMcpServer(config, state, options) {
31
+ const submitTool = tool(`submit_${config.toolName}`, [
32
+ `Submit the final ${config.name}. Call this EXACTLY once, when you have`,
33
+ 'finished mapping every node and edge. After it succeeds, end your turn —',
34
+ 'do NOT also paste the same data as a fenced code block. If validation',
35
+ 'fails, the error tells you what to fix; call the tool again.',
36
+ ].join(' '), {
37
+ summary: z.string().min(1).describe(config.summaryDescribe),
38
+ nodes: config.nodesSchema.describe(config.nodesDescribe),
39
+ edges: config.edgesSchema.describe(config.edgesDescribe),
40
+ }, async (args) => {
41
+ const extraction = {
42
+ summary: args.summary,
43
+ nodes: args.nodes,
44
+ edges: args.edges,
45
+ };
46
+ const { error } = validateGraphConsistency(extraction);
47
+ if (error) {
48
+ return {
49
+ content: [{ type: 'text', text: error }],
50
+ isError: true,
51
+ };
52
+ }
53
+ state.captured = extraction;
54
+ return {
55
+ content: [
56
+ {
57
+ type: 'text',
58
+ text: `Captured ${extraction.nodes.length} nodes / ${extraction.edges.length} edges. End your turn now.`,
59
+ },
60
+ ],
61
+ };
62
+ });
63
+ return createSdkMcpServer({
64
+ name: config.name,
65
+ version: '1.0.0',
66
+ tools: [submitTool, createRecordProgressTool(options?.onProgress)],
67
+ });
68
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared prompt builders for graph diagram phases. The system prompt loads the
3
+ * type's SKILL.md (project-overridable) and appends its JSON output contract;
4
+ * the user prompt wraps a type-specific task + process paragraph in the
5
+ * standard "call the submit tool" envelope.
6
+ */
7
+ export declare function buildDiagramSystemPrompt(skillRef: string, contractKey: string, opts?: {
8
+ projectDir?: string;
9
+ hasCodebase?: boolean;
10
+ }): Promise<string>;
11
+ export declare function buildDiagramUserPrompt(args: {
12
+ /** e.g. "Map the state machine for". The product name is appended. */
13
+ task: string;
14
+ productName: string;
15
+ productDescription?: string;
16
+ guidance?: string;
17
+ /** One paragraph telling the agent what to scan / how to proceed. */
18
+ process: string;
19
+ /** MCP server name, e.g. 'state-diagram'. */
20
+ mcpName: string;
21
+ /** Submit tool suffix, e.g. 'state_diagram' → submit_state_diagram. */
22
+ toolName: string;
23
+ }): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared prompt builders for graph diagram phases. The system prompt loads the
3
+ * type's SKILL.md (project-overridable) and appends its JSON output contract;
4
+ * the user prompt wraps a type-specific task + process paragraph in the
5
+ * standard "call the submit tool" envelope.
6
+ */
7
+ import { processConditionals, resolveSkill, } from '../../services/skill-resolver.js';
8
+ import { OUTPUT_CONTRACTS } from '../output-contracts.js';
9
+ export async function buildDiagramSystemPrompt(skillRef, contractKey, opts) {
10
+ const skill = await resolveSkill(skillRef, { projectDir: opts?.projectDir });
11
+ if (!skill) {
12
+ throw new Error(`Failed to load skill: ${skillRef}`);
13
+ }
14
+ const prompt = processConditionals(skill.prompt, {
15
+ hasCodebase: opts?.hasCodebase ?? true,
16
+ });
17
+ return `${prompt}
18
+
19
+ ${OUTPUT_CONTRACTS[contractKey]}`;
20
+ }
21
+ export function buildDiagramUserPrompt(args) {
22
+ const descBlock = args.productDescription
23
+ ? `\n**Product description**: ${args.productDescription}`
24
+ : '';
25
+ const guidanceBlock = args.guidance
26
+ ? `\n\n**Human guidance for this run** (focus or exclude as instructed):\n${args.guidance}`
27
+ : '';
28
+ return `${args.task} **${args.productName}**.${descBlock}${guidanceBlock}
29
+
30
+ ${args.process}
31
+
32
+ Call \`mcp__${args.mcpName}__record_progress\` at each phase boundary so the user can see your progress (otherwise the CLI looks frozen).
33
+
34
+ When you are done, return the result by **calling the \`mcp__${args.mcpName}__submit_${args.toolName}\` tool exactly once**. Do not paste the JSON as a fenced text block — the tool call is the deliverable. If the tool returns an error, fix the issue it describes and call the tool again.`;
35
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * er-diagram phase: clone the product's repo, ask Claude to map every
3
+ * persistence entity (table / view / enum / junction) and the relationships
4
+ * between them into a structured ErDiagramExtraction, then persist the result
5
+ * to diagrams / diagram_nodes / diagram_edges (rows tagged `type = 'er'`) via the
6
+ * Supabase SDK.
7
+ *
8
+ * Companion to data-flow / screen-flow: same generation pattern (workspace
9
+ * clone + Claude Agent SDK + in-process MCP server), same storage tables,
10
+ * different domain.
11
+ */
12
+ export interface ErDiagramPhaseOptions {
13
+ /** Product-scoped diagram. Mutually exclusive with `repoId`. */
14
+ productId?: string;
15
+ /** Repo-only diagram: a single repositories row, no product context. */
16
+ repoId?: string;
17
+ diagramId: string;
18
+ guidance?: string;
19
+ verbose?: boolean;
20
+ }
21
+ export interface ErDiagramPhaseResult {
22
+ status: 'success' | 'error';
23
+ message: string;
24
+ nodesCreated?: number;
25
+ edgesCreated?: number;
26
+ summary?: string;
27
+ }
28
+ export declare function runErDiagramPhase(options: ErDiagramPhaseOptions): Promise<ErDiagramPhaseResult>;