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,84 @@
1
+ /**
2
+ * ER Diagram domain types.
3
+ *
4
+ * An ErEntity is a structured description of one persistence entity in a
5
+ * product — a table, a view, or an enum. The CLI extracts these from schema
6
+ * files (migrations, ORM models, type definitions) and the desktop renders
7
+ * them as an entity-relationship diagram.
8
+ *
9
+ * Companion to DataNodeSchema / ScreenSchema: same flow-graph shape (nodes +
10
+ * edges sharing the `diagrams` table storing the JSONB schema), different domain.
11
+ * ER edges describe foreign-key / inheritance relationships between entities,
12
+ * with a cardinality, not data movement or user navigation.
13
+ */
14
+ // ============================================================================
15
+ // Runtime validation for AI-produced extraction
16
+ // ============================================================================
17
+ const ENTITY_KINDS = new Set([
18
+ 'entity',
19
+ 'view',
20
+ 'enum',
21
+ 'junction',
22
+ ]);
23
+ const RELATION_KINDS = new Set([
24
+ 'one-to-one',
25
+ 'one-to-many',
26
+ 'many-to-many',
27
+ 'inherits',
28
+ ]);
29
+ function isRecord(value) {
30
+ return typeof value === 'object' && value !== null;
31
+ }
32
+ function isErEntity(value) {
33
+ if (!isRecord(value)) {
34
+ return false;
35
+ }
36
+ if (typeof value.slug !== 'string' || value.slug.length === 0) {
37
+ return false;
38
+ }
39
+ if (typeof value.name !== 'string' || value.name.length === 0) {
40
+ return false;
41
+ }
42
+ if (typeof value.kind !== 'string' ||
43
+ !ENTITY_KINDS.has(value.kind)) {
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+ function isErRelation(value) {
49
+ if (!isRecord(value)) {
50
+ return false;
51
+ }
52
+ if (typeof value.fromSlug !== 'string') {
53
+ return false;
54
+ }
55
+ if (typeof value.toSlug !== 'string') {
56
+ return false;
57
+ }
58
+ if (typeof value.kind !== 'string' ||
59
+ !RELATION_KINDS.has(value.kind)) {
60
+ return false;
61
+ }
62
+ return true;
63
+ }
64
+ export function isErDiagramExtraction(value) {
65
+ if (!isRecord(value)) {
66
+ return false;
67
+ }
68
+ if (typeof value.summary !== 'string') {
69
+ return false;
70
+ }
71
+ if (!Array.isArray(value.entities)) {
72
+ return false;
73
+ }
74
+ if (!Array.isArray(value.relations)) {
75
+ return false;
76
+ }
77
+ if (!value.entities.every(isErEntity)) {
78
+ return false;
79
+ }
80
+ if (!value.relations.every(isErRelation)) {
81
+ return false;
82
+ }
83
+ return true;
84
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * flowchart phase: map the control flow of one process / function / algorithm
3
+ * as a flowchart — start/end, process steps, decisions (with branch labels),
4
+ * I/O, and subroutine calls. Persisted to the diagrams tables with
5
+ * `type = 'flowchart'`.
6
+ */
7
+ import { type DiagramPhaseResult } from '../diagram-shared/generate.js';
8
+ export interface FlowchartPhaseOptions {
9
+ productId?: string;
10
+ repoId?: string;
11
+ diagramId: string;
12
+ guidance?: string;
13
+ verbose?: boolean;
14
+ }
15
+ export declare function runFlowchartPhase(options: FlowchartPhaseOptions): Promise<DiagramPhaseResult>;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * flowchart phase: map the control flow of one process / function / algorithm
3
+ * as a flowchart — start/end, process steps, decisions (with branch labels),
4
+ * I/O, and subroutine calls. Persisted to the diagrams tables with
5
+ * `type = 'flowchart'`.
6
+ */
7
+ import { z } from 'zod';
8
+ import { generateDiagram, } from '../diagram-shared/generate.js';
9
+ import { buildDiagramSystemPrompt, buildDiagramUserPrompt, } from '../diagram-shared/prompts.js';
10
+ const flowchartNode = z.object({
11
+ slug: z.string().min(1),
12
+ name: z.string().min(1),
13
+ kind: z.enum(['start', 'end', 'process', 'decision', 'io', 'subroutine']),
14
+ file: z.string().optional(),
15
+ description: z.string().optional(),
16
+ });
17
+ const flowchartEdge = z.object({
18
+ fromSlug: z.string().min(1),
19
+ toSlug: z.string().min(1),
20
+ kind: z.enum(['flow', 'branch']),
21
+ /** Branch label for edges out of a decision, e.g. "yes" / "no" / "error". */
22
+ label: z.string().optional(),
23
+ sourceFile: z.string().optional(),
24
+ });
25
+ export function runFlowchartPhase(options) {
26
+ return generateDiagram({
27
+ ...options,
28
+ workspaceKey: 'flowchart',
29
+ fenceName: 'flowchart',
30
+ nounPlural: 'steps',
31
+ edgeNounPlural: 'arrows',
32
+ mcpConfig: {
33
+ name: 'flowchart',
34
+ toolName: 'flowchart',
35
+ summaryDescribe: '1-3 sentence narrative of the process this flowchart captures.',
36
+ nodesSchema: z.array(flowchartNode),
37
+ nodesDescribe: 'Every step: start / end / process / decision / io / subroutine. slug MUST be unique. Use exactly one `start`.',
38
+ edgesSchema: z.array(flowchartEdge),
39
+ edgesDescribe: 'Arrows. kind = flow (plain) or branch (out of a decision; set label to the branch condition like "yes"/"no"). Endpoints MUST reference emitted steps.',
40
+ },
41
+ buildSystemPrompt: (a) => buildDiagramSystemPrompt('phase/flowchart', 'flowchart', a),
42
+ buildUserPrompt: (a) => buildDiagramUserPrompt({
43
+ ...a,
44
+ task: 'Map a flowchart for',
45
+ mcpName: 'flowchart',
46
+ toolName: 'flowchart',
47
+ process: 'Pick ONE important process / function / algorithm (guidance may name it; otherwise choose a central one — a request handler, a core algorithm, a job). Read it top to bottom and translate its control flow into steps: a single `start`, `process` steps for actions, `decision` diamonds for branches (each outgoing edge a `branch` with a "yes"/"no"/condition label), `io` for reads/writes, `subroutine` for significant calls, and `end` node(s) for returns/exits. Follow the happy path plus the important branches.',
48
+ }),
49
+ });
50
+ }
@@ -917,7 +917,7 @@ to keep the user informed during long runs. This is observability only — it
917
917
  does not affect the extraction.
918
918
 
919
919
  ScreenSchema fields:
920
- - \`slug\` (unique within the flow), \`name\`, \`route?\`, \`file?\`
920
+ - \`slug\` (unique within the diagram), \`name\`, \`route?\`, \`file?\`
921
921
  - \`kind\`: one of \`page\`, \`modal\`, \`drawer\`, \`tab\`, \`state\`
922
922
  - \`layout\`: one of \`centered\`, \`sidebar\`, \`split\`, \`list-detail\`, \`tabs\`, \`stacked\`
923
923
  - \`header?\`: \`{ title, subtitle?, back?, actions?: [{ label, variant?, icon? }] }\`
@@ -971,7 +971,7 @@ to keep the user informed during long runs. This is observability only — it
971
971
  does not affect the extraction.
972
972
 
973
973
  DataNodeSchema fields:
974
- - \`slug\` (unique within the flow), \`name\`, \`kind\`, \`file?\`
974
+ - \`slug\` (unique within the diagram), \`name\`, \`kind\`, \`file?\`
975
975
  - \`kind\`: one of \`source\`, \`dataset\`, \`transform\`, \`sink\`, \`queue\`, \`model\`
976
976
  - \`description?\`: one-sentence summary
977
977
  - \`tech?\`: technology / format hint (e.g. \`postgres\`, \`parquet\`, \`kafka\`, \`openai-api\`)
@@ -1021,5 +1021,181 @@ submit_data_flow({
1021
1021
  ]
1022
1022
  })
1023
1023
  \`\`\`
1024
+ `,
1025
+ 'er-diagram': `
1026
+ **CRITICAL — How to return the result**:
1027
+
1028
+ Return the extraction by calling the MCP tool
1029
+ \`mcp__er-diagram__submit_er_diagram\` **exactly once** with three arguments:
1030
+
1031
+ - \`summary\` — 1-3 sentence narrative of the data model (core entities + relationships)
1032
+ - \`entities\` — array of ErEntity objects (every table / view / enum / junction)
1033
+ - \`relations\` — array of ErRelation objects (foreign-key / inheritance edges)
1034
+
1035
+ The tool validates the arguments against the schema. If it returns an error,
1036
+ fix the issue it describes and call the tool again. After a successful call,
1037
+ end your turn — do not also paste the same data as a fenced text block.
1038
+
1039
+ You can also call \`mcp__er-diagram__record_progress({ phase, message })\` at
1040
+ each phase boundary (detection / enumeration / entities / relations / submission)
1041
+ to keep the user informed during long runs. This is observability only — it
1042
+ does not affect the extraction.
1043
+
1044
+ ErEntity fields:
1045
+ - \`slug\` (unique within the diagram), \`name\`, \`kind\`, \`file?\`
1046
+ - \`kind\`: one of \`entity\`, \`view\`, \`enum\`, \`junction\`
1047
+ - \`description?\`: one-sentence summary
1048
+ - \`columns?\`: array of \`{ name, type?, isPrimaryKey?, isForeignKey?, isNullable?, isUnique?, description?, references? }\`
1049
+ - \`references\`: for FKs, \`target-entity-slug.column\` (e.g. \`users.id\`)
1050
+ - \`stats?\`: array of \`{ label, value }\` (row counts, cardinality hints)
1051
+
1052
+ ErRelation fields:
1053
+ - \`fromSlug\` (child / FK holder), \`toSlug\` (parent / referenced) — both MUST appear in entities
1054
+ - \`kind\`: one of \`one-to-one\`, \`one-to-many\`, \`many-to-many\`, \`inherits\`
1055
+ - \`label?\`: free-form descriptor (e.g. \`placed by\`, \`belongs to\`)
1056
+ - \`sourceColumn?\` / \`targetColumn?\`: the FK column and the referenced column
1057
+ - \`sourceFile?\`: file containing the FK constraint / association
1058
+
1059
+ Direction convention: fromSlug holds the foreign key (child / "many" side),
1060
+ toSlug is the referenced entity (parent / "one" side). \`orders.user_id → users.id\`
1061
+ becomes \`{ fromSlug: "orders", toSlug: "users", kind: "one-to-many" }\`.
1062
+
1063
+ Schematic example of the tool call:
1064
+
1065
+ \`\`\`
1066
+ submit_er_diagram({
1067
+ summary: "Storefront schema: users place orders made of order-items that reference products; roles are linked to users through a junction table.",
1068
+ entities: [
1069
+ { slug: "users", name: "users", kind: "entity",
1070
+ file: "db/migrations/0001_users.sql",
1071
+ columns: [
1072
+ { name: "id", type: "uuid", isPrimaryKey: true },
1073
+ { name: "email", type: "varchar(255)", isUnique: true },
1074
+ { name: "created_at", type: "timestamptz" }
1075
+ ] },
1076
+ { slug: "orders", name: "orders", kind: "entity",
1077
+ file: "db/migrations/0002_orders.sql",
1078
+ columns: [
1079
+ { name: "id", type: "uuid", isPrimaryKey: true },
1080
+ { name: "user_id", type: "uuid", isForeignKey: true, references: "users.id" },
1081
+ { name: "status", type: "order_status" }
1082
+ ] },
1083
+ { slug: "order-status", name: "order_status", kind: "enum",
1084
+ file: "db/migrations/0002_orders.sql" }
1085
+ ],
1086
+ relations: [
1087
+ { fromSlug: "orders", toSlug: "users", kind: "one-to-many",
1088
+ label: "placed by", sourceColumn: "user_id", targetColumn: "id",
1089
+ sourceFile: "db/migrations/0002_orders.sql" }
1090
+ ]
1091
+ })
1092
+ \`\`\`
1093
+ `,
1094
+ 'sequence-diagram': `
1095
+ **CRITICAL — How to return the result**:
1096
+
1097
+ Return the extraction by calling the MCP tool
1098
+ \`mcp__sequence-diagram__submit_sequence_diagram\` **exactly once** with three arguments:
1099
+
1100
+ - \`summary\` — 1-3 sentence narrative naming the scenario, the participants, and the outcome
1101
+ - \`participants\` — array of SequenceParticipant objects (every lifeline)
1102
+ - \`messages\` — array of SequenceMessage objects, each with an explicit \`order\`
1103
+
1104
+ The tool validates the arguments against the schema. If it returns an error,
1105
+ fix the issue it describes and call the tool again. After a successful call,
1106
+ end your turn — do not also paste the same data as a fenced text block.
1107
+
1108
+ You can also call \`mcp__sequence-diagram__record_progress({ phase, message })\`
1109
+ at each phase boundary (detection / enumeration / participants / messages /
1110
+ submission) to keep the user informed during long runs. This is observability
1111
+ only — it does not affect the extraction.
1112
+
1113
+ A sequence diagram captures ONE scenario. Map a single end-to-end flow of control.
1114
+
1115
+ SequenceParticipant fields:
1116
+ - \`slug\` (unique within the diagram), \`name\`, \`kind\`, \`file?\`
1117
+ - \`kind\`: one of \`actor\`, \`service\`, \`component\`, \`database\`, \`queue\`, \`external\`
1118
+ - \`description?\`: one-sentence role summary
1119
+
1120
+ SequenceMessage fields:
1121
+ - \`fromSlug\` (sender), \`toSlug\` (receiver) — both MUST appear in participants
1122
+ - \`kind\`: one of \`sync\`, \`async\`, \`return\`, \`self\`
1123
+ - \`order\`: **1-based integer** position in the execution timeline (do not skip or reuse numbers)
1124
+ - \`label?\`: the call / message text (\`POST /login\`, \`validateToken(jwt)\`, \`rows\`)
1125
+ - \`sourceFile?\`: file (+ line) where the call happens
1126
+
1127
+ Order convention: number messages in real execution order; a \`return\` comes
1128
+ after the \`sync\` call it answers. A \`self\` message has \`fromSlug === toSlug\`.
1129
+
1130
+ Schematic example of the tool call:
1131
+
1132
+ \`\`\`
1133
+ submit_sequence_diagram({
1134
+ summary: "Email/password login: the browser posts credentials to AuthService, which verifies them against the users table and issues a JWT.",
1135
+ participants: [
1136
+ { slug: "user", name: "User", kind: "actor" },
1137
+ { slug: "auth-service", name: "AuthService", kind: "service",
1138
+ file: "src/routes/auth.ts" },
1139
+ { slug: "users", name: "users", kind: "database",
1140
+ file: "db/migrations/0001_users.sql" }
1141
+ ],
1142
+ messages: [
1143
+ { fromSlug: "user", toSlug: "auth-service", kind: "sync", order: 1,
1144
+ label: "POST /login {email, password}", sourceFile: "src/routes/auth.ts" },
1145
+ { fromSlug: "auth-service", toSlug: "users", kind: "sync", order: 2,
1146
+ label: "SELECT … WHERE email = ?", sourceFile: "src/routes/auth.ts" },
1147
+ { fromSlug: "users", toSlug: "auth-service", kind: "return", order: 3,
1148
+ label: "user row" },
1149
+ { fromSlug: "auth-service", toSlug: "auth-service", kind: "self", order: 4,
1150
+ label: "verifyPassword()" },
1151
+ { fromSlug: "auth-service", toSlug: "user", kind: "return", order: 5,
1152
+ label: "200 {token}" }
1153
+ ]
1154
+ })
1155
+ \`\`\`
1156
+ `,
1157
+ 'state-diagram': `
1158
+ **CRITICAL — How to return the result**:
1159
+
1160
+ Call \`mcp__state-diagram__submit_state_diagram\` **exactly once** with:
1161
+ - \`summary\` — what this state machine models
1162
+ - \`nodes\` — states: \`{ slug, name, kind, file?, description?, onEntry?, onExit?, activity? }\`; \`kind\` ∈ \`initial\` | \`state\` | \`final\` | \`choice\` | \`composite\` (exactly one \`initial\`)
1163
+ - \`edges\` — transitions: \`{ fromSlug, toSlug, kind: "transition", label?, sourceFile? }\`; put \`trigger [guard] / action\` in \`label\`
1164
+
1165
+ Every fromSlug / toSlug MUST reference a node slug. The tool validates; on error,
1166
+ fix and call again. You may call \`mcp__state-diagram__record_progress\` for status.
1167
+ `,
1168
+ 'class-diagram': `
1169
+ **CRITICAL — How to return the result**:
1170
+
1171
+ Call \`mcp__class-diagram__submit_class_diagram\` **exactly once** with:
1172
+ - \`summary\` — the core types and how they relate
1173
+ - \`nodes\` — \`{ slug, name, kind, file?, description?, stereotype?, attributes?: [{ name, type?, visibility?, isStatic? }], methods?: [{ name, params?, returnType?, visibility?, isStatic?, isAbstract? }] }\`; \`kind\` ∈ \`class\` | \`interface\` | \`abstract\` | \`enum\`
1174
+ - \`edges\` — \`{ fromSlug, toSlug, kind, label?, sourceFile? }\`; \`kind\` ∈ \`inheritance\` | \`implementation\` | \`composition\` | \`aggregation\` | \`association\` | \`dependency\` (fromSlug = child/owner/dependent)
1175
+
1176
+ Every fromSlug / toSlug MUST reference a node slug. The tool validates; on error,
1177
+ fix and call again. You may call \`mcp__class-diagram__record_progress\` for status.
1178
+ `,
1179
+ 'architecture-diagram': `
1180
+ **CRITICAL — How to return the result**:
1181
+
1182
+ Call \`mcp__architecture-diagram__submit_architecture_diagram\` **exactly once** with:
1183
+ - \`summary\` — the architecture and its main building blocks
1184
+ - \`nodes\` — components: \`{ slug, name, kind, file?, description?, tech?, responsibilities?: [string] }\`; \`kind\` ∈ \`module\` | \`service\` | \`package\` | \`ui\` | \`datastore\` | \`external\`
1185
+ - \`edges\` — dependencies: \`{ fromSlug, toSlug, kind, label?, sourceFile? }\`; \`kind\` ∈ \`depends-on\` | \`calls\` | \`imports\` | \`data\` (fromSlug = depender)
1186
+
1187
+ Every fromSlug / toSlug MUST reference a node slug. The tool validates; on error,
1188
+ fix and call again. You may call \`mcp__architecture-diagram__record_progress\` for status.
1189
+ `,
1190
+ flowchart: `
1191
+ **CRITICAL — How to return the result**:
1192
+
1193
+ Call \`mcp__flowchart__submit_flowchart\` **exactly once** with:
1194
+ - \`summary\` — the process this flowchart captures
1195
+ - \`nodes\` — steps: \`{ slug, name, kind, file?, description? }\`; \`kind\` ∈ \`start\` | \`end\` | \`process\` | \`decision\` | \`io\` | \`subroutine\` (exactly one \`start\`)
1196
+ - \`edges\` — \`{ fromSlug, toSlug, kind, label?, sourceFile? }\`; \`kind\` ∈ \`flow\` | \`branch\` (branch edges out of a \`decision\` set \`label\` to "yes"/"no"/condition)
1197
+
1198
+ Every fromSlug / toSlug MUST reference a node slug. The tool validates; on error,
1199
+ fix and call again. You may call \`mcp__flowchart__record_progress\` for status.
1024
1200
  `,
1025
1201
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * screen-flow phase: clone the product's repo, ask Claude to map every
3
3
  * user-facing screen and the transitions between them into a structured
4
- * ScreenFlowExtraction, then persist the result to flows / flow_nodes /
5
- * flow_edges (rows tagged `type = 'screen'`) via the Supabase SDK.
4
+ * ScreenFlowExtraction, then persist the result to diagrams / diagram_nodes /
5
+ * diagram_edges (rows tagged `type = 'screen'`) via the Supabase SDK.
6
6
  *
7
7
  * Companion to find-architecture / find-bugs / find-features. Same workspace
8
8
  * pattern, but writes to its own tables rather than filing issues.
@@ -12,7 +12,7 @@ export interface ScreenFlowOptions {
12
12
  productId?: string;
13
13
  /** Repo-only flow: a single repositories row, no product context. */
14
14
  repoId?: string;
15
- flowId: string;
15
+ diagramId: string;
16
16
  guidance?: string;
17
17
  verbose?: boolean;
18
18
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * screen-flow phase: clone the product's repo, ask Claude to map every
3
3
  * user-facing screen and the transitions between them into a structured
4
- * ScreenFlowExtraction, then persist the result to flows / flow_nodes /
5
- * flow_edges (rows tagged `type = 'screen'`) via the Supabase SDK.
4
+ * ScreenFlowExtraction, then persist the result to diagrams / diagram_nodes /
5
+ * diagram_edges (rows tagged `type = 'screen'`) via the Supabase SDK.
6
6
  *
7
7
  * Companion to find-architecture / find-bugs / find-features. Same workspace
8
8
  * pattern, but writes to its own tables rather than filing issues.
@@ -14,7 +14,7 @@ import { getSupabase } from '../../supabase/client.js';
14
14
  import { logError, logInfo, logSuccess, logWarning } from '../../utils/logger.js';
15
15
  import { cleanupIssueRepo } from '../../workspace/workspace-manager.js';
16
16
  import { fetchProductBasics } from '../find-shared/mcp.js';
17
- import { cloneFlowRepos, describeRepoScope } from '../flow-shared/clone-repos.js';
17
+ import { cloneDiagramRepos, describeRepoScope } from '../diagram-shared/clone-repos.js';
18
18
  import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
19
19
  import { createScreenFlowCaptureState, createScreenFlowMcpServer, validateConsistency, } from './mcp-server.js';
20
20
  import { createScreenFlowSystemPrompt, createScreenFlowUserPrompt, } from './prompts.js';
@@ -27,7 +27,7 @@ const COLUMN_WIDTH = 380;
27
27
  const ROW_HEIGHT = 480;
28
28
  const COLUMNS = 4;
29
29
  export async function runScreenFlowPhase(options) {
30
- const { productId, repoId, flowId, guidance, verbose } = options;
30
+ const { productId, repoId, diagramId, guidance, verbose } = options;
31
31
  const repoOnly = !productId && Boolean(repoId);
32
32
  if (productId) {
33
33
  logInfo(`Starting screen-flow generation for product ${productId}`);
@@ -36,9 +36,9 @@ export async function runScreenFlowPhase(options) {
36
36
  logInfo(`Starting screen-flow generation for repository ${repoId}`);
37
37
  }
38
38
  const supabase = getSupabase();
39
- await markFlowRunning(supabase, flowId);
40
- const repositoryIds = await getFlowRepositoryIds(supabase, flowId);
41
- const cloneResult = await cloneFlowRepos({
39
+ await markDiagramRunning(supabase, diagramId);
40
+ const repositoryIds = await getDiagramRepositoryIds(supabase, diagramId);
41
+ const cloneResult = await cloneDiagramRepos({
42
42
  productId,
43
43
  repoId,
44
44
  repositoryIds,
@@ -46,7 +46,7 @@ export async function runScreenFlowPhase(options) {
46
46
  verbose,
47
47
  });
48
48
  if (!cloneResult.ok) {
49
- await markFlowFailed(supabase, flowId, cloneResult.message);
49
+ await markDiagramFailed(supabase, diagramId, cloneResult.message);
50
50
  return { status: 'error', message: cloneResult.message };
51
51
  }
52
52
  const { projectDir, cleanupDir, repos } = cloneResult;
@@ -104,17 +104,17 @@ export async function runScreenFlowPhase(options) {
104
104
  }
105
105
  if (!extraction) {
106
106
  const msg = 'Screen flow extraction failed: agent did not call submit_screen_flow and no parseable screen_flow block was found in the response';
107
- await markFlowFailed(supabase, flowId, msg);
107
+ await markDiagramFailed(supabase, diagramId, msg);
108
108
  return { status: 'error', message: msg };
109
109
  }
110
110
  logInfo(`Extraction produced ${extraction.nodes.length} screens / ${extraction.edges.length} transitions`);
111
111
  const theme = resolveTheme(extraction.theme, repos);
112
112
  if (Object.keys(theme).length > 0) {
113
113
  logInfo(`Theme: ${Object.entries(theme).map(([k, v]) => `${k}=${v}`).join(', ')}`);
114
- await persistTheme(supabase, flowId, theme);
114
+ await persistTheme(supabase, diagramId, theme);
115
115
  }
116
- const { nodesCreated, edgesCreated } = await persistFlow(supabase, flowId, extraction);
117
- await markFlowSuccess(supabase, flowId, extraction.summary);
116
+ const { nodesCreated, edgesCreated } = await persistDiagram(supabase, diagramId, extraction);
117
+ await markDiagramSuccess(supabase, diagramId, extraction.summary);
118
118
  succeeded = true;
119
119
  logSuccess(`Screen flow generated: ${nodesCreated} screens, ${edgesCreated} transitions`);
120
120
  return {
@@ -128,7 +128,7 @@ export async function runScreenFlowPhase(options) {
128
128
  catch (error) {
129
129
  const errorMessage = error instanceof Error ? error.message : String(error);
130
130
  logError(`Screen flow failed: ${errorMessage}`);
131
- await markFlowFailed(supabase, flowId, errorMessage);
131
+ await markDiagramFailed(supabase, diagramId, errorMessage);
132
132
  return { status: 'error', message: errorMessage };
133
133
  }
134
134
  finally {
@@ -169,12 +169,12 @@ async function resolveRepoBasics(repositoryId, repos) {
169
169
  description: basics?.description ?? undefined,
170
170
  };
171
171
  }
172
- /** Read the ordered repo set a flow was scoped to (may be empty). */
173
- async function getFlowRepositoryIds(supabase, flowId) {
172
+ /** Read the ordered repo set a diagram was scoped to (may be empty). */
173
+ async function getDiagramRepositoryIds(supabase, diagramId) {
174
174
  const { data } = await supabase
175
- .from('flows')
175
+ .from('diagrams')
176
176
  .select('repository_ids')
177
- .eq('id', flowId)
177
+ .eq('id', diagramId)
178
178
  .single();
179
179
  return (data?.repository_ids ?? []).filter(Boolean);
180
180
  }
@@ -234,31 +234,31 @@ function tryFallbackParse(resultMessage, assistantText) {
234
234
  // ============================================================================
235
235
  // Persistence
236
236
  // ============================================================================
237
- async function markFlowRunning(supabase, flowId) {
237
+ async function markDiagramRunning(supabase, diagramId) {
238
238
  const { error } = await supabase
239
- .from('flows')
239
+ .from('diagrams')
240
240
  .update({ status: 'running', error: null })
241
- .eq('id', flowId);
241
+ .eq('id', diagramId);
242
242
  if (error) {
243
- logWarning(`Could not mark flow as running: ${error.message}`);
243
+ logWarning(`Could not mark diagram as running: ${error.message}`);
244
244
  }
245
245
  }
246
- async function markFlowFailed(supabase, flowId, errorMessage) {
246
+ async function markDiagramFailed(supabase, diagramId, errorMessage) {
247
247
  await supabase
248
- .from('flows')
248
+ .from('diagrams')
249
249
  .update({
250
250
  status: 'failed',
251
251
  error: errorMessage,
252
252
  completed_at: new Date().toISOString(),
253
253
  })
254
- .eq('id', flowId);
254
+ .eq('id', diagramId);
255
255
  }
256
- async function persistTheme(supabase, flowId, theme) {
256
+ async function persistTheme(supabase, diagramId, theme) {
257
257
  // Theme is screen-flow-specific; stash it inside the generic options JSONB.
258
258
  const { data, error: readError } = await supabase
259
- .from('flows')
259
+ .from('diagrams')
260
260
  .select('options')
261
- .eq('id', flowId)
261
+ .eq('id', diagramId)
262
262
  .single();
263
263
  if (readError) {
264
264
  logWarning(`Could not read flow options: ${readError.message}`);
@@ -269,34 +269,34 @@ async function persistTheme(supabase, flowId, theme) {
269
269
  theme,
270
270
  };
271
271
  const { error } = await supabase
272
- .from('flows')
272
+ .from('diagrams')
273
273
  .update({ options: nextOptions })
274
- .eq('id', flowId);
274
+ .eq('id', diagramId);
275
275
  if (error) {
276
276
  logWarning(`Could not persist extracted theme: ${error.message}`);
277
277
  }
278
278
  }
279
- async function markFlowSuccess(supabase, flowId, summary) {
279
+ async function markDiagramSuccess(supabase, diagramId, summary) {
280
280
  await supabase
281
- .from('flows')
281
+ .from('diagrams')
282
282
  .update({
283
283
  status: 'success',
284
284
  summary,
285
285
  error: null,
286
286
  completed_at: new Date().toISOString(),
287
287
  })
288
- .eq('id', flowId);
288
+ .eq('id', diagramId);
289
289
  }
290
- async function persistFlow(supabase, flowId, extraction) {
290
+ async function persistDiagram(supabase, diagramId, extraction) {
291
291
  // Re-runs replace prior content for the same flow row.
292
- await supabase.from('flow_edges').delete().eq('flow_id', flowId);
293
- await supabase.from('flow_nodes').delete().eq('flow_id', flowId);
292
+ await supabase.from('diagram_edges').delete().eq('diagram_id', diagramId);
293
+ await supabase.from('diagram_nodes').delete().eq('diagram_id', diagramId);
294
294
  if (extraction.nodes.length === 0) {
295
295
  return { nodesCreated: 0, edgesCreated: 0 };
296
296
  }
297
- const nodeRows = extraction.nodes.map((n, i) => buildNodeRow(flowId, n, i));
297
+ const nodeRows = extraction.nodes.map((n, i) => buildNodeRow(diagramId, n, i));
298
298
  const { data: insertedNodes, error: nodesError } = await supabase
299
- .from('flow_nodes')
299
+ .from('diagram_nodes')
300
300
  .insert(nodeRows)
301
301
  .select('id, slug');
302
302
  if (nodesError) {
@@ -304,11 +304,11 @@ async function persistFlow(supabase, flowId, extraction) {
304
304
  }
305
305
  const slugToId = new Map((insertedNodes ?? []).map((n) => [n.slug, n.id]));
306
306
  const edgeRows = extraction.edges
307
- .map((e) => buildEdgeRow(flowId, e, slugToId))
307
+ .map((e) => buildEdgeRow(diagramId, e, slugToId))
308
308
  .filter((e) => e !== null);
309
309
  if (edgeRows.length > 0) {
310
310
  const { error: edgesError } = await supabase
311
- .from('flow_edges')
311
+ .from('diagram_edges')
312
312
  .insert(edgeRows);
313
313
  if (edgesError) {
314
314
  throw new Error(`Failed to insert edges: ${edgesError.message}`);
@@ -319,9 +319,9 @@ async function persistFlow(supabase, flowId, extraction) {
319
319
  edgesCreated: edgeRows.length,
320
320
  };
321
321
  }
322
- function buildNodeRow(flowId, node, index) {
322
+ function buildNodeRow(diagramId, node, index) {
323
323
  return {
324
- flow_id: flowId,
324
+ diagram_id: diagramId,
325
325
  slug: node.slug,
326
326
  name: node.name,
327
327
  kind: node.kind,
@@ -330,14 +330,14 @@ function buildNodeRow(flowId, node, index) {
330
330
  position_y: Math.floor(index / COLUMNS) * ROW_HEIGHT,
331
331
  };
332
332
  }
333
- function buildEdgeRow(flowId, edge, slugToId) {
333
+ function buildEdgeRow(diagramId, edge, slugToId) {
334
334
  const fromId = slugToId.get(edge.fromSlug);
335
335
  const toId = slugToId.get(edge.toSlug);
336
336
  if (!fromId || !toId) {
337
337
  return null;
338
338
  }
339
339
  return {
340
- flow_id: flowId,
340
+ diagram_id: diagramId,
341
341
  from_node_id: fromId,
342
342
  to_node_id: toId,
343
343
  label: edge.triggerLabel,
@@ -166,7 +166,7 @@ export function validateConsistency(extraction) {
166
166
  for (const node of extraction.nodes) {
167
167
  if (slugs.has(node.slug)) {
168
168
  return {
169
- error: `Duplicate node slug "${node.slug}". Each node.slug MUST be unique within the flow. Re-call submit_screen_flow with deduplicated nodes.`,
169
+ error: `Duplicate node slug "${node.slug}". Each node.slug MUST be unique within the diagram. Re-call submit_screen_flow with deduplicated nodes.`,
170
170
  };
171
171
  }
172
172
  slugs.add(node.slug);
@@ -208,7 +208,7 @@ export function createSubmitScreenFlowTool(state) {
208
208
  .describe('1-3 sentence narrative of what kind of app this is and its primary user flows.'),
209
209
  nodes: z
210
210
  .array(screenNodeSchema)
211
- .describe('Every user-facing screen, modal, drawer, tab, or named state. node.slug MUST be unique within the flow.'),
211
+ .describe('Every user-facing screen, modal, drawer, tab, or named state. node.slug MUST be unique within the diagram.'),
212
212
  edges: z
213
213
  .array(screenEdgeSchema)
214
214
  .describe('Transitions between screens. Every fromSlug / toSlug MUST reference a slug present in nodes; drop edges whose endpoints you did not emit.'),
@@ -0,0 +1,30 @@
1
+ /**
2
+ * sequence-diagram phase: clone the product's repo, ask Claude to trace one
3
+ * scenario through the code and map the participants (actor / service /
4
+ * component / database / queue / external) and the ordered messages between
5
+ * them into a structured SequenceDiagramExtraction, then persist the result
6
+ * to diagrams / diagram_nodes / diagram_edges (rows tagged `type = 'sequence'`) via the
7
+ * Supabase SDK.
8
+ *
9
+ * Companion to data-flow / screen-flow / er-diagram: same generation pattern
10
+ * (workspace clone + Claude Agent SDK + in-process MCP server), same storage
11
+ * tables, different domain. Message ordering is persisted in the edge's
12
+ * `metadata.order`.
13
+ */
14
+ export interface SequenceDiagramPhaseOptions {
15
+ /** Product-scoped diagram. Mutually exclusive with `repoId`. */
16
+ productId?: string;
17
+ /** Repo-only diagram: a single repositories row, no product context. */
18
+ repoId?: string;
19
+ diagramId: string;
20
+ guidance?: string;
21
+ verbose?: boolean;
22
+ }
23
+ export interface SequenceDiagramPhaseResult {
24
+ status: 'success' | 'error';
25
+ message: string;
26
+ nodesCreated?: number;
27
+ edgesCreated?: number;
28
+ summary?: string;
29
+ }
30
+ export declare function runSequenceDiagramPhase(options: SequenceDiagramPhaseOptions): Promise<SequenceDiagramPhaseResult>;