edsger 0.59.0 → 0.61.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 (37) hide show
  1. package/dist/auth/env-store.js +3 -0
  2. package/dist/commands/data-flow/index.d.ts +17 -0
  3. package/dist/commands/data-flow/index.js +46 -0
  4. package/dist/commands/recipes/index.d.ts +15 -0
  5. package/dist/commands/recipes/index.js +34 -0
  6. package/dist/commands/screen-flow/index.d.ts +4 -4
  7. package/dist/commands/screen-flow/index.js +5 -5
  8. package/dist/commands/sync-aws/index.d.ts +16 -0
  9. package/dist/commands/sync-aws/index.js +184 -0
  10. package/dist/commands/sync-datadog/index.d.ts +16 -0
  11. package/dist/commands/sync-datadog/index.js +199 -0
  12. package/dist/commands/sync-terraform/index.d.ts +16 -0
  13. package/dist/commands/sync-terraform/index.js +211 -0
  14. package/dist/index.js +99 -8
  15. package/dist/phases/data-flow/index.d.ts +25 -0
  16. package/dist/phases/data-flow/index.js +257 -0
  17. package/dist/phases/data-flow/mcp-server.d.ts +85 -0
  18. package/dist/phases/data-flow/mcp-server.js +140 -0
  19. package/dist/phases/data-flow/prompts.d.ts +14 -0
  20. package/dist/phases/data-flow/prompts.js +36 -0
  21. package/dist/phases/data-flow/types.d.ts +71 -0
  22. package/dist/phases/data-flow/types.js +86 -0
  23. package/dist/phases/output-contracts.js +71 -0
  24. package/dist/phases/recipes/index.d.ts +56 -0
  25. package/dist/phases/recipes/index.js +301 -0
  26. package/dist/phases/recipes/mcp-server.d.ts +63 -0
  27. package/dist/phases/recipes/mcp-server.js +204 -0
  28. package/dist/phases/recipes/prompts.d.ts +35 -0
  29. package/dist/phases/recipes/prompts.js +105 -0
  30. package/dist/phases/recipes/types.d.ts +42 -0
  31. package/dist/phases/recipes/types.js +16 -0
  32. package/dist/phases/screen-flow/index.d.ts +2 -2
  33. package/dist/phases/screen-flow/index.js +27 -15
  34. package/dist/phases/screen-flow/mcp-server.d.ts +1 -1
  35. package/dist/skills/phase/data-flow/SKILL.md +82 -0
  36. package/package.json +3 -3
  37. package/vitest.config.ts +1 -1
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Prompts for the recipes phase.
3
+ *
4
+ * Agent's job: explore the cloned product repo and identify each non-trivial
5
+ * CAPABILITY the product implements ("AI video generation", "PDF export",
6
+ * "real-time presence", "Stripe subscription billing", etc.) and document
7
+ * HOW the capability is built — which services / tools / libraries are
8
+ * chained together. NOT a tech-stack inventory.
9
+ *
10
+ * The agent is fed the team's existing recipes (compact form: id + name +
11
+ * summary + services) and must, for each capability it identifies, pick one
12
+ * of:
13
+ * - link_recipe — existing recipe describes this capability correctly,
14
+ * just record this product uses it
15
+ * - update_recipe — existing recipe is close but the agent has better /
16
+ * more accurate info from this repo; overwrite
17
+ * - create_recipe — no existing recipe matches; new entry
18
+ * - unlink_recipe — this product was previously linked to a recipe that
19
+ * is no longer present in the repo
20
+ *
21
+ * Submission is via MCP tool calls only — no fenced JSON.
22
+ */
23
+ export function createRecipesSystemPrompt() {
24
+ return `You are a senior staff engineer cataloguing the IMPLEMENTATION RECIPES a product uses.
25
+
26
+ The current working directory is a fresh clone of the product's repository. Use Glob/Grep/Read (and Bash for git/log when helpful) to explore it before writing.
27
+
28
+ ## What is a "recipe"?
29
+
30
+ A recipe describes ONE non-trivial capability the product delivers, and HOW it is built: which services, libraries, tools, and techniques are chained together to make it work end-to-end.
31
+
32
+ Good recipe names: "AI video generation", "PDF export with custom fonts", "Real-time collaborative editing", "Stripe subscription billing with proration", "GitHub OAuth device flow".
33
+ Bad recipe names: "React frontend", "TypeScript", "Tailwind", "Authentication" (too vague).
34
+
35
+ A recipe is the WHAT + HOW pair. The WHAT is the user-facing capability. The HOW is the specific stack of services chained to deliver it. Two products can implement the same capability with the same stack — that is one recipe, linked from both products. Two products that implement "OCR" but one uses Tesseract and the other uses PaddleOCR are TWO different recipes (same capability name, different services).
36
+
37
+ ## Output protocol
38
+
39
+ The MCP server exposes these tools — use them, do NOT paste content as fenced code:
40
+
41
+ 1. \`get_recipe_detail({ recipe_id })\` — fetch the full content of an existing recipe when you need to decide whether to link, update, or create. The prompt only gives you names + summaries + services to keep your context small.
42
+
43
+ 2. \`create_recipe({ name, summary, content, services, evidence })\` — register a brand-new recipe in the team library AND link this product to it.
44
+
45
+ 3. \`update_recipe({ recipe_id, summary?, content?, services?, evidence })\` — overwrite an existing recipe with better information you discovered in this repo AND link this product to it. Use when the existing recipe is broadly correct but lacks detail / has outdated services / has a vague summary.
46
+
47
+ 4. \`link_recipe({ recipe_id, evidence })\` — just link this product to an existing recipe that already describes the capability accurately. The recipe's content is not touched.
48
+
49
+ 5. \`unlink_recipe({ recipe_id })\` — drop a previously-linked recipe that the agent has confirmed is NO LONGER present in this repo (capability was removed, or the previous scan was wrong). Only unlink things in the "Currently linked to this product" list.
50
+
51
+ Call exactly one of create / update / link for each capability you identify. Call unlink at the end for any previously-linked recipe you could not corroborate. When you are done with every capability and every unlink, end your turn — no summary message, no JSON dump.
52
+
53
+ ## Rules
54
+
55
+ - \`services\` is a SORTED list of canonical service / tool / library names ("ElevenLabs", "ffmpeg", "Whisper", "Stripe", "Resend"). Sort alphabetically so equivalent recipes compare cleanly. Don't include language/framework names ("React", "Node") unless they're the actual differentiator.
56
+ - \`content\` is markdown. Aim for 200–800 words per recipe. Structure: a short intro sentence, then numbered steps describing the flow, then a "Files" subsection with relative paths (e.g. \`src/services/video/encoder.ts\`).
57
+ - \`evidence\` is one paragraph of plain text saying where in THIS product's repo the recipe shows up: file paths, key entry points. Used to help reviewers verify the link.
58
+ - Don't invent capabilities. If the repo is small or only does one thing, emit one recipe — don't pad.
59
+ - Prefer LINK over UPDATE over CREATE in that order. Reuse the team library aggressively; the whole point is cross-product visibility into the same recipe.
60
+ - When the same capability is implemented with a clearly different stack, that's a NEW recipe even if the name collides. Disambiguate the name (\"PDF export (Puppeteer)\" vs \"PDF export (pdf-lib)\") so the team can tell them apart.`;
61
+ }
62
+ export function createRecipesUserPrompt(ctx) {
63
+ const lines = [];
64
+ lines.push(`# Product: ${ctx.productName}`);
65
+ if (ctx.productDescription) {
66
+ lines.push('');
67
+ lines.push('## Description');
68
+ lines.push(ctx.productDescription);
69
+ }
70
+ if (ctx.guidance && ctx.guidance.trim()) {
71
+ lines.push('');
72
+ lines.push('## Reviewer guidance (focus or exclusions)');
73
+ lines.push(ctx.guidance.trim());
74
+ }
75
+ lines.push('');
76
+ lines.push('## Existing recipes in this team');
77
+ if (ctx.teamRecipes.length === 0) {
78
+ lines.push('(none — every recipe you identify will be a `create_recipe`.)');
79
+ }
80
+ else {
81
+ lines.push('Prefer `link_recipe` / `update_recipe` over `create_recipe` when one of these matches the capability you found. Call `get_recipe_detail` if you need to see the full content before deciding.');
82
+ lines.push('');
83
+ for (const r of ctx.teamRecipes) {
84
+ const svc = r.services.length > 0 ? ` services=[${r.services.join(', ')}]` : '';
85
+ const sum = r.summary ? ` — ${r.summary}` : '';
86
+ lines.push(`- id=${r.id} "${r.name}"${svc}${sum}`);
87
+ }
88
+ }
89
+ lines.push('');
90
+ lines.push('## Currently linked to this product');
91
+ if (ctx.existingLinks.length === 0) {
92
+ lines.push('(none)');
93
+ }
94
+ else {
95
+ lines.push('If you confirm any of these is no longer present in the repo, call `unlink_recipe` with its id.');
96
+ lines.push('');
97
+ for (const l of ctx.existingLinks) {
98
+ lines.push(`- id=${l.recipeId} "${l.name}"`);
99
+ }
100
+ }
101
+ lines.push('');
102
+ lines.push('## Task');
103
+ lines.push('Explore the cloned repository, identify every non-trivial capability the product delivers, and for each call exactly one of `create_recipe` / `update_recipe` / `link_recipe`. Then call `unlink_recipe` for any previously-linked recipe you could not corroborate. End your turn when done.');
104
+ return lines.join('\n');
105
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shapes the recipes MCP tools accept and return. The Zod schemas live in
3
+ * mcp-server.ts; these plain TS types are what the rest of the phase
4
+ * (orchestrator, persistence helpers, tests) consumes so they don't have
5
+ * to inflate Zod into their dependency graph.
6
+ */
7
+ export interface RecipeSummary {
8
+ id: string;
9
+ name: string;
10
+ summary: string | null;
11
+ services: string[];
12
+ }
13
+ export interface RecipeDetail extends RecipeSummary {
14
+ content: string | null;
15
+ }
16
+ export interface CreateRecipeArgs {
17
+ name: string;
18
+ summary: string;
19
+ content: string;
20
+ services: string[];
21
+ evidence: string;
22
+ }
23
+ export interface UpdateRecipeArgs {
24
+ recipeId: string;
25
+ summary?: string;
26
+ content?: string;
27
+ services?: string[];
28
+ evidence: string;
29
+ }
30
+ export interface LinkRecipeArgs {
31
+ recipeId: string;
32
+ evidence: string;
33
+ }
34
+ export interface UnlinkRecipeArgs {
35
+ recipeId: string;
36
+ }
37
+ export declare const RECIPE_SUMMARY_MAX = 500;
38
+ export declare const RECIPE_CONTENT_MAX = 50000;
39
+ export declare const RECIPE_NAME_MAX = 200;
40
+ export declare const RECIPE_SERVICES_MAX = 20;
41
+ export declare const RECIPE_SERVICE_NAME_MAX = 80;
42
+ export declare const RECIPE_EVIDENCE_MAX = 4000;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shapes the recipes MCP tools accept and return. The Zod schemas live in
3
+ * mcp-server.ts; these plain TS types are what the rest of the phase
4
+ * (orchestrator, persistence helpers, tests) consumes so they don't have
5
+ * to inflate Zod into their dependency graph.
6
+ */
7
+ // Defensive caps mirroring the DB CHECK constraints from
8
+ // 20260521000000_create_recipes.sql. Keeping the limits here lets the MCP
9
+ // tool reject oversized output with an actionable error message instead of
10
+ // getting a Postgres constraint violation.
11
+ export const RECIPE_SUMMARY_MAX = 500;
12
+ export const RECIPE_CONTENT_MAX = 50_000;
13
+ export const RECIPE_NAME_MAX = 200;
14
+ export const RECIPE_SERVICES_MAX = 20;
15
+ export const RECIPE_SERVICE_NAME_MAX = 80;
16
+ export const RECIPE_EVIDENCE_MAX = 4000;
@@ -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 screen_flows /
5
- * screen_flow_nodes / screen_flow_edges via the Supabase SDK.
4
+ * ScreenFlowExtraction, then persist the result to flows / flow_nodes /
5
+ * flow_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.
@@ -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 screen_flows /
5
- * screen_flow_nodes / screen_flow_edges via the Supabase SDK.
4
+ * ScreenFlowExtraction, then persist the result to flows / flow_nodes /
5
+ * flow_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.
@@ -186,7 +186,7 @@ function tryFallbackParse(resultMessage, assistantText) {
186
186
  // ============================================================================
187
187
  async function markFlowRunning(supabase, flowId) {
188
188
  const { error } = await supabase
189
- .from('screen_flows')
189
+ .from('flows')
190
190
  .update({ status: 'running', error: null })
191
191
  .eq('id', flowId);
192
192
  if (error) {
@@ -195,7 +195,7 @@ async function markFlowRunning(supabase, flowId) {
195
195
  }
196
196
  async function markFlowFailed(supabase, flowId, errorMessage) {
197
197
  await supabase
198
- .from('screen_flows')
198
+ .from('flows')
199
199
  .update({
200
200
  status: 'failed',
201
201
  error: errorMessage,
@@ -204,9 +204,23 @@ async function markFlowFailed(supabase, flowId, errorMessage) {
204
204
  .eq('id', flowId);
205
205
  }
206
206
  async function persistTheme(supabase, flowId, theme) {
207
+ // Theme is screen-flow-specific; stash it inside the generic options JSONB.
208
+ const { data, error: readError } = await supabase
209
+ .from('flows')
210
+ .select('options')
211
+ .eq('id', flowId)
212
+ .single();
213
+ if (readError) {
214
+ logWarning(`Could not read flow options: ${readError.message}`);
215
+ return;
216
+ }
217
+ const nextOptions = {
218
+ ...(data?.options ?? {}),
219
+ theme,
220
+ };
207
221
  const { error } = await supabase
208
- .from('screen_flows')
209
- .update({ theme })
222
+ .from('flows')
223
+ .update({ options: nextOptions })
210
224
  .eq('id', flowId);
211
225
  if (error) {
212
226
  logWarning(`Could not persist extracted theme: ${error.message}`);
@@ -214,7 +228,7 @@ async function persistTheme(supabase, flowId, theme) {
214
228
  }
215
229
  async function markFlowSuccess(supabase, flowId, summary) {
216
230
  await supabase
217
- .from('screen_flows')
231
+ .from('flows')
218
232
  .update({
219
233
  status: 'success',
220
234
  summary,
@@ -225,14 +239,14 @@ async function markFlowSuccess(supabase, flowId, summary) {
225
239
  }
226
240
  async function persistFlow(supabase, flowId, extraction) {
227
241
  // Re-runs replace prior content for the same flow row.
228
- await supabase.from('screen_flow_edges').delete().eq('flow_id', flowId);
229
- await supabase.from('screen_flow_nodes').delete().eq('flow_id', flowId);
242
+ await supabase.from('flow_edges').delete().eq('flow_id', flowId);
243
+ await supabase.from('flow_nodes').delete().eq('flow_id', flowId);
230
244
  if (extraction.nodes.length === 0) {
231
245
  return { nodesCreated: 0, edgesCreated: 0 };
232
246
  }
233
247
  const nodeRows = extraction.nodes.map((n, i) => buildNodeRow(flowId, n, i));
234
248
  const { data: insertedNodes, error: nodesError } = await supabase
235
- .from('screen_flow_nodes')
249
+ .from('flow_nodes')
236
250
  .insert(nodeRows)
237
251
  .select('id, slug');
238
252
  if (nodesError) {
@@ -244,7 +258,7 @@ async function persistFlow(supabase, flowId, extraction) {
244
258
  .filter((e) => e !== null);
245
259
  if (edgeRows.length > 0) {
246
260
  const { error: edgesError } = await supabase
247
- .from('screen_flow_edges')
261
+ .from('flow_edges')
248
262
  .insert(edgeRows);
249
263
  if (edgesError) {
250
264
  throw new Error(`Failed to insert edges: ${edgesError.message}`);
@@ -260,8 +274,6 @@ function buildNodeRow(flowId, node, index) {
260
274
  flow_id: flowId,
261
275
  slug: node.slug,
262
276
  name: node.name,
263
- route: node.route ?? null,
264
- file: node.file ?? null,
265
277
  kind: node.kind,
266
278
  schema: node,
267
279
  position_x: (index % COLUMNS) * COLUMN_WIDTH,
@@ -278,8 +290,8 @@ function buildEdgeRow(flowId, edge, slugToId) {
278
290
  flow_id: flowId,
279
291
  from_node_id: fromId,
280
292
  to_node_id: toId,
281
- trigger_label: edge.triggerLabel,
282
- trigger_file: edge.triggerFile ?? null,
293
+ label: edge.triggerLabel,
294
+ source_anchor: edge.triggerFile ?? null,
283
295
  kind: edge.kind,
284
296
  };
285
297
  }
@@ -183,10 +183,10 @@ export declare function createSubmitScreenFlowTool(state: ScreenFlowCaptureState
183
183
  export declare function createRecordProgressTool(sink?: ScreenFlowProgressSink): import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
184
184
  phase: z.ZodEnum<{
185
185
  detection: "detection";
186
+ submission: "submission";
186
187
  routing: "routing";
187
188
  screens: "screens";
188
189
  transitions: "transitions";
189
- submission: "submission";
190
190
  }>;
191
191
  message: z.ZodString;
192
192
  }>;
@@ -0,0 +1,82 @@
1
+ ---
2
+ description: Map a product's data nodes (sources, datasets, transforms, sinks, queues, models) and the connections between them into a structured data flow
3
+ kind: phase
4
+ user-invocable: false
5
+ ---
6
+
7
+ You are a senior data engineer mapping a codebase's **data flow** — where data originates, what stores it, what computes on it, where it terminates, and how it moves between those nodes. Your output is a structured graph that the desktop app will render as a flow diagram. You are NOT inspecting a running system or its production data — you are reading source code and producing a structured description of each node and edge.
8
+
9
+ **What counts as a data node**:
10
+
11
+ - `source` — external input: API ingestion job, scraper, webhook handler that produces a record, sensor reading, manual upload form
12
+ - `dataset` — stored collection: DB table (Postgres / SQLite / Mongo / DynamoDB row class), object-store bucket (S3 / GCS), file on disk, in-memory cache (Redis hash, in-process store), Parquet / CSV / JSONL on disk
13
+ - `transform` — computation: ETL stage, data normalizer, scorer, aggregation job, pipeline step, scheduled job, queue worker that mutates payloads
14
+ - `sink` — terminal output: external API write (Stripe, Slack, email), generated report, alert, file export
15
+ - `queue` — async message bus: SQS / Kafka topic / NATS subject / Redis pub/sub channel / in-memory event emitter
16
+ - `model` — ML model or LLM/scoring service: OpenAI / Anthropic / Gemini call site, local model invocation, ranking model. Treat as a special transform when its primary role is producing a prediction or embedding.
17
+
18
+ **Distinguish from screen-flow**: this is about **data movement**, not user navigation. UI components are not data nodes. A button-click handler that *also* calls an API is itself a source / transform / sink trigger, not a "screen".
19
+
20
+ **For each node, extract a DataNodeSchema** with these fields:
21
+
22
+ - `slug` — stable short identifier (kebab-case, e.g. `raw-events`, `enrich-user`)
23
+ - `name` — human-readable display name
24
+ - `kind` — one of the six above
25
+ - `file` — primary source file path relative to repo root (schema/migration file for datasets, definition file for transforms/sinks/sources/queues, model invocation file for models)
26
+ - `description?` — one short sentence ("Nightly job that scrapes vendor catalogs and normalizes them into the products table")
27
+ - `tech?` — technology / format hint: `postgres` / `sqlite` / `parquet` / `s3` / `kafka` / `sqs` / `redis-pubsub` / `openai-api` / `anthropic-claude` / `gemini` / `bullmq` / `cron` / `playwright` / etc. Free-form, prefer the most common name
28
+ - `schedule?` — for transforms / sources: `cron 0 0 * * *`, `on-event`, `manual`, `continuous`, `on-webhook`
29
+ - `inputs?` — array of `{ name, type?, required?, description? }`. For transforms: what fields it reads. For sinks: what fields it sends. For models: prompt template variables / feature names
30
+ - `outputs?` — array of `{ name, type?, required?, description? }`. For sources: what fields it emits. For datasets: column schema. For transforms: emitted record fields. For queues: message payload fields. For models: prediction / embedding fields
31
+ - `sample?` — `{ columns: [string], rows: [[string]] }` — at most 4 sample rows for datasets only. Fabricate realistic placeholder content; this is a documentation artifact
32
+ - `stats?` — array of `{ label, value }` for volume / latency / count hints (e.g. `{ label: "rows", value: "~2M" }`, `{ label: "p50 latency", value: "180ms" }`). Best-effort, leave empty if nothing useful is visible from code
33
+
34
+ **Connections (edges)**: direction is always **data movement**. `fromSlug` is upstream (origin), `toSlug` is downstream (destination). Sources to extract from:
35
+
36
+ - Database access calls: `db.query(...)`, ORM model calls (Prisma / SQLAlchemy / TypeORM / ActiveRecord), Supabase / Firestore SDK calls, raw SQL strings referencing a known table
37
+ - Object-store reads/writes: `s3.getObject` / `s3.putObject`, `fs.readFile` / `fs.writeFile` on a known dataset path
38
+ - Queue / topic publishes & consumes: `producer.send(...)`, `subscribe('topic-name', ...)`, BullMQ `queue.add` / `worker.process`
39
+ - Model invocations: SDK calls like `anthropic.messages.create`, `openai.chat.completions.create`, local model `predict(...)` — wire the calling transform / sink to the model node
40
+ - Cron / scheduler triggers: cron config → `control` edge from a synthetic `cron` node to the job, OR represent cron schedule on the job's `schedule` field if there's no other reason to model the trigger separately
41
+ - External API calls (HTTP fetch to a third-party): emit a sink node for the third party and a `data` edge to it
42
+
43
+ For each edge produce:
44
+
45
+ - `fromSlug` — upstream node's slug
46
+ - `toSlug` — downstream node's slug
47
+ - `kind`:
48
+ - `data` — plain data movement (most common: transform reads dataset, transform writes to sink)
49
+ - `event` — async event/message passed via a queue or pub/sub
50
+ - `control` — control-flow trigger without data payload (cron triggers job, file-watcher triggers handler)
51
+ - `derives` — lineage: downstream node is materialized from upstream (rollup, materialized view, snapshot)
52
+ - `label?` — free-form descriptor (`nightly batch`, `on user signup`, `embedding`, `daily rollup`)
53
+ - `sourceFile?` — file containing the connection definition (when distinct from the from-node's file)
54
+
55
+ **Discipline**:
56
+
57
+ - Be grounded — every node MUST correspond to actual code (a table, a file, a queue, a model invocation, etc). No invented datasets.
58
+ - Deduplicate: if multiple files reference the same dataset / queue / model, keep one node and emit edges from each consumer.
59
+ - Prefer fewer, clearer nodes. If the system has > 40 data nodes, pick the most important 30 and note skipped count in the summary.
60
+ - Datasets, queues, and models are nouns; transforms and sources are verbs. Name them accordingly.
61
+ - Edges always point to a node you also emit. Drop any edge whose target you couldn't extract.
62
+ - A model invocation is its own node, not part of the calling transform — this lets a reader see "all the places we call Claude" at a glance.
63
+
64
+ **Process**:
65
+
66
+ <!-- if:hasCodebase -->
67
+
68
+ 1. **Detect the stack**: Read `package.json` / `pyproject.toml` / `go.mod` / `Cargo.toml` / `requirements.txt` / `Gemfile` to identify the runtime and obvious data libraries.
69
+ 2. **Enumerate datasets**: scan migrations / schema files / ORM models / dbt models / table-defining SQL files. Each table or collection becomes a `dataset` node.
70
+ 3. **Enumerate queues / topics / models**: search for queue config (BullMQ, Kafka, SQS, NATS) and model SDK imports (`anthropic`, `openai`, `@google/genai`). Each becomes a `queue` or `model` node.
71
+ 4. **Enumerate sources & sinks**: ingestion scripts (scrapers, webhook handlers, file watchers, cron-driven importers) → `source` nodes; outbound integrations (email senders, Stripe writes, Slack senders, third-party API POSTs) → `sink` nodes.
72
+ 5. **Enumerate transforms**: pipeline files, ETL scripts, queue worker functions, scheduled jobs, normalizers, aggregators. Each becomes a `transform` node.
73
+ 6. **Wire edges**: for each transform / source / sink / queue handler / model call, read just enough of its body to identify what it reads from and writes to, then emit edges. Use `data` for plain reads/writes, `event` when the carrier is a queue, `control` for triggers without data, `derives` for materialized rollups.
74
+ 7. **Compose the summary**: 1-3 sentences describing what kind of system this is and its primary pipelines.
75
+
76
+ <!-- endif -->
77
+ <!-- if:!hasCodebase -->
78
+
79
+ 8. **Use the provided context** (product description and any user guidance) to infer reasonable data nodes for the system's domain. Be explicit in the summary that the flow is inferred rather than extracted.
80
+ 9. Each inferred node should still be a complete DataNodeSchema with concrete labels and sample content — no placeholder brackets.
81
+
82
+ <!-- endif -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.59.0",
3
+ "version": "0.61.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"
@@ -50,8 +50,8 @@
50
50
  "commander": "^12.0.0",
51
51
  "cosmiconfig": "^9.0.0",
52
52
  "dotenv": "^16.4.5",
53
- "edsger-contract": "0.4.0",
54
- "edsger-tools": "0.4.0",
53
+ "edsger-contract": "0.5.0",
54
+ "edsger-tools": "0.5.0",
55
55
  "gray-matter": "^4.0.3",
56
56
  "zod": "^4.0.0"
57
57
  },
package/vitest.config.ts CHANGED
@@ -16,7 +16,7 @@ export default defineConfig({
16
16
  'src/phases/sync-sentry-issues/__tests__/**/*.test.ts',
17
17
  'src/phases/sync-shared/__tests__/**/*.test.ts',
18
18
  'src/phases/screen-flow/__tests__/**/*.test.ts',
19
- 'src/phases/product-techniques/__tests__/**/*.test.ts',
19
+ 'src/phases/recipes/__tests__/**/*.test.ts',
20
20
  'src/types/__tests__/**/*.test.ts',
21
21
  'src/commands/find-smells/__tests__/**/*.test.ts',
22
22
  ],