feat: Add subsniffer kit — subscription audit agent#208
feat: Add subsniffer kit — subscription audit agent#208SatyamSingh8306 wants to merge 6 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughChangesSubSniffer adds a Lamatic subscription-audit flow, server-side execution, a Next.js interface for submitting statements and viewing results, deployment configuration, environment templates, and setup documentation. SubSniffer audit flow
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
:robot_face: AgentKit Structural ValidationNew Contributions Detected
Check Results
🎉 All checks passed! This contribution follows the AgentKit structure. |
There was a problem hiding this comment.
Actionable comments posted: 15
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@kits/subsniffer/apps/actions/orchestrate.ts`:
- Around line 24-25: Update the response normalization in the orchestration
handler to accept only the expected `{ analysis, report }` shape from
`res?.result?.answer`, `res?.result`, or `res?.output`; remove the final raw
`res` fallback and throw a descriptive error when none of the known shapes
match.
- Around line 1-37: Update auditSubscriptions to import ../../lamatic.config and
use its step definitions when selecting or executing the Lamatic flow, instead
of reading SUBSNIFFER_FLOW_ID directly from process.env. Preserve the existing
client execution, response normalization, and error handling while ensuring the
imported configuration drives the flow identifier or definition.
In `@kits/subsniffer/apps/app/globals.css`:
- Around line 8-12: Replace the hardcoded colors in the body selector with CSS
custom properties, defining appropriate --background and --foreground variables
and referencing them via var(). Follow the existing variable conventions in
globals.css and ensure the body uses those variables for background and color.
In `@kits/subsniffer/apps/app/page.tsx`:
- Around line 72-99: Refactor the form containing run to use react-hook-form
with a zodResolver and a zod schema requiring a non-empty statement while
allowing optional goals; replace the statement/goals useState bindings with
register, handleSubmit, and formState errors, and preserve the existing
auditSubscriptions flow and loading/error handling. Add react-hook-form, zod,
and the required resolver dependency to package.json, and update the submit
handler and field error rendering accordingly.
- Around line 125-145: Add matching htmlFor and id attributes for the statement
and goals form controls: update the two labels and their corresponding Textarea
components in the page component, using unique, descriptive identifiers
consistently.
- Around line 259-268: Validate the LLM-provided cancellation_url before
rendering it as an href. Add a URL sanitizer helper that parses the value and
only returns URLs using http: or https: protocols, then update the cancellation
link block to use the sanitized result and render the link only when it is
valid.
In `@kits/subsniffer/apps/components/ui/badge.tsx`:
- Around line 4-17: Wrap the exported Badge component with React.forwardRef,
adding an HTMLSpanElement ref parameter and forwarding it to the rendered span
while preserving the existing className merging and props behavior; update the
component display name if needed for linting or debugging.
In `@kits/subsniffer/apps/components/ui/button.tsx`:
- Around line 6-22: Update the Button component to use React.forwardRef, adding
an HTMLButtonElement ref parameter and passing it to the underlying button
element while preserving the existing props, variant handling, and styling;
assign an appropriate display name if needed for debugging.
In `@kits/subsniffer/apps/components/ui/textarea.tsx`:
- Around line 4-16: The Textarea component does not forward refs, preventing
react-hook-form registration and programmatic focus. Refactor Textarea to use
React.forwardRef<HTMLTextAreaElement,
React.TextareaHTMLAttributes<HTMLTextAreaElement>>, accept a ref parameter, and
pass it to the underlying textarea while preserving existing className handling
and props.
In `@kits/subsniffer/apps/lib/lamatic-client.ts`:
- Line 5: Replace the any-typed client variable with the Lamatic SDK client
type, preserving its nullable initial state. Update the declaration of client in
the lamatic client module and ensure the exported client used by orchestrate’s
executeFlow call remains properly type-checked.
In `@kits/subsniffer/apps/next.config.mjs`:
- Around line 3-4: Remove the typescript.ignoreBuildErrors and
eslint.ignoreDuringBuilds overrides from the Next.js configuration so production
builds fail on type-checking or lint errors; update the surrounding
configuration without adding replacement suppression flags.
In `@kits/subsniffer/apps/package.json`:
- Line 13: Replace the floating "latest" version for the lamatic dependency in
package.json with the specific latest stable version available at merge time,
using an exact version or approved caret range to ensure reproducible dependency
resolution.
In `@kits/subsniffer/README.md`:
- Line 3: Add a blank line immediately after every Markdown section heading in
the README, including the headings at the referenced locations, while preserving
the existing content and structure.
- Around line 23-30: Specify the text language on all fenced code blocks in the
README, including the diagram, sample input, and repository tree fences. Update
the fences around the content near the diagram and the sections identified by
the diff so they use text.
- Around line 55-68: Add a prominent warning immediately before the paste
example in the SubSniffer README instructing users to redact account numbers,
names, addresses, transaction IDs, and other unnecessary PII, and note that the
submitted text is forwarded to Lamatic as-is.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3ed95a96-6bee-4934-8a6f-f791def4a017
⛔ Files ignored due to path filters (1)
kits/subsniffer/apps/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (30)
kits/subsniffer/.env.examplekits/subsniffer/.gitignorekits/subsniffer/README.mdkits/subsniffer/agent.mdkits/subsniffer/apps/.env.examplekits/subsniffer/apps/.gitignorekits/subsniffer/apps/actions/orchestrate.tskits/subsniffer/apps/app/globals.csskits/subsniffer/apps/app/layout.tsxkits/subsniffer/apps/app/page.tsxkits/subsniffer/apps/components/header.tsxkits/subsniffer/apps/components/ui/badge.tsxkits/subsniffer/apps/components/ui/button.tsxkits/subsniffer/apps/components/ui/card.tsxkits/subsniffer/apps/components/ui/textarea.tsxkits/subsniffer/apps/lib/lamatic-client.tskits/subsniffer/apps/lib/utils.tskits/subsniffer/apps/next.config.mjskits/subsniffer/apps/package.jsonkits/subsniffer/apps/postcss.config.mjskits/subsniffer/apps/tsconfig.jsonkits/subsniffer/constitutions/default.mdkits/subsniffer/flows/subsniffer.tskits/subsniffer/lamatic.config.tskits/subsniffer/model-configs/subsniffer_detect-subscriptions.tskits/subsniffer/model-configs/subsniffer_write-report.tskits/subsniffer/prompts/subsniffer_detect-subscriptions_system.mdkits/subsniffer/prompts/subsniffer_detect-subscriptions_user.mdkits/subsniffer/prompts/subsniffer_write-report_system.mdkits/subsniffer/prompts/subsniffer_write-report_user.md
SatyamSingh8306
left a comment
There was a problem hiding this comment.
- suggest changes are not mandatory
There was a problem hiding this comment.
♻️ Duplicate comments (4)
kits/subsniffer/apps/lib/lamatic-client.ts (1)
5-5: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAgent, your client's cover identity is compromised —
anytyping blows the operation.The cached
clientvariable is still typed asany, which meansclient.executeFlow()inorchestrate.tsoperates without type safety. The Lamatic SDK exports aLamaticclass — useLamatic | nullfor the variable declaration so downstream calls remain type-checked.This was flagged in a previous review and remains unresolved.
🔧 Proposed fix
-let client: any; +let client: Lamatic | null;Also applies to: 26-26
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kits/subsniffer/apps/lib/lamatic-client.ts` at line 5, Replace the any type on the cached client variable with Lamatic | null, importing Lamatic from the Lamatic SDK as needed, so client.executeFlow() remains type-checked downstream.Source: Learnings
kits/subsniffer/apps/actions/orchestrate.ts (2)
16-16: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winMissing
../../lamatic.configimport — coding guideline violation persists.The coding guideline for
kits/*/apps/actions/orchestrate.tsrequires importing and using../../lamatic.configto read step definitions from the parent kit. This file still readsSUBSNIFFER_FLOW_IDdirectly fromprocess.envwithout referencing the kit configuration. The kit'slamatic.config.tscontains the step definitions that should drive flow execution here.As per coding guidelines,
kits/*/apps/actions/orchestrate.ts: Kit Next.js apps must import and use../../lamatic.configto read step definitions from the parent kit.This was flagged in a previous review and remains unresolved.
🔧 Proposed fix
"use server"; import { getLamaticClient } from "`@/lib/lamatic-client`"; +import lamaticConfig from "../../lamatic.config"; export async function auditSubscriptions( statement: string, goals?: string, ): Promise<{ success: boolean; data?: any; error?: string }> { try { - const flowId = process.env.SUBSNIFFER_FLOW_ID; + const flowStep = lamaticConfig.steps?.find((s) => s.id === "subsniffer"); + const flowId = flowStep?.flowId ?? process.env.SUBSNIFFER_FLOW_ID; if (!flowId) { throw new Error( - "SUBSNIFFER_FLOW_ID is not set. Deploy the SubSniffer flow in Lamatic and add its id to apps/.env.local.", + "SubSniffer flow ID not found. Ensure lamatic.config.ts defines the step or set SUBSNIFFER_FLOW_ID in apps/.env.local.", ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kits/subsniffer/apps/actions/orchestrate.ts` at line 16, Update the orchestration entrypoint to import the parent kit configuration from ../../lamatic.config and use its step definitions to determine the flow instead of reading SUBSNIFFER_FLOW_ID directly from process.env; ensure the execution logic references the imported configuration consistently.Source: Coding guidelines
30-31: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winOverly permissive response normalization — the raw
resfallback is a liability.The chain
res?.result?.answer ?? res?.result ?? res?.output ?? reswill return the entire raw response object asdatawhen none of the expected fields exist. The downstream consumer inpage.tsx(lines 83-112) expects{ analysis, report }— passing the raw response would setanalysisto the entire response object, producing a broken UI.This was flagged in a previous review and remains unresolved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kits/subsniffer/apps/actions/orchestrate.ts` around lines 30 - 31, The response normalization in orchestrate.ts is too permissive because it falls back to the entire raw response. Update the normalization logic around data and the downstream consumer in page.tsx to accept only the expected result fields, such as result.answer, result, or output when they contain the required { analysis, report } shape; otherwise return or handle an invalid/empty result explicitly instead of passing res through.kits/subsniffer/apps/components/ui/button.tsx (1)
10-27: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAgent, this component can't accept a ref — your cover is blown.
Buttonstill doesn't forward refs, limiting consumers who need imperative access (focus management, scroll-into-view). Based on learnings, preferReact.forwardRefwhen exposing components to maintain backward compatibility expectations with React 18-style ref forwarding.This was flagged in a previous review and remains unresolved.
Based on learnings: "For UI components, prefer
React.forwardRefwhen exposing components to maintain backward compatibility expectations with React 18-style ref forwarding."♻️ Proposed fix
-export function Button({ - className, - variant = "default", - ...props -}: React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: Variant }) { - return ( - <button - className={cn( - "inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold shadow-sm transition focus:outline-none focus:ring-2 focus:ring-indigo-400 disabled:cursor-not-allowed disabled:opacity-60", - variant === "outline" - ? "border border-slate-300 bg-white text-slate-700 hover:bg-slate-50" - : "bg-indigo-600 text-white hover:bg-indigo-500", - className, - )} - {...props} - /> - ); -} +export const Button = React.forwardRef< + HTMLButtonElement, + React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: Variant } +>(function Button({ className, variant = "default", ...props }, ref) { + return ( + <button + ref={ref} + className={cn( + "inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold shadow-sm transition focus:outline-none focus:ring-2 focus:ring-indigo-400 disabled:cursor-not-allowed disabled:opacity-60", + variant === "outline" + ? "border border-slate-300 bg-white text-slate-700 hover:bg-slate-50" + : "bg-indigo-600 text-white hover:bg-indigo-500", + className, + )} + {...props} + /> + ); +});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kits/subsniffer/apps/components/ui/button.tsx` around lines 10 - 27, Button does not forward refs to its underlying button element. Update the Button component to use React.forwardRef, accept a React.Ref<HTMLButtonElement> alongside its existing props, and pass that ref to the rendered button while preserving the current variant, className, and prop behavior.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@kits/subsniffer/apps/actions/orchestrate.ts`:
- Line 16: Update the orchestration entrypoint to import the parent kit
configuration from ../../lamatic.config and use its step definitions to
determine the flow instead of reading SUBSNIFFER_FLOW_ID directly from
process.env; ensure the execution logic references the imported configuration
consistently.
- Around line 30-31: The response normalization in orchestrate.ts is too
permissive because it falls back to the entire raw response. Update the
normalization logic around data and the downstream consumer in page.tsx to
accept only the expected result fields, such as result.answer, result, or output
when they contain the required { analysis, report } shape; otherwise return or
handle an invalid/empty result explicitly instead of passing res through.
In `@kits/subsniffer/apps/components/ui/button.tsx`:
- Around line 10-27: Button does not forward refs to its underlying button
element. Update the Button component to use React.forwardRef, accept a
React.Ref<HTMLButtonElement> alongside its existing props, and pass that ref to
the rendered button while preserving the current variant, className, and prop
behavior.
In `@kits/subsniffer/apps/lib/lamatic-client.ts`:
- Line 5: Replace the any type on the cached client variable with Lamatic |
null, importing Lamatic from the Lamatic SDK as needed, so client.executeFlow()
remains type-checked downstream.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: ebf67853-32e9-4028-be80-bf71c6f001d6
📒 Files selected for processing (10)
kits/subsniffer/apps/actions/orchestrate.tskits/subsniffer/apps/app/layout.tsxkits/subsniffer/apps/app/page.tsxkits/subsniffer/apps/components/header.tsxkits/subsniffer/apps/components/ui/badge.tsxkits/subsniffer/apps/components/ui/button.tsxkits/subsniffer/apps/components/ui/card.tsxkits/subsniffer/apps/components/ui/textarea.tsxkits/subsniffer/apps/lib/lamatic-client.tskits/subsniffer/apps/lib/utils.ts
|
Hi @SatyamSingh8306! 👋 Before this PR can be reviewed by maintainers, please resolve all comments and requested changes from the CodeRabbit automated review. Steps to follow:
This helps keep the review process efficient for everyone. Thank you! 🙏 |
SatyamSingh8306
left a comment
There was a problem hiding this comment.
- required changes are not mandatory (can be improved in iterative way)
- model-configs: leave model/credential empty so importing user picks any provider in their own Lamatic project (no hardcoded model, no leaked id) - flows/subsniffer.ts: add meta.author for Studio registration
Studio's runtime validator reported 'config_json.nodes must be a non-empty array' even though the .ts is syntactically valid and contains 4 nodes. The large top-of-file block comment was the suspect (escaped quotes and long multiline ASCII content inside a TS comment block). Replaced with a short single-line comment. The full documentation lives in README.md and agent.md.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
kits/subsniffer/flows/subsniffer.ts (1)
132-133: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winSchema
requiredarrays omit fields the system prompt explicitly requests.The detect-subscriptions system prompt instructs the model to output
reason,cancellation_url(per subscription),top_recommendations, andrisk_flags. However, the InstructorLLMNode schema only requires["merchant", "amount", "cadence", "category", "usage", "monthly_cost"]at the item level and["summary", "subscriptions", "totals"]at the top level. Since InstructorLLMNode enforces schema validation, the model may omit non-required fields, producing incomplete audit results with missing reasons, cancellation URLs, recommendations, and risk flags.🔧 Proposed fix: add missing fields to schema `required` arrays
"required": ["merchant", "amount", "cadence", "category", "usage", "monthly_cost"] + "required": ["merchant", "amount", "cadence", "category", "usage", "reason", "monthly_cost", "cancellation_url"]And at the top level:
"required": ["summary", "subscriptions", "totals"] + "required": ["summary", "subscriptions", "totals", "top_recommendations", "risk_flags"]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kits/subsniffer/flows/subsniffer.ts` around lines 132 - 133, Update the JSON schema used by the detect-subscriptions InstructorLLMNode so each subscription item’s required array also includes reason and cancellation_url, and the top-level required array includes top_recommendations and risk_flags. Keep these entries aligned with the fields requested by the detect-subscriptions system prompt.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@kits/subsniffer/flows/subsniffer.ts`:
- Around line 132-133: Update the JSON schema used by the detect-subscriptions
InstructorLLMNode so each subscription item’s required array also includes
reason and cancellation_url, and the top-level required array includes
top_recommendations and risk_flags. Keep these entries aligned with the fields
requested by the detect-subscriptions system prompt.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: fda51e77-4df5-4205-9d92-6a3614faa5ef
📒 Files selected for processing (1)
kits/subsniffer/flows/subsniffer.ts
- Rewrite flows/subsniffer.ts using the JSON-style exported format (double-quoted keys, meta.tags as array, matching the Studio export). The previous JS-literal key style prevented the Studio validator's config_json parser from extracting nodes, causing 'config_json.nodes must be a non-empty array'. - Drop deprecated baseUrl from apps/tsconfig.json (TypeScript 7.0 warning). paths now resolves relative to tsconfig location. - Gitignore apps/tsconfig.tsbuildinfo (TypeScript incremental cache). - Update meta.author email to match the registered Lamatic account.
SatyamSingh8306
left a comment
There was a problem hiding this comment.
Non mandatory changes (Subsniffer can be improved interatively)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@kits/subsniffer/flows/subsniffer.ts`:
- Around line 141-166: The edges array is missing the execution link from
triggerNode_1 to InstructorLLMNode_954. Add an edge connecting triggerNode_1 to
InstructorLLMNode_954 with the appropriate source/target handles and edge type,
so the flow executes through InstructorLLMNode_954 before LLMNode_456 and
responseNode_triggerNode_1.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1524fe8e-7aeb-42b1-ba77-80645abce697
📒 Files selected for processing (4)
kits/subsniffer/apps/.gitignorekits/subsniffer/apps/tsconfig.jsonkits/subsniffer/flows/subsniffer.tskits/subsniffer/lamatic.config.ts
💤 Files with no reviewable changes (1)
- kits/subsniffer/apps/tsconfig.json
CodeRabbit flagged: the API Request trigger was only wired to the response node, leaving the detection stage un-reachable. Adds the triggerNode_1 -> InstructorLLMNode_954 defaultEdge so the full trigger -> detect -> write -> response chain executes.
SatyamSingh8306
left a comment
There was a problem hiding this comment.
fixed in upcoming version
PR Checklist
1. Select Contribution Type
kits/subsniffer/)2. General Requirements
subsniffer)3. File Structure
lamatic.config.tspresent with valid metadata (name, description, tags, steps, author, env keys)flows/subsniffer.ts(single .ts file, Lamatic Studio export format)constitutions/default.md,agent.md,README.mdpresent.env.examplewith placeholder values only (kit root + apps/)4. Validation
npm install && npm run devworks locally (production build verified on Next 15.5.20)feat:(required by the validation workflow)lamatic.config.tspresent with valid metadata (name, description, tags, steps, author, env keys)flows/subsniffer.ts(single .ts file, Lamatic Studio export format)constitutions/default.md,agent.md,README.mdpresent.env.examplewith placeholder values only (kit root + apps/)4. Validation
npm install && npm run devworks locally (production build verified on Next 15.5.20)feat:(required by the validation workflow)kits/subsniffer/kit for “SubSniffer — Subscription Audit”:kits/subsniffer/.env.examplekits/subsniffer/.gitignorekits/subsniffer/README.mdkits/subsniffer/agent.mdkits/subsniffer/constitutions/default.mdkits/subsniffer/lamatic.config.tskits/subsniffer/flows/subsniffer.ts(flow definition/export)kits/subsniffer/model-configs/subsniffer_detect-subscriptions.tssubsniffer_write-report.tskits/subsniffer/prompts/subsniffer_detect-subscriptions_system.mdsubsniffer_detect-subscriptions_user.mdsubsniffer_write-report_system.mdsubsniffer_write-report_user.mdkits/subsniffer/apps/:kits/subsniffer/apps/.env.examplekits/subsniffer/apps/.gitignorekits/subsniffer/apps/actions/orchestrate.ts(server actionauditSubscriptions(); executes the Lamatic flow bySUBSNIFFER_FLOW_IDand normalizes the response)kits/subsniffer/apps/app/globals.csskits/subsniffer/apps/app/layout.tsxkits/subsniffer/apps/app/page.tsx(client UI: input statement/goals → callsauditSubscriptions()→ rendersanalysis+report)kits/subsniffer/apps/components/header.tsxkits/subsniffer/apps/components/ui/badge.tsxbutton.tsxcard.tsxtextarea.tsxkits/subsniffer/apps/lib/lamatic-client.ts(cached SDK client; validatesLAMATIC_*env vars at call time)kits/subsniffer/apps/lib/utils.ts(cnTailwind class merge helper)kits/subsniffer/apps/next.config.mjskits/subsniffer/apps/package.jsonkits/subsniffer/apps/postcss.config.mjskits/subsniffer/apps/tsconfig.jsonkits/subsniffer/flows/subsniffer.ts; noflow.jsonpresent—node types defined directly in the TS flow export):triggerNode(graphqlNode): GraphQL “API Request” trigger acceptingstatement(string) andgoals(string).dynamicNode(InstructorLLMNode_954): schema-constrained JSON “Detect Subscriptions” output containingsummary,subscriptions[],totals, plustop_recommendations[]andrisk_flags[](with required fields includingmerchant,amount,cadence,category,usage,monthly_costper subscription).dynamicNode(LLMNode_456): “Write Report” LLM converts the structured analysis into user-facinggeneratedResponse(mapped toreport).responseNode(graphqlResponseNode): “API Response” returning JSON with:analysis={{InstructorLLMNode_954.output}}report={{LLMNode_456.output.generatedResponse}}InstructorLLMNode→LLMNode→ response, plus aresponseEdgefrom trigger to response.