edsger 0.69.0 → 0.71.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/dist/api/github.d.ts +1 -1
- package/dist/api/github.js +1 -1
- package/dist/commands/architecture-diagram/index.d.ts +8 -0
- package/dist/commands/architecture-diagram/index.js +10 -0
- package/dist/commands/class-diagram/index.d.ts +7 -0
- package/dist/commands/class-diagram/index.js +9 -0
- package/dist/commands/data-flow/index.d.ts +5 -5
- package/dist/commands/data-flow/index.js +8 -8
- package/dist/commands/diagram-shared/index.d.ts +21 -0
- package/dist/commands/diagram-shared/index.js +37 -0
- package/dist/commands/discover/index.d.ts +14 -0
- package/dist/commands/discover/index.js +29 -0
- package/dist/commands/er-diagram/index.d.ts +19 -0
- package/dist/commands/er-diagram/index.js +55 -0
- package/dist/commands/flowchart/index.d.ts +8 -0
- package/dist/commands/flowchart/index.js +10 -0
- package/dist/commands/screen-flow/index.d.ts +5 -5
- package/dist/commands/screen-flow/index.js +8 -8
- package/dist/commands/sequence-diagram/index.d.ts +19 -0
- package/dist/commands/sequence-diagram/index.js +55 -0
- package/dist/commands/state-diagram/index.d.ts +7 -0
- package/dist/commands/state-diagram/index.js +9 -0
- package/dist/index.js +139 -5
- package/dist/phases/architecture-diagram/index.d.ts +15 -0
- package/dist/phases/architecture-diagram/index.js +51 -0
- package/dist/phases/class-diagram/index.d.ts +14 -0
- package/dist/phases/class-diagram/index.js +76 -0
- package/dist/phases/data-flow/index.d.ts +2 -2
- package/dist/phases/data-flow/index.js +37 -37
- package/dist/phases/data-flow/mcp-server.d.ts +1 -1
- package/dist/phases/data-flow/mcp-server.js +2 -2
- package/dist/phases/data-flow/types.d.ts +1 -1
- package/dist/phases/data-flow/types.js +1 -1
- package/dist/phases/diagram-shared/clone-repos.d.ts +63 -0
- package/dist/phases/diagram-shared/clone-repos.js +153 -0
- package/dist/phases/diagram-shared/generate.d.ts +42 -0
- package/dist/phases/diagram-shared/generate.js +162 -0
- package/dist/phases/diagram-shared/graph.d.ts +62 -0
- package/dist/phases/diagram-shared/graph.js +169 -0
- package/dist/phases/diagram-shared/mcp.d.ts +35 -0
- package/dist/phases/diagram-shared/mcp.js +68 -0
- package/dist/phases/diagram-shared/prompts.d.ts +23 -0
- package/dist/phases/diagram-shared/prompts.js +35 -0
- package/dist/phases/discover-services/index.d.ts +29 -0
- package/dist/phases/discover-services/index.js +528 -0
- package/dist/phases/er-diagram/index.d.ts +28 -0
- package/dist/phases/er-diagram/index.js +290 -0
- package/dist/phases/er-diagram/mcp-server.d.ts +77 -0
- package/dist/phases/er-diagram/mcp-server.js +144 -0
- package/dist/phases/er-diagram/prompts.d.ts +14 -0
- package/dist/phases/er-diagram/prompts.js +36 -0
- package/dist/phases/er-diagram/types.d.ts +76 -0
- package/dist/phases/er-diagram/types.js +84 -0
- package/dist/phases/flowchart/index.d.ts +15 -0
- package/dist/phases/flowchart/index.js +50 -0
- package/dist/phases/output-contracts.js +178 -2
- package/dist/phases/screen-flow/index.d.ts +3 -3
- package/dist/phases/screen-flow/index.js +47 -45
- package/dist/phases/screen-flow/mcp-server.js +2 -2
- package/dist/phases/sequence-diagram/index.d.ts +30 -0
- package/dist/phases/sequence-diagram/index.js +290 -0
- package/dist/phases/sequence-diagram/mcp-server.d.ts +64 -0
- package/dist/phases/sequence-diagram/mcp-server.js +134 -0
- package/dist/phases/sequence-diagram/prompts.d.ts +14 -0
- package/dist/phases/sequence-diagram/prompts.js +36 -0
- package/dist/phases/sequence-diagram/types.d.ts +52 -0
- package/dist/phases/sequence-diagram/types.js +93 -0
- package/dist/phases/state-diagram/index.d.ts +15 -0
- package/dist/phases/state-diagram/index.js +53 -0
- package/dist/skills/phase/architecture-diagram/SKILL.md +41 -0
- package/dist/skills/phase/class-diagram/SKILL.md +44 -0
- package/dist/skills/phase/er-diagram/SKILL.md +71 -0
- package/dist/skills/phase/flowchart/SKILL.md +38 -0
- package/dist/skills/phase/sequence-diagram/SKILL.md +67 -0
- package/dist/skills/phase/state-diagram/SKILL.md +38 -0
- package/dist/workspace/session-workspace.d.ts +2 -2
- package/dist/workspace/session-workspace.js +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,17 +9,22 @@ import { runLogin, runLogout, runStatus } from './auth/login.js';
|
|
|
9
9
|
import { runAgentWorkflow } from './commands/agent-workflow/index.js';
|
|
10
10
|
import { runAnalyzeLogs } from './commands/analyze-logs/index.js';
|
|
11
11
|
import { runAppStoreGeneration } from './commands/app-store/index.js';
|
|
12
|
+
import { runArchitectureDiagram } from './commands/architecture-diagram/index.js';
|
|
12
13
|
import { runBuild } from './commands/build/index.js';
|
|
13
14
|
import { runChatServeCommand } from './commands/chat-serve/index.js';
|
|
14
15
|
import { runChecklists } from './commands/checklists/index.js';
|
|
16
|
+
import { runClassDiagram } from './commands/class-diagram/index.js';
|
|
15
17
|
import { runCodeReview } from './commands/code-review/index.js';
|
|
16
18
|
import { runConfigGet, runConfigList, runConfigSet, runConfigUnset, } from './commands/config/index.js';
|
|
17
19
|
import { runDataFlow } from './commands/data-flow/index.js';
|
|
20
|
+
import { runDiscover } from './commands/discover/index.js';
|
|
21
|
+
import { runErDiagram } from './commands/er-diagram/index.js';
|
|
18
22
|
import { runFinancingDeck } from './commands/financing-deck/index.js';
|
|
19
23
|
import { runFindArchitecture } from './commands/find-architecture/index.js';
|
|
20
24
|
import { runFindBugs } from './commands/find-bugs/index.js';
|
|
21
25
|
import { runFindFeatures } from './commands/find-features/index.js';
|
|
22
26
|
import { parseCategoriesOption, runFindSmells, } from './commands/find-smells/index.js';
|
|
27
|
+
import { runFlowchart } from './commands/flowchart/index.js';
|
|
23
28
|
import { runGrowthAnalysis } from './commands/growth-analysis/index.js';
|
|
24
29
|
import { runInit } from './commands/init/index.js';
|
|
25
30
|
import { runIntelligence } from './commands/intelligence/index.js';
|
|
@@ -33,9 +38,11 @@ import { runRefactor } from './commands/refactor/refactor.js';
|
|
|
33
38
|
import { runReleaseSyncCommand } from './commands/release-sync/index.js';
|
|
34
39
|
import { runRunSheetCommand } from './commands/run-sheet/index.js';
|
|
35
40
|
import { runScreenFlow } from './commands/screen-flow/index.js';
|
|
41
|
+
import { runSequenceDiagram } from './commands/sequence-diagram/index.js';
|
|
36
42
|
import { runSessionServeCommand } from './commands/session-serve/index.js';
|
|
37
43
|
import { runSessionTurnCommand } from './commands/session-turn/index.js';
|
|
38
44
|
import { runSmokeTestCommand } from './commands/smoke-test/index.js';
|
|
45
|
+
import { runStateDiagram } from './commands/state-diagram/index.js';
|
|
39
46
|
import { runSyncAws } from './commands/sync-aws/index.js';
|
|
40
47
|
import { runSyncDatadog } from './commands/sync-datadog/index.js';
|
|
41
48
|
import { runSyncGithubIssues } from './commands/sync-github-issues/index.js';
|
|
@@ -174,7 +181,7 @@ program
|
|
|
174
181
|
program
|
|
175
182
|
.command('screen-flow [productId]')
|
|
176
183
|
.description('Generate a structured screen flow (screens + transitions) for a product (or standalone repository) from its source code')
|
|
177
|
-
.requiredOption('--
|
|
184
|
+
.requiredOption('--diagram-id <id>', 'Pending diagrams row id to populate')
|
|
178
185
|
.option('--repo-id <id>', 'Run in repo-only mode against a single repositories row (no product context)')
|
|
179
186
|
.option('-g, --guidance <text>', 'Human direction for the AI (focus areas, exclusions)')
|
|
180
187
|
.option('-v, --verbose', 'Verbose output')
|
|
@@ -184,7 +191,7 @@ program
|
|
|
184
191
|
throw new Error('Provide a productId or --repo-id (repo-only mode) for screen-flow');
|
|
185
192
|
}
|
|
186
193
|
await runScreenFlow(productId, {
|
|
187
|
-
|
|
194
|
+
diagramId: opts.diagramId,
|
|
188
195
|
repoId: opts.repoId,
|
|
189
196
|
guidance: opts.guidance,
|
|
190
197
|
verbose: opts.verbose,
|
|
@@ -201,7 +208,7 @@ program
|
|
|
201
208
|
program
|
|
202
209
|
.command('data-flow [productId]')
|
|
203
210
|
.description('Generate a structured data flow (sources, datasets, transforms, sinks, queues, models) for a product (or standalone repository) from its source code')
|
|
204
|
-
.requiredOption('--
|
|
211
|
+
.requiredOption('--diagram-id <id>', 'Pending diagrams row id to populate')
|
|
205
212
|
.option('--repo-id <id>', 'Run in repo-only mode against a single repositories row (no product context)')
|
|
206
213
|
.option('-g, --guidance <text>', 'Human direction for the AI (focus areas, exclusions)')
|
|
207
214
|
.option('-v, --verbose', 'Verbose output')
|
|
@@ -211,7 +218,7 @@ program
|
|
|
211
218
|
throw new Error('Provide a productId or --repo-id (repo-only mode) for data-flow');
|
|
212
219
|
}
|
|
213
220
|
await runDataFlow(productId, {
|
|
214
|
-
|
|
221
|
+
diagramId: opts.diagramId,
|
|
215
222
|
repoId: opts.repoId,
|
|
216
223
|
guidance: opts.guidance,
|
|
217
224
|
verbose: opts.verbose,
|
|
@@ -223,6 +230,112 @@ program
|
|
|
223
230
|
}
|
|
224
231
|
});
|
|
225
232
|
// ============================================================
|
|
233
|
+
// Subcommand: edsger er-diagram <productId>
|
|
234
|
+
// ============================================================
|
|
235
|
+
program
|
|
236
|
+
.command('er-diagram [productId]')
|
|
237
|
+
.description('Generate a structured entity-relationship diagram (tables, views, enums, junctions + their relationships) for a product (or standalone repository) from its schema source')
|
|
238
|
+
.requiredOption('--diagram-id <id>', 'Pending diagrams row id to populate')
|
|
239
|
+
.option('--repo-id <id>', 'Run in repo-only mode against a single repositories row (no product context)')
|
|
240
|
+
.option('-g, --guidance <text>', 'Human direction for the AI (focus areas, exclusions)')
|
|
241
|
+
.option('-v, --verbose', 'Verbose output')
|
|
242
|
+
.action(async (productId, opts) => {
|
|
243
|
+
try {
|
|
244
|
+
if (!productId && !opts.repoId) {
|
|
245
|
+
throw new Error('Provide a productId or --repo-id (repo-only mode) for er-diagram');
|
|
246
|
+
}
|
|
247
|
+
await runErDiagram(productId, {
|
|
248
|
+
diagramId: opts.diagramId,
|
|
249
|
+
repoId: opts.repoId,
|
|
250
|
+
guidance: opts.guidance,
|
|
251
|
+
verbose: opts.verbose,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
// ============================================================
|
|
260
|
+
// Subcommand: edsger sequence-diagram <productId>
|
|
261
|
+
// ============================================================
|
|
262
|
+
program
|
|
263
|
+
.command('sequence-diagram [productId]')
|
|
264
|
+
.description('Generate a structured sequence diagram (participants + ordered messages for one scenario) for a product (or standalone repository) from its source code')
|
|
265
|
+
.requiredOption('--diagram-id <id>', 'Pending diagrams row id to populate')
|
|
266
|
+
.option('--repo-id <id>', 'Run in repo-only mode against a single repositories row (no product context)')
|
|
267
|
+
.option('-g, --guidance <text>', 'Human direction for the AI — the scenario to map (e.g. "user checkout")')
|
|
268
|
+
.option('-v, --verbose', 'Verbose output')
|
|
269
|
+
.action(async (productId, opts) => {
|
|
270
|
+
try {
|
|
271
|
+
if (!productId && !opts.repoId) {
|
|
272
|
+
throw new Error('Provide a productId or --repo-id (repo-only mode) for sequence-diagram');
|
|
273
|
+
}
|
|
274
|
+
await runSequenceDiagram(productId, {
|
|
275
|
+
diagramId: opts.diagramId,
|
|
276
|
+
repoId: opts.repoId,
|
|
277
|
+
guidance: opts.guidance,
|
|
278
|
+
verbose: opts.verbose,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
const diagramSubcommands = [
|
|
287
|
+
{
|
|
288
|
+
name: 'state-diagram',
|
|
289
|
+
description: 'Generate a state diagram (states + transitions for one lifecycle) for a product (or standalone repository) from its source code',
|
|
290
|
+
guidance: 'Human direction for the AI — the lifecycle to map',
|
|
291
|
+
run: runStateDiagram,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'class-diagram',
|
|
295
|
+
description: 'Generate a UML class diagram (types + relationships) for a product (or standalone repository) from its source code',
|
|
296
|
+
guidance: 'Human direction for the AI (focus areas, exclusions)',
|
|
297
|
+
run: runClassDiagram,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'architecture-diagram',
|
|
301
|
+
description: 'Generate an architecture/component diagram (components + dependencies) for a product (or standalone repository) from its source code',
|
|
302
|
+
guidance: 'Human direction for the AI (focus areas, exclusions)',
|
|
303
|
+
run: runArchitectureDiagram,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'flowchart',
|
|
307
|
+
description: 'Generate a flowchart (control flow of one process/function) for a product (or standalone repository) from its source code',
|
|
308
|
+
guidance: 'Human direction for the AI — the process to map',
|
|
309
|
+
run: runFlowchart,
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
for (const sub of diagramSubcommands) {
|
|
313
|
+
program
|
|
314
|
+
.command(`${sub.name} [productId]`)
|
|
315
|
+
.description(sub.description)
|
|
316
|
+
.requiredOption('--diagram-id <id>', 'Pending diagrams row id to populate')
|
|
317
|
+
.option('--repo-id <id>', 'Run in repo-only mode against a single repositories row (no product context)')
|
|
318
|
+
.option('-g, --guidance <text>', sub.guidance)
|
|
319
|
+
.option('-v, --verbose', 'Verbose output')
|
|
320
|
+
.action(async (productId, opts) => {
|
|
321
|
+
try {
|
|
322
|
+
if (!productId && !opts.repoId) {
|
|
323
|
+
throw new Error(`Provide a productId or --repo-id (repo-only mode) for ${sub.name}`);
|
|
324
|
+
}
|
|
325
|
+
await sub.run(productId, {
|
|
326
|
+
diagramId: opts.diagramId,
|
|
327
|
+
repoId: opts.repoId,
|
|
328
|
+
guidance: opts.guidance,
|
|
329
|
+
verbose: opts.verbose,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
// ============================================================
|
|
226
339
|
// Subcommand: edsger user-psychology <productId>
|
|
227
340
|
// ============================================================
|
|
228
341
|
program
|
|
@@ -659,7 +772,7 @@ program
|
|
|
659
772
|
.command('quality-benchmark [productId]')
|
|
660
773
|
.description("Run an industrial-grade code quality benchmark against the product's (or a standalone repository's) GitHub repo")
|
|
661
774
|
.option('--repo <path>', "Override the auto-clone with a local checkout (default: clone the product's repo into ~/edsger/quality-<owner>-<repo>)")
|
|
662
|
-
.option('--repo-id <id>',
|
|
775
|
+
.option('--repo-id <id>', 'Benchmark a specific repository (id). With a productId, targets that linked repo; without a productId, runs in repo-only mode (no product)')
|
|
663
776
|
.option('--branch <name>', 'Override the detected default branch on the report envelope')
|
|
664
777
|
.option('--pkg-manager <name>', 'npm | pnpm | yarn (auto-detected if absent)')
|
|
665
778
|
.option('--no-install', 'Refuse to install missing tools; mark them unmeasured')
|
|
@@ -776,6 +889,27 @@ program
|
|
|
776
889
|
process.exit(1);
|
|
777
890
|
}
|
|
778
891
|
});
|
|
892
|
+
// ============================================================
|
|
893
|
+
// Subcommand: edsger discover <teamId> [runId]
|
|
894
|
+
//
|
|
895
|
+
// Scans every repo in the team's connected GitHub org and upserts an
|
|
896
|
+
// inferred service for each into the service catalog. Drives an existing
|
|
897
|
+
// discovery_runs row (created by the desktop) to running → success/failed.
|
|
898
|
+
// ============================================================
|
|
899
|
+
program
|
|
900
|
+
.command('discover <teamId> [runId]')
|
|
901
|
+
.description('Discover services by scanning every repo in the team’s connected GitHub organisation')
|
|
902
|
+
.option('-v, --verbose', 'Verbose output')
|
|
903
|
+
.option('--org <name>', 'Override the GitHub organisation to scan')
|
|
904
|
+
.action(async (teamId, runId, opts) => {
|
|
905
|
+
try {
|
|
906
|
+
await runDiscover(teamId, runId, opts);
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
779
913
|
// Subcommand: edsger sync-terraform <teamId>
|
|
780
914
|
//
|
|
781
915
|
// Clones the team's terraform repo (from teams.terraform_repo_full_name)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* architecture-diagram phase: map a component/dependency diagram (C4-ish) —
|
|
3
|
+
* the modules / services / packages / datastores / external systems that make
|
|
4
|
+
* up the product and how they depend on one another. Persisted to the
|
|
5
|
+
* diagrams tables with `type = 'architecture'`.
|
|
6
|
+
*/
|
|
7
|
+
import { type DiagramPhaseResult } from '../diagram-shared/generate.js';
|
|
8
|
+
export interface ArchitectureDiagramPhaseOptions {
|
|
9
|
+
productId?: string;
|
|
10
|
+
repoId?: string;
|
|
11
|
+
diagramId: string;
|
|
12
|
+
guidance?: string;
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function runArchitectureDiagramPhase(options: ArchitectureDiagramPhaseOptions): Promise<DiagramPhaseResult>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* architecture-diagram phase: map a component/dependency diagram (C4-ish) —
|
|
3
|
+
* the modules / services / packages / datastores / external systems that make
|
|
4
|
+
* up the product and how they depend on one another. Persisted to the
|
|
5
|
+
* diagrams tables with `type = 'architecture'`.
|
|
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 archNode = z.object({
|
|
11
|
+
slug: z.string().min(1),
|
|
12
|
+
name: z.string().min(1),
|
|
13
|
+
kind: z.enum(['module', 'service', 'package', 'ui', 'datastore', 'external']),
|
|
14
|
+
file: z.string().optional(),
|
|
15
|
+
description: z.string().optional(),
|
|
16
|
+
tech: z.string().optional(),
|
|
17
|
+
responsibilities: z.array(z.string()).optional(),
|
|
18
|
+
});
|
|
19
|
+
const archEdge = z.object({
|
|
20
|
+
fromSlug: z.string().min(1),
|
|
21
|
+
toSlug: z.string().min(1),
|
|
22
|
+
kind: z.enum(['depends-on', 'calls', 'imports', 'data']),
|
|
23
|
+
label: z.string().optional(),
|
|
24
|
+
sourceFile: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
export function runArchitectureDiagramPhase(options) {
|
|
27
|
+
return generateDiagram({
|
|
28
|
+
...options,
|
|
29
|
+
workspaceKey: 'architecture-diagram',
|
|
30
|
+
fenceName: 'architecture_diagram',
|
|
31
|
+
nounPlural: 'components',
|
|
32
|
+
edgeNounPlural: 'dependencies',
|
|
33
|
+
mcpConfig: {
|
|
34
|
+
name: 'architecture-diagram',
|
|
35
|
+
toolName: 'architecture_diagram',
|
|
36
|
+
summaryDescribe: '1-3 sentence narrative of the system architecture and its main building blocks.',
|
|
37
|
+
nodesSchema: z.array(archNode),
|
|
38
|
+
nodesDescribe: 'Every component: module / service / package / ui / datastore / external. slug MUST be unique.',
|
|
39
|
+
edgesSchema: z.array(archEdge),
|
|
40
|
+
edgesDescribe: 'Dependencies. kind = depends-on / calls / imports / data. fromSlug is the depender. Endpoints MUST reference emitted components.',
|
|
41
|
+
},
|
|
42
|
+
buildSystemPrompt: (a) => buildDiagramSystemPrompt('phase/architecture-diagram', 'architecture-diagram', a),
|
|
43
|
+
buildUserPrompt: (a) => buildDiagramUserPrompt({
|
|
44
|
+
...a,
|
|
45
|
+
task: 'Map the architecture (component & dependency) diagram for',
|
|
46
|
+
mcpName: 'architecture-diagram',
|
|
47
|
+
toolName: 'architecture_diagram',
|
|
48
|
+
process: 'Detect the stack and the top-level structure (workspaces/packages, service boundaries, top-level src dirs, deployment units). Treat each meaningful unit as a component of the right kind (a UI/front-end, backend services, internal modules/packages, datastores, third-party/external systems). Wire dependency edges from the import graph, cross-service calls, and datastore/external access. Aim for a readable ~10-25 components — group leaf files into their module; do not draw a file-level graph.',
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* class-diagram phase: map a UML class diagram — classes / interfaces / enums
|
|
3
|
+
* with their members, and the inheritance / composition / association edges
|
|
4
|
+
* between them. Persisted to the diagrams tables with `type = 'class'`.
|
|
5
|
+
*/
|
|
6
|
+
import { type DiagramPhaseResult } from '../diagram-shared/generate.js';
|
|
7
|
+
export interface ClassDiagramPhaseOptions {
|
|
8
|
+
productId?: string;
|
|
9
|
+
repoId?: string;
|
|
10
|
+
diagramId: string;
|
|
11
|
+
guidance?: string;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function runClassDiagramPhase(options: ClassDiagramPhaseOptions): Promise<DiagramPhaseResult>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* class-diagram phase: map a UML class diagram — classes / interfaces / enums
|
|
3
|
+
* with their members, and the inheritance / composition / association edges
|
|
4
|
+
* between them. Persisted to the diagrams tables with `type = 'class'`.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { generateDiagram, } from '../diagram-shared/generate.js';
|
|
8
|
+
import { buildDiagramSystemPrompt, buildDiagramUserPrompt, } from '../diagram-shared/prompts.js';
|
|
9
|
+
const visibility = z.enum(['public', 'private', 'protected']);
|
|
10
|
+
const classNode = z.object({
|
|
11
|
+
slug: z.string().min(1),
|
|
12
|
+
name: z.string().min(1),
|
|
13
|
+
kind: z.enum(['class', 'interface', 'abstract', 'enum']),
|
|
14
|
+
file: z.string().optional(),
|
|
15
|
+
description: z.string().optional(),
|
|
16
|
+
stereotype: z.string().optional(),
|
|
17
|
+
attributes: z
|
|
18
|
+
.array(z.object({
|
|
19
|
+
name: z.string(),
|
|
20
|
+
type: z.string().optional(),
|
|
21
|
+
visibility: visibility.optional(),
|
|
22
|
+
isStatic: z.boolean().optional(),
|
|
23
|
+
}))
|
|
24
|
+
.optional(),
|
|
25
|
+
methods: z
|
|
26
|
+
.array(z.object({
|
|
27
|
+
name: z.string(),
|
|
28
|
+
params: z.string().optional(),
|
|
29
|
+
returnType: z.string().optional(),
|
|
30
|
+
visibility: visibility.optional(),
|
|
31
|
+
isStatic: z.boolean().optional(),
|
|
32
|
+
isAbstract: z.boolean().optional(),
|
|
33
|
+
}))
|
|
34
|
+
.optional(),
|
|
35
|
+
});
|
|
36
|
+
const classEdge = z.object({
|
|
37
|
+
fromSlug: z.string().min(1),
|
|
38
|
+
toSlug: z.string().min(1),
|
|
39
|
+
kind: z.enum([
|
|
40
|
+
'inheritance',
|
|
41
|
+
'implementation',
|
|
42
|
+
'composition',
|
|
43
|
+
'aggregation',
|
|
44
|
+
'association',
|
|
45
|
+
'dependency',
|
|
46
|
+
]),
|
|
47
|
+
/** Role name or multiplicity, e.g. "1..*", "owns". */
|
|
48
|
+
label: z.string().optional(),
|
|
49
|
+
sourceFile: z.string().optional(),
|
|
50
|
+
});
|
|
51
|
+
export function runClassDiagramPhase(options) {
|
|
52
|
+
return generateDiagram({
|
|
53
|
+
...options,
|
|
54
|
+
workspaceKey: 'class-diagram',
|
|
55
|
+
fenceName: 'class_diagram',
|
|
56
|
+
nounPlural: 'classes',
|
|
57
|
+
edgeNounPlural: 'relationships',
|
|
58
|
+
mcpConfig: {
|
|
59
|
+
name: 'class-diagram',
|
|
60
|
+
toolName: 'class_diagram',
|
|
61
|
+
summaryDescribe: '1-3 sentence narrative of the core types and how they relate.',
|
|
62
|
+
nodesSchema: z.array(classNode),
|
|
63
|
+
nodesDescribe: 'Every class / interface / abstract / enum with its key attributes and methods. slug MUST be unique.',
|
|
64
|
+
edgesSchema: z.array(classEdge),
|
|
65
|
+
edgesDescribe: 'Relationships. kind = inheritance / implementation / composition / aggregation / association / dependency. fromSlug is the child / owner / dependent side. Endpoints MUST reference emitted classes.',
|
|
66
|
+
},
|
|
67
|
+
buildSystemPrompt: (a) => buildDiagramSystemPrompt('phase/class-diagram', 'class-diagram', a),
|
|
68
|
+
buildUserPrompt: (a) => buildDiagramUserPrompt({
|
|
69
|
+
...a,
|
|
70
|
+
task: 'Map a UML class diagram for',
|
|
71
|
+
mcpName: 'class-diagram',
|
|
72
|
+
toolName: 'class_diagram',
|
|
73
|
+
process: 'Detect the language/OO model, then map the core domain types — classes, interfaces, abstract base types, enums — capturing each one\'s defining attributes and methods (visibility + static/abstract where it matters; you need not list every member of huge types). Wire edges: `inheritance` (extends), `implementation` (implements), `composition`/`aggregation` (owns/holds), `association` (references), `dependency` (uses). Prefer the ~30 most important types if there are many.',
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* data-flow phase: clone the product's repo, ask Claude to map every data
|
|
3
3
|
* node (source / dataset / transform / sink / queue / model) and the
|
|
4
4
|
* connections between them into a structured DataFlowExtraction, then
|
|
5
|
-
* persist the result to
|
|
5
|
+
* persist the result to diagrams / diagram_nodes / diagram_edges (rows tagged
|
|
6
6
|
* `type = 'data'`) via the Supabase SDK.
|
|
7
7
|
*
|
|
8
8
|
* Companion to screen-flow: same generation pattern (workspace clone +
|
|
@@ -14,7 +14,7 @@ export interface DataFlowPhaseOptions {
|
|
|
14
14
|
productId?: string;
|
|
15
15
|
/** Repo-only flow: a single repositories row, no product context. */
|
|
16
16
|
repoId?: string;
|
|
17
|
-
|
|
17
|
+
diagramId: string;
|
|
18
18
|
guidance?: string;
|
|
19
19
|
verbose?: boolean;
|
|
20
20
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* data-flow phase: clone the product's repo, ask Claude to map every data
|
|
3
3
|
* node (source / dataset / transform / sink / queue / model) and the
|
|
4
4
|
* connections between them into a structured DataFlowExtraction, then
|
|
5
|
-
* persist the result to
|
|
5
|
+
* persist the result to diagrams / diagram_nodes / diagram_edges (rows tagged
|
|
6
6
|
* `type = 'data'`) via the Supabase SDK.
|
|
7
7
|
*
|
|
8
8
|
* Companion to screen-flow: same generation pattern (workspace clone +
|
|
@@ -13,10 +13,10 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
|
13
13
|
import { getRepositoryBasics } from '../../api/github.js';
|
|
14
14
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
15
15
|
import { getSupabase } from '../../supabase/client.js';
|
|
16
|
-
import { logError, logInfo, logSuccess, logWarning } from '../../utils/logger.js';
|
|
16
|
+
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
17
17
|
import { cleanupIssueRepo } from '../../workspace/workspace-manager.js';
|
|
18
|
+
import { cloneDiagramRepos, describeRepoScope, } from '../diagram-shared/clone-repos.js';
|
|
18
19
|
import { fetchProductBasics } from '../find-shared/mcp.js';
|
|
19
|
-
import { cloneFlowRepos, describeRepoScope } from '../flow-shared/clone-repos.js';
|
|
20
20
|
import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
|
|
21
21
|
import { createDataFlowCaptureState, createDataFlowMcpServer, validateConsistency, } from './mcp-server.js';
|
|
22
22
|
import { createDataFlowSystemPrompt, createDataFlowUserPrompt, } from './prompts.js';
|
|
@@ -28,7 +28,7 @@ const COLUMN_WIDTH = 320;
|
|
|
28
28
|
const ROW_HEIGHT = 220;
|
|
29
29
|
const COLUMNS = 4;
|
|
30
30
|
export async function runDataFlowPhase(options) {
|
|
31
|
-
const { productId, repoId,
|
|
31
|
+
const { productId, repoId, diagramId, guidance, verbose } = options;
|
|
32
32
|
const repoOnly = !productId && Boolean(repoId);
|
|
33
33
|
if (productId) {
|
|
34
34
|
logInfo(`Starting data-flow generation for product ${productId}`);
|
|
@@ -37,9 +37,9 @@ export async function runDataFlowPhase(options) {
|
|
|
37
37
|
logInfo(`Starting data-flow generation for repository ${repoId}`);
|
|
38
38
|
}
|
|
39
39
|
const supabase = getSupabase();
|
|
40
|
-
await
|
|
41
|
-
const repositoryIds = await
|
|
42
|
-
const cloneResult = await
|
|
40
|
+
await markDiagramRunning(supabase, diagramId);
|
|
41
|
+
const repositoryIds = await getDiagramRepositoryIds(supabase, diagramId);
|
|
42
|
+
const cloneResult = await cloneDiagramRepos({
|
|
43
43
|
productId,
|
|
44
44
|
repoId,
|
|
45
45
|
repositoryIds,
|
|
@@ -47,7 +47,7 @@ export async function runDataFlowPhase(options) {
|
|
|
47
47
|
verbose,
|
|
48
48
|
});
|
|
49
49
|
if (!cloneResult.ok) {
|
|
50
|
-
await
|
|
50
|
+
await markDiagramFailed(supabase, diagramId, cloneResult.message);
|
|
51
51
|
return { status: 'error', message: cloneResult.message };
|
|
52
52
|
}
|
|
53
53
|
const { projectDir, cleanupDir, repos } = cloneResult;
|
|
@@ -100,12 +100,12 @@ export async function runDataFlowPhase(options) {
|
|
|
100
100
|
}
|
|
101
101
|
if (!extraction) {
|
|
102
102
|
const msg = 'Data flow extraction failed: agent did not call submit_data_flow and no parseable data_flow block was found in the response';
|
|
103
|
-
await
|
|
103
|
+
await markDiagramFailed(supabase, diagramId, msg);
|
|
104
104
|
return { status: 'error', message: msg };
|
|
105
105
|
}
|
|
106
106
|
logInfo(`Extraction produced ${extraction.nodes.length} nodes / ${extraction.edges.length} connections`);
|
|
107
|
-
const { nodesCreated, edgesCreated } = await
|
|
108
|
-
await
|
|
107
|
+
const { nodesCreated, edgesCreated } = await persistDiagram(supabase, diagramId, extraction);
|
|
108
|
+
await markDiagramSuccess(supabase, diagramId, extraction.summary);
|
|
109
109
|
succeeded = true;
|
|
110
110
|
logSuccess(`Data flow generated: ${nodesCreated} nodes, ${edgesCreated} connections`);
|
|
111
111
|
return {
|
|
@@ -119,7 +119,7 @@ export async function runDataFlowPhase(options) {
|
|
|
119
119
|
catch (error) {
|
|
120
120
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
121
121
|
logError(`Data flow failed: ${errorMessage}`);
|
|
122
|
-
await
|
|
122
|
+
await markDiagramFailed(supabase, diagramId, errorMessage);
|
|
123
123
|
return { status: 'error', message: errorMessage };
|
|
124
124
|
}
|
|
125
125
|
finally {
|
|
@@ -139,12 +139,12 @@ async function resolveRepoBasics(repositoryId, repos) {
|
|
|
139
139
|
description: basics?.description ?? undefined,
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
-
/** Read the ordered repo set a
|
|
143
|
-
async function
|
|
142
|
+
/** Read the ordered repo set a diagram was scoped to (may be empty). */
|
|
143
|
+
async function getDiagramRepositoryIds(supabase, diagramId) {
|
|
144
144
|
const { data } = await supabase
|
|
145
|
-
.from('
|
|
145
|
+
.from('diagrams')
|
|
146
146
|
.select('repository_ids')
|
|
147
|
-
.eq('id',
|
|
147
|
+
.eq('id', diagramId)
|
|
148
148
|
.single();
|
|
149
149
|
return (data?.repository_ids ?? []).filter(Boolean);
|
|
150
150
|
}
|
|
@@ -197,45 +197,45 @@ function tryFallbackParse(resultMessage, assistantText) {
|
|
|
197
197
|
// ============================================================================
|
|
198
198
|
// Persistence
|
|
199
199
|
// ============================================================================
|
|
200
|
-
async function
|
|
200
|
+
async function markDiagramRunning(supabase, diagramId) {
|
|
201
201
|
const { error } = await supabase
|
|
202
|
-
.from('
|
|
202
|
+
.from('diagrams')
|
|
203
203
|
.update({ status: 'running', error: null })
|
|
204
|
-
.eq('id',
|
|
204
|
+
.eq('id', diagramId);
|
|
205
205
|
if (error) {
|
|
206
|
-
logWarning(`Could not mark
|
|
206
|
+
logWarning(`Could not mark diagram as running: ${error.message}`);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
-
async function
|
|
209
|
+
async function markDiagramFailed(supabase, diagramId, errorMessage) {
|
|
210
210
|
await supabase
|
|
211
|
-
.from('
|
|
211
|
+
.from('diagrams')
|
|
212
212
|
.update({
|
|
213
213
|
status: 'failed',
|
|
214
214
|
error: errorMessage,
|
|
215
215
|
completed_at: new Date().toISOString(),
|
|
216
216
|
})
|
|
217
|
-
.eq('id',
|
|
217
|
+
.eq('id', diagramId);
|
|
218
218
|
}
|
|
219
|
-
async function
|
|
219
|
+
async function markDiagramSuccess(supabase, diagramId, summary) {
|
|
220
220
|
await supabase
|
|
221
|
-
.from('
|
|
221
|
+
.from('diagrams')
|
|
222
222
|
.update({
|
|
223
223
|
status: 'success',
|
|
224
224
|
summary,
|
|
225
225
|
error: null,
|
|
226
226
|
completed_at: new Date().toISOString(),
|
|
227
227
|
})
|
|
228
|
-
.eq('id',
|
|
228
|
+
.eq('id', diagramId);
|
|
229
229
|
}
|
|
230
|
-
async function
|
|
231
|
-
await supabase.from('
|
|
232
|
-
await supabase.from('
|
|
230
|
+
async function persistDiagram(supabase, diagramId, extraction) {
|
|
231
|
+
await supabase.from('diagram_edges').delete().eq('diagram_id', diagramId);
|
|
232
|
+
await supabase.from('diagram_nodes').delete().eq('diagram_id', diagramId);
|
|
233
233
|
if (extraction.nodes.length === 0) {
|
|
234
234
|
return { nodesCreated: 0, edgesCreated: 0 };
|
|
235
235
|
}
|
|
236
|
-
const nodeRows = extraction.nodes.map((n, i) => buildNodeRow(
|
|
236
|
+
const nodeRows = extraction.nodes.map((n, i) => buildNodeRow(diagramId, n, i));
|
|
237
237
|
const { data: insertedNodes, error: nodesError } = await supabase
|
|
238
|
-
.from('
|
|
238
|
+
.from('diagram_nodes')
|
|
239
239
|
.insert(nodeRows)
|
|
240
240
|
.select('id, slug');
|
|
241
241
|
if (nodesError) {
|
|
@@ -243,11 +243,11 @@ async function persistFlow(supabase, flowId, extraction) {
|
|
|
243
243
|
}
|
|
244
244
|
const slugToId = new Map((insertedNodes ?? []).map((n) => [n.slug, n.id]));
|
|
245
245
|
const edgeRows = extraction.edges
|
|
246
|
-
.map((e) => buildEdgeRow(
|
|
246
|
+
.map((e) => buildEdgeRow(diagramId, e, slugToId))
|
|
247
247
|
.filter((e) => e !== null);
|
|
248
248
|
if (edgeRows.length > 0) {
|
|
249
249
|
const { error: edgesError } = await supabase
|
|
250
|
-
.from('
|
|
250
|
+
.from('diagram_edges')
|
|
251
251
|
.insert(edgeRows);
|
|
252
252
|
if (edgesError) {
|
|
253
253
|
throw new Error(`Failed to insert edges: ${edgesError.message}`);
|
|
@@ -258,9 +258,9 @@ async function persistFlow(supabase, flowId, extraction) {
|
|
|
258
258
|
edgesCreated: edgeRows.length,
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
|
-
function buildNodeRow(
|
|
261
|
+
function buildNodeRow(diagramId, node, index) {
|
|
262
262
|
return {
|
|
263
|
-
|
|
263
|
+
diagram_id: diagramId,
|
|
264
264
|
slug: node.slug,
|
|
265
265
|
name: node.name,
|
|
266
266
|
kind: node.kind,
|
|
@@ -269,14 +269,14 @@ function buildNodeRow(flowId, node, index) {
|
|
|
269
269
|
position_y: Math.floor(index / COLUMNS) * ROW_HEIGHT,
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
|
-
function buildEdgeRow(
|
|
272
|
+
function buildEdgeRow(diagramId, edge, slugToId) {
|
|
273
273
|
const fromId = slugToId.get(edge.fromSlug);
|
|
274
274
|
const toId = slugToId.get(edge.toSlug);
|
|
275
275
|
if (!fromId || !toId) {
|
|
276
276
|
return null;
|
|
277
277
|
}
|
|
278
278
|
return {
|
|
279
|
-
|
|
279
|
+
diagram_id: diagramId,
|
|
280
280
|
from_node_id: fromId,
|
|
281
281
|
to_node_id: toId,
|
|
282
282
|
label: edge.label ?? null,
|
|
@@ -27,8 +27,8 @@ export declare function createSubmitDataFlowTool(state: DataFlowCaptureState): i
|
|
|
27
27
|
kind: z.ZodEnum<{
|
|
28
28
|
model: "model";
|
|
29
29
|
source: "source";
|
|
30
|
-
dataset: "dataset";
|
|
31
30
|
transform: "transform";
|
|
31
|
+
dataset: "dataset";
|
|
32
32
|
sink: "sink";
|
|
33
33
|
queue: "queue";
|
|
34
34
|
}>;
|
|
@@ -53,7 +53,7 @@ export function validateConsistency(extraction) {
|
|
|
53
53
|
for (const node of extraction.nodes) {
|
|
54
54
|
if (slugs.has(node.slug)) {
|
|
55
55
|
return {
|
|
56
|
-
error: `Duplicate node slug "${node.slug}". Each node.slug MUST be unique within the
|
|
56
|
+
error: `Duplicate node slug "${node.slug}". Each node.slug MUST be unique within the diagram. Re-call submit_data_flow with deduplicated nodes.`,
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
slugs.add(node.slug);
|
|
@@ -87,7 +87,7 @@ export function createSubmitDataFlowTool(state) {
|
|
|
87
87
|
.describe('1-3 sentence narrative of what this system does with data and the primary pipelines.'),
|
|
88
88
|
nodes: z
|
|
89
89
|
.array(dataNodeSchema)
|
|
90
|
-
.describe('Every data node: source / dataset / transform / sink / queue / model. node.slug MUST be unique within the
|
|
90
|
+
.describe('Every data node: source / dataset / transform / sink / queue / model. node.slug MUST be unique within the diagram.'),
|
|
91
91
|
edges: z
|
|
92
92
|
.array(dataEdgeSchema)
|
|
93
93
|
.describe('Connections. fromSlug = upstream, toSlug = downstream. Every fromSlug / toSlug MUST reference a slug present in nodes; drop edges whose endpoints you did not emit.'),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* unified <DataNodePreview> component.
|
|
9
9
|
*
|
|
10
10
|
* Companion to ScreenSchema: same flow-graph shape (nodes + edges with a
|
|
11
|
-
* shared `
|
|
11
|
+
* shared `diagrams` table storing the JSONB schema), different domain. Data
|
|
12
12
|
* flow edges describe how data moves between nodes, not user navigation.
|
|
13
13
|
*/
|
|
14
14
|
export type DataNodeKind = 'source' | 'dataset' | 'transform' | 'sink' | 'queue' | 'model';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* unified <DataNodePreview> component.
|
|
9
9
|
*
|
|
10
10
|
* Companion to ScreenSchema: same flow-graph shape (nodes + edges with a
|
|
11
|
-
* shared `
|
|
11
|
+
* shared `diagrams` table storing the JSONB schema), different domain. Data
|
|
12
12
|
* flow edges describe how data moves between nodes, not user navigation.
|
|
13
13
|
*/
|
|
14
14
|
// ============================================================================
|