| name | analyze-pp-solution |
| description | Use when analyzing an exported Power Platform solution folder for maintainability issues mapped to the 10 SIG guidelines based on the "Building Maintainable Software" book, translated to Power Platform. |
| allowed-tools | shell |
1. Purpose
This agent analyzes an exported Power Platform solution .zip artifact that is passed in as inputs with the prompt, unpacks it and checks it against the 10 SIG maintainability guidelines translated to Power Platform. It then produces a structured per-guideline findings report with ratings and actionable recommendations.
The agent MUST:
- Use the provided solution .zip file as the single source of truth for the analysis.
- Use
Expand-Archive in PowerShell to extract the solution zip file to /extracted-[solution-name]/
- Analyze extracted files Power Platform solution by reading the extract XML, JSON and YAML files.
- If there are multiple apps in the extracted folder (recognizable as additional zip files) repeat the extraction step using the same naming convention for the output folder (e.g.
/[app-name]/) and analyze each app separately.
- If there are Power Automate Desktop flows in the solution, parse the
customizations.xml file to retrieve their definitions. The JSON files for workflows only include cloud flow definitions and the desktop flow definitions are stored directly in the customizations.xml file as Robin code in a single XML node per desktop flow. The desktop flow binary files contain some information on dependencies and objects used by those desktop flows, such as screenshots of UI elements, connectors used, etc. but they do not contain the actual logic of the desktop flows, which is only available in the customizations.xml file.
- Produce a structured but adaptable report as output
If the user has not provided a solution name, the agent MUST explicitly state this as a blocker and request the solution name before proceeding.
Expected Folder Structure in the extracted solution
<SolutionRoot>/
solution.xml — component manifest
customizations.xml — solution metadata, components and full desktop flow definitions
CanvasApps/
<AppName>/src/
App.fx.yaml — named formulas, app-level settings
<ScreenName>.fx.yaml — per-screen controls and formulas
Workflows/
<FlowName>-<GUID>.json — cloud flow definitions
Entities/
<TableName>/
Entity.xml
BusinessRules/ — Dataverse business rule XML
connectionreferences/ — connector references
environmentvariabledefinitions/ — env var definitions
environmentvariablevalues/
desktopflowbinaries/ — Power Automate Desktop flow binary definitions
2. Analysis - the 10 Guidelines
G1 — Write Short Units of Code
Principle: Limit the length of code units to 15 lines of code.
Rationale: Each unit of code (formula, flow, subflow, function, etc.) should be short and focused on a single concern to enhance readability and maintainability.
Files:
CanvasApps/*/src/*.fx.yaml for canvas app formulas
Workflows/*.json for cloud flow definitions
customizations.xml for desktop flow definitions (Robin code)
What to check:
- For Canvas Apps:
- Read every control property value (OnSelect, OnChange, OnVisible, Items, etc.)
- Flag formulas exceeding 15 lines or 800 characters
- Count semicolons in OnSelect — flag if > 5 (multiple concerns)
- Grep
App.fx.yaml for App.Formulas: block — presence = positive signal
- Grep for
Function( or UDF definitions — presence = positive signal
- For Cloud Flows:
- Parse JSON, extract each action's definition
- Flag flows with more than 15 steps and no scopes (lack of modularity)
- Check for scopes being used to group actions — positive signal
- Check for multiple
Initialize variable actions - flag if > 3
- If scopes are present, check if any scope contains more than 15 actions or the entire flow contains more than 30 actions — flag if true
- If Office Scripts are used, flag as minor potential issue to be checked manually (scripts can contain long code but are outside of the solution artifact)
- For Desktop Flows:
- Parse
customizations.xml, extract Robin code for each desktop flow
- Flag desktop flows with only the Main subflow containing more than 15 actions and no subflows
- If subflows are present:
- that's a positive signal of modularity
- check if there is a subflow named
Template, indicating a framework approach, which is a positive signal
- flag if any subflow contains more than 15 actions. If a
Template subflow is present, deduct the same actions that are in the Template from the total count in each subflow, as those are reusable actions and not a sign of a long unit of code. Only flag if subflows still exceed 15 actions after discounting Template actions.
- Parse each individual scripting action (e.g. PowerShell, Python) and flag if any script has functions/modules that exceed 15 lines of code.
Red flags: OnSelect with 5+ semicolon-separated statements, no named formulas in a large app, single property formula > 30 lines, flow with > 15 steps and no scopes, desktop flow with Main subflow > 15 actions and no subflows, any subflow > 15 actions, any script action with functions/modules > 15 lines.
G2 — Write Simple Units of Code
Principle: Limit the number of branch points per unit to 4.
Rationale: Nested logic (e.g. If statements inside If statements) increases cognitive load and reduces maintainability.
Files:
CanvasApps/*/src/*.fx.yaml for canvas app formulas
Workflows/*.json for cloud flow definitions
customizations.xml for desktop flow definitions (Robin code)
What to check:
- For Canvas Apps:
- grep for
If( and count nesting (If inside If) — flag depth > 2
- grep for
Switch( — presence = positive signal
- For Cloud Flows:
- parse
actions JSON structure, look for "type": "If" inside another "type": "If" — flag depth > 2
- look for
"type": "Switch" — positive signal, but flag if more than 4 cases in a single Switch (too many branches)
- look for type
"type": "Until" or "type": "Foreach" inside another of any of these two types - flag depth > 1 (nested loops)
- For Desktop Flows:
- parse
customizations.xml, look for nested If statements — flag depth > 2
- look for Switch actions — positive signal, but flag if more than 4 cases in a single Switch (too many branches)
- look for nested loops — flag depth > 1
Red flags: nested If depth > 2 in canvas or flows, zero Switch actions in flows with multi-path logic, no Scope actions for error handling, nested loops depth > 1.
G3 — Write Code Once
Principle: Do not copy code.
Rationale: Copying code increases maintenance effort and the risk of introducing inconsistencies. Reuse existing code units instead of duplicating them.
Files:
solution.xml - general solution manifest
Workflows/*.json - cloud flow definitions
CanvasApps/*/src/*.fx.yaml - canvas app formulas
customizations.xml - desktop flow definitions (Robin code)
environmentvariabledefinitions/ - environment variable definitions
What to check:
- General:
solution.xml: grep for ComponentType="ComponentLibrary" — positive signal
environmentvariabledefinitions/: count files — positive signal if > 0, but don't flag if none, as there may be other kinds of external configurations
- For Canvas Apps:
- grep for
Function( or UDF definitions — positive signal
- grep for
Launch( — positive signal (multi-app architecture)
- grep for
Param( — positive signal (receives context from another app)
- in .fx.yaml: grep for
https:// or http:// hardcoded strings — flag these
- in .fx.yaml: look for identical formula fragments repeated > 3 times across screens
- For Cloud Flows:
- grep for child flow calls — look for
"type": "Workflow" — positive signal
- grep for
"type": "Response" - positive signal (flow is reusable by other flows)
- look for multiple flows with identical action blocks (e.g. same HTTP call, same Dataverse action), except for child flow calls — flag if > 3
- look for hardcoded URLs, GUIDs, email addresses and similar values in flow actions — flag these
- look for conditional branches that use the same logic in multiple places and/or have actions using the same expressions with different inputs — flag if > 3
- For Desktop Flows:
- parse
customizations.xml, look for identical action blocks across multiple desktop flows (ignore Template blocks, if a subflow named Template exists) — flag if > 3
- look for hardcoded URLs, GUIDs, email addresses and similar values in desktop flow actions — flag these
- look for conditional branches that use the same logic in multiple places and/or have actions using the same expressions with different inputs — flag if > 3
- grep for
External.RunFlow - this indicates child desktop flow calls - positive signal
- grep for
CALL - this indicates subflow calls - positive signal
- grep for
Template - this indicates a subflow named Template, which is a positive signal of a framework approach
Red flags: no component library when Canvas Apps exist, large flows with no child flows, hardcoded values in flows or canvas, repeated formula blocks across screens, repeated action blocks in flows.
G4 — Keep Unit Interfaces Small
Principle: Limit the number of parameters per unit to 4.
Rationale: Large numbers of parameters increase complexity and reduce maintainability. Use data structures or environment variables to pass multiple values.
Files:
CanvasApps/*/src/*.fx.yaml for canvas apps component properties
Workflows/*.json for cloud flow definitions
customizations.xml for desktop flow definitions (Robin code)
What to check:
- For Canvas apps:
- grep for
CustomProperties: and count entries per component — flag > 4
- For Cloud Flows:
- find flows with
"type": "Request" (manual trigger), count properties.schema.properties input fields — flag > 4
- find
"Respond_to_a_PowerApp_or_flow" action, count output fields — flag > 4
- For Desktop Flows:
- parse
customizations.xml, find desktop flows where the <Inputs> node exists, count input parameters — flag > 4
- parse
customizations.xml, find desktop flows where the <Outputs> node exists, count output parameters — flag > 4
Red flags: component with > 4 custom properties, flows with > 4 input fields, flows with > 4 output fields.
G5 — Separate Concerns in Modules
Principle: Avoid large modules in order to achieve loose coupling between them by assigning responsibilities to separate modules.
Rationale: Large modules with multiple responsibilities are harder to maintain and understand. Separate concerns into smaller, focused modules to improve maintainability and reduce coupling.
Files:
solution.xml - general manifest for components
CanvasApps/*/src/*.fx.yaml - canvas app definitions
Entities/*/BusinessRules/ - business rules
Workflows/*.json - cloud flow definitions
customizations.xml - desktop flow definitions (Robin code)
What to check:
- General:
solution.xml: count ComponentType="CanvasApp" vs ComponentType="AppModule" (model-driven)
- For Canvas Apps:
- count
.fx.yaml files in src/ (= screen count) — flag if > 15
- grep for
Launch( — positive signal (multi-app architecture)
- grep for
Param( — positive signal (receives context from another app)
- .fx.yaml: grep for
Patch( combined with If(IsBlank( in same OnSelect — flag (validation in UI layer)
- Business rules:
Entities/*/BusinessRules/: count XML files — positive signal
- For Cloud Flows:
- parse each flow JSON, count total actions — flag if one flow has 5× more actions than average
- parse each flow JSON, count total scopes — positive signal if > 0
- parse each flow JSON, check for child flow calls — positive signal (modular approach)
- parse each flow JSON, check the number of different services that are called from the flow (e.g. Dataverse, SharePoint, Teams, Outlook, etc.) — flag if > 3 (too many responsibilities in one flow)
- For Desktop Flows:
- parse
customizations.xml, count total actions in each desktop flow — flag if one flow has 5× more actions than average
- parse
customizations.xml, count total subflows in each desktop flow — positive signal if > 0
- parse
customizations.xml, check for subflow named Template — positive signal (framework approach)
- parse
customizations.xml, check for External.RunFlow calls — positive signal (child desktop flow calls)
- parse
customizations.xml, check the number of different applications that are called from the desktop flow (e.g. Excel, UI automation, Browser automation, etc.) — flag if > 3 (too many responsibilities in one flow)
Red flags: single canvas app > 15 screens, no model-driven apps for data-heavy solution, no Launch/Param usage, no Dataverse business rules when Power Apps are involved, validation logic embedded in canvas Patch calls, too many applications called from a single flow, no subflows in desktop flows, no child flows used in large flows.
G6 — Couple Architecture Components Loosely
Principle: Avoid tight coupling between architecture components by minimizing the relative amount of code within modules that is exposed to modules in other components.
Rationale: Tight coupling between components increases maintenance effort and reduces flexibility. Minimize the amount of code that is exposed to other components to achieve loose coupling.
Files:
Workflows/*.json - for cloud flows
CanvasApps/*/src/*.fx.yaml - for Canvas Apps
customizations.xml - for desktop flows (Robin code)
solution.xml - general solution manifest
What to check:
- For Cloud Flows:
- categorise trigger types from JSON:
"OpenApiConnectionWebhook" with Dataverse = event-driven (positive)
"Request" HTTP trigger = potential tight coupling
"Recurrence" = scheduled (neutral)
"ApiConnectionNotification" = connector-based event (positive)
- check if office scripts are used with input/output parameters - potential for tight coupling with code outside of the solution artifact, flag for manual review if true
- For Desktop Flows:
- parse
customizations.xml, find desktop flows where the <Inputs> node exists, count input parameters — flag > 4
- parse
customizations.xml, find desktop flows where the <Outputs> node exists, count output parameters — flag > 4
- For Canvas Apps:
- .fx.yaml: grep for
http inside Office365Outlook, direct HTTP connector calls, or hardcoded flow trigger URLs
- General:
solution.xml: grep for ComponentType="CustomConnector" — positive signal
- Check for missing dependencies in
solution.xml (e.g. a flow calls a child flow or uses a custom table that is not in the solution) — flag if true
Red flags: all flows are HTTP-triggered (no event-driven), canvas app contains hardcoded flow URL calls, no Dataverse-triggered flows in a Dataverse solution, desktop flows with > 4 input or output parameters, missing dependencies in the solution manifest, Office Scripts with input/output parameters.
G7 — Keep Architecture Components Balanced
Principle: Balance the number and relative size of top-level components in your code.
Rationale: Disproportionately large components can indicate poor separation of concerns and can be harder to maintain.
Files:
solution.xml - general solution manifest
CanvasApps/*/src/ - for Canvas Apps
Workflows/*.json - for cloud flows
customizations.xml - for desktop flows (Robin code)
What to check:
- For Canvas Apps:
- count
.fx.yaml screen files per app, compute min/max/average — flag if max > 3× average
- For Cloud Flows:
- parse each flow JSON, count total actions — flag if one flow has 5× more actions than average
- For Desktop Flows:
- parse
customizations.xml, count total actions in each desktop flow, compute min/max/average — flag if max > 5× average
- General:
solution.xml: list component types and counts — flag extreme skew
solution.xml: check if custom connectors are included next to flows, apps and other components — flag if true
Red flags: one app with 3× more screens than others, one flow with 5× more actions than others, custom connectors not in dedicated solutions.
G8 — Keep Your Codebase Small
Principle: Keep your codebase as small as feasible.
Rationale: Large codebases are harder to maintain. Strive to keep your codebase as small as possible by implementing only necessary features and avoiding over-engineering.
Files:
solution.xml - general solution manifest
CanvasApps/*/src/ - for Canvas Apps
Entities/*/BusinessRules/ - for business rules
connectionreferences/ - for connection references
Workflows/*.json - for cloud flows
customizations.xml - for desktop flows (Robin code)
What to check:
- General:
solution.xml: count AppModule (model-driven app) components — absence in data-heavy solution = flag
Entities/*/BusinessRules/: count XML files — positive signal
connectionreferences/: identify custom vs. prebuilt connectors
- For Canvas Apps:
- Canvas .fx.yaml: count
Patch( occurrences — high count in absence of model-driven apps = flag
- Canvas .fx.yaml: total line count across all screen files — proxy for custom code volume
- For Cloud Flows:
- parse each flow JSON, count total actions — proxy for code volume
- count the total number of cloud flows - flag if > 50
- check if office scripts are used - potential for code outside of the solution artifact, flag for manual review if true
- check if any generic child flows (e.g. for Logging, Notifications, etc.) are included in the solution - flag if true (should be in a separate dedicated solution)
- For Desktop Flows:
- parse
customizations.xml, count total actions in each desktop flow — proxy for code volume
- check if any generic child flows (e.g. for Logging, Notifications, etc.) are included in the solution - flag if true (should be in a separate dedicated solution)
- count the total number of desktop flows - flag if > 10
Red flags: zero model-driven apps for a data management solution, many Patch() screens that could be model-driven views/forms, custom connectors where prebuilt equivalents exist, one massive solution with over 50 cloud flows or over 10 desktop flows, reusable child flows included in the same solution instead of a separate one, potential code outside of the solution artifact (Office Scripts).
G9 — Automate Tests
Principle: Automate testing to ensure code quality and facilitate maintenance.
Rationale: Automated tests help catch issues early and reduce the risk of introducing bugs during maintenance.
Files:
- solution root (recursive search),
Workflows/*.json for cloud flow definitions
What to check:
- General:
- Search solution root for
*.testplan.yaml, testplan.yaml, or any YAML with testSuite: key — positive signal
- Search for
azure-pipelines.yml or .github/workflows/ — positive signal
- Look for solution checker result files (
*.sarif, *-checker-results.*) — positive signal
- For Cloud Flows:
- count flows with at least one
"type": "Scope" action (error handling) - positive signal
- look for monitoring/alerting flows (triggered on
Failed run of another flow) - positive signal
- For Desktop Flows:
- parse
customizations.xml, count Test case flows for Desktop flows - positive signal
- look for error handling patterns in desktop flows, such as subflows or actions dedicated to logging or handling errors - positive signal
Red flags: no test plan YAML files when Canvas Apps exist, no CI/CD pipeline config, flows with zero Scope/error-handling actions, no solution checker results, desktop flows with no error handling patterns.
G10 — Write Clean Code
Files:
CanvasApps/*/src/*.fx.yaml for Canvas Apps
Workflows/*.json for Cloud Flows
customizations.xml for Desktop Flows (Robin code)
solution.xml - general solution manifest
connectionreferences/ - for connection references
environmentvariabledefinitions/ - for environment variable definitions
desktopflowbinaries/ - for desktop flow binary definitions
What to check:
- For Canvas Apps:
- grep all control names for default patterns:
Button\d+, TextInput\d+, Label\d+, Gallery\d+, Icon\d+, Image\d+
- Calculate %: (default-named controls / total controls) — flag if > 20%
- check for prefix convention:
btn, txt, lbl, gal, ico, img, drp, sldr — positive signal
- For Cloud Flows:
- in JSON, check each action's
metadata.operationMetadataId vs. its display name property — flag unchanged/default names like "Send_an_email_\(V2\)"
- flag action names with indexes like
Send_an_email_\(V2\)_2 or Send_an_email_\(V2\)_3 — these are default names, indicating multiple actions of the same type, extremely reducing readability
- check
"status": "Stopped" at flow level — each = dead code flag
- check for actions with non-empty
description or note fields — flag as moderate issue if any, as clean code does not need comments
- check variable name length - flag if any are > 25 characters
- check if variable names include data type prefixes like
str, int, bool — flag as minor issue, as this is a code smell
- check flow naming convention:
[Trigger/Schedule/Parent/Child] - SolutionName - FlowName — flag if not followed
- check for error handling patterns in flows, such as Scopes with
Configure run after settings for error handling — positive signal, flag if none exist
- check for constant values used in calculations/expressions - flag if any, as these should be named variables
- check for any actions after a
Terminate action that is not in a conditional branch — flag as dead code
- For Desktop Flows:
- parse
customizations.xml, check variable name lengths - flag if any are > 25 characters
- check if variable names include data type prefixes like
str, int, bool — flag as minor issue, as this is a code smell
- check flow naming convention:
SolutionName - FlowName — flag if not followed
- check subflow names for any ambiguous names like
Subflow1, Subflow2, etc. — flag if any found
- check subflow name length - flag if any are > 25 characters
- check desktop flow binaries to find UI elements and images with ambiguous elements, such as
Button, TextBox, Image, etc. — flag if any found
- check for any code that can never be accessed - such as code after a
Stop flow or after Exit subflow action with no labels and no means leading to it - flag as dead code
- check for any disabled (commented out) actions in the desktop flow definition - flag as dead code
- check for any conditional branches that are never taken (e.g. If condition is always true or always false) - flag as dead code
- check for any image recognition and mouse/keyboard actions - flag as moderate issue, as these are brittle and can break easily
- check for any comments in Robin code — flag as moderate issue if any, as clean code does not need comments
- check for error handling patterns in desktop flows, such as subflows or actions dedicated to logging or handling errors — positive signal, flag if none exist
- check for
Template subflow - this indicates a framework approach, which is a positive signal
- check for named
Region and On block error actions - these are positive signals
- check for constant values used in calculations/expressions - flag if any, as these should be named variables
- General:
- parse
solution.xml, check connection reference names for default patterns like shared_[connectorname]_[randomAlphanumericString] — flag if any of connection references have default names
- check if connection references follow the recommended naming convention:
[SolutionName]_[ConnectorName] — flag if not followed. Consider that SolutionName may include spaces, hyphens and other special characters not allowed in logical names. Assume it is correct if the connection reference name contains the solution name with any special characters replaced with underscores.
- check environment variable names to follow the recommended naming convention:
[SolutionName]_[VariableName] — flag if not followed. Consider the same rule for SolutionName as stated above for connection references.
Red flags: > 20% controls with default names, flow actions using default connector names, disabled (Stopped) flows in the solution, long variable, subflow or control names, ambiguous subflow or UI element names, dead code in flows or desktop flows, hardcoded values in flows or desktop flows, no error handling patterns in flows or desktop flows, no Template subflow in desktop flows, flows, connection references and environment variables not following naming conventions.
3. Output Format
A single file named <solution_name_slug>-v<solution-version>-code-analysis.md saved in the same folder as the input solution zip.
Slug rules: lowercase, spaces and non-alphanumeric characters replaced with hyphens, consecutive hyphens collapsed.
Example: docs_folder = docs-contoso-hr/, solution_name = "Contoso HR", version = "1.0.0.0" → output file: scontoso-hr-v1.0.0.0-code-analysis.md
# Power Platform Maintainability Report
**Solution:** [display name and version from solution.xml]
**Analyzed:** [date]
## Summary
| # | Guideline | Rating | Key Finding |
|---|-----------|--------|-------------|
| G1 | Write Short Units of Code | 🟢/🟡/🔴 | [one-liner] |
| G2 | Write Simple Units of Code | ... | ... |
| G3 | Write Code Once | ... | ... |
| G4 | Keep Unit Interfaces Small | ... | ... |
| G5 | Separate Concerns in Modules | ... | ... |
| G6 | Couple Architecture Components Loosely | ... | ... |
| G7 | Keep Architecture Components Balanced | ... | ... |
| G8 | Keep Your Codebase Small | ... | ... |
| G9 | Automate Tests | ... | ... |
| G10 | Write Clean Code | ... | ... |
---
## G1 — Write Short Units of Code
**Rating:** 🟢/🟡/🔴
**Findings:**
- [specific finding: file path, control name, line count]
**Positive signals:**
- [what the solution does right]
**Recommendation:**
- [concrete next step referencing Power Platform feature]
[... repeat for G2–G10 ...]
---
## Overall Assessment
[2–3 sentences on the solution's maintainability posture, referencing the three SIG principles:
simple guidelines, start now, be proportional.]
4. Rating Heuristics
| Rating | Meaning |
|---|
| 🟢 | Follows the guideline; at most isolated minor issues |
| 🟡 | Pattern of issues; some attention needed |
| 🔴 | Systematic violations; refactoring recommended |
Apply proportionality: a few long formulas = 🟡, majority of OnSelects are bloated = 🔴, one unnamed control in an otherwise clean app = 🟢.
5. Content Rules (STRICT)
- No undocumented assumptions
- Any assumption MUST be explicitly stated with "⚠️ Assumption: [description]."
- For any missing information, explicitly state: "⚠️ Information not available in solution metadata."
- Use formal, neutral language
6. Constraints
- Read-only analysis
- No deployment or configuration changes
- No inferred business intent beyond solution metadata
7. Stop Conditions
- Complete and return the document when every applicable section is thoroughly addressed.
- Mark any non-applicable sections as 'Not Applicable.'
- If any part is unclear or out of scope, seek clarification as needed.