# Power Automate Documentation

> Trigger whenever the user uploads or references a Power Automate solution `.zip` and asks about references, dependencies on other flows, or related questions — phrasing like "document this flow", "what does this flow read/write/touch", "map the connection references", "audit this solution", or "which flows call which".

- Skill: `kody-w/power-automate-documentation` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add kody-w/power-automate-documentation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kody-w/power-automate-documentation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: kody-w (https://skillmd.com/u/kody-w)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kody-w/power-automate-documentation

---


You are producing a **technical reference document** for a Power Automate solution export: what each flow does, in plain English, and every place it reaches outside itself to read, write, or delete data. The source of truth is always the JSON inside the solution zip, never the flow's display name or your assumptions about what a flow "probably" does.

**Inventory before you narrate.** The failure mode to avoid: skimming a flow's JSON, writing a plausible-sounding summary, and closing with a note that "a fuller audit would require deeper parsing." If you ever find yourself about to write a sentence like that, it means you skipped the inventory step below — go back and do it, don't disclose the shortcut instead of taking it. Every action in every flow gets listed and accounted for; there is no partial-credit version of this task.

## Solution Zip Anatomy

An unpacked solution zip has this shape:

- **`solution.xml`** — `SolutionManifest`: `UniqueName`, `Version`, `Managed` (0 = unmanaged, 1 = managed), `Publisher`.
- **`customizations.xml`** — a `<Workflows>` element listing every flow in the solution, and a `<connectionreferences>` element. `WorkflowId` and `Name` are always attributes on `<Workflow>`; everything else (`JsonFileName`, `Category`, `StateCode`, `StatusCode`, ...) appears as attributes in compact/hand-built exports but as child elements in full Dataverse exports (e.g. via `pac solution unpack` or a live environment export) — read whichever form is present. When `StateCode`/`StatusCode` are present, `0`/`1` means the flow is Draft (off) and `1`/`2` means it's Activated (on) — worth a one-line note per flow. `<connectionreferences>` is often empty even when flows use connectors — the connection is then resolved through the flow JSON itself (see below). Real Dataverse exports may instead ship one file per connection reference under a top-level `connectionreferences/` folder — check for that folder too, and treat it as the same information as an inline `<connectionreferences>` entry.
- **`Workflows/*.json`** — one file per flow, keyed at `properties.definition.triggers` and `properties.definition.actions`, using the standard Logic Apps workflow-definition schema. Full exports also carry `properties.connectionReferences` — see below.

If more than one solution zip is provided in the same request, treat each as an independent solution for the per-flow steps below, but pool their flow-ID maps before resolving child-flow calls, so a call in one zip can resolve to a flow shipped in another.

### Resolving connection references

A connector action's `inputs.host.connectionName` (e.g. `"shared_sharepointonline"`) is **not** itself the declared connection reference — it's a short key into the flow's own `properties.connectionReferences` block:

```json
"properties": {
  "connectionReferences": {
    "shared_sharepointonline": {
      "connection": { "connectionReferenceLogicalName": "new_sharedsharepointonline_3d8ac" },
      "api": { "name": "shared_sharepointonline" },
      "runtimeSource": "embedded"
    }
  }
}
```

Resolve in two hops: `action.inputs.host.connectionName` → `flow.properties.connectionReferences[key].connection.connectionReferenceLogicalName` → match that logical name against `customizations.xml`'s `<connectionreference connectionreferencelogicalname="...">` for the human-readable `connectionreferencedisplayname`. If `properties.connectionReferences` is absent from the flow JSON (common in hand-built/test exports), fall back to the connector name embedded directly in `host.apiId`/`host.connectionName` — there's no indirection to resolve, and that's fine, not an error.

`runtimeSource` is worth carrying into the output: `"embedded"` means the flow always runs using the same fixed connection (usually the maker's); `"invoker"` means it runs using whoever triggered the flow's own connection — a meaningful distinction for a data-access audit, since "invoker" means access actually varies by user.

## Building the Action Inventory (do this before classifying or writing anything)

For **every** flow, before you assign a single category or write a single sentence of prose, produce a flat, numbered list of every trigger and action in that flow, walking the JSON depth-first in document order:

1. List the trigger first.
2. List each top-level action in `properties.definition.actions`, in the order they appear.
3. **Whenever an action's JSON contains a nested `actions` object** — directly, or under `else.actions`, under any entry of `cases.*.actions`, or under `default.actions` — stop and list every one of those nested actions too, indented under their parent, before moving on to the next sibling at the outer level. An `If` is not one list item; it's one list item *plus* however many actions sit inside its `true` branch *plus* however many sit inside its `else`. A `Switch` is one item plus every action in every case plus every action in `default`. A `Scope`, `Foreach`, or `Until` is one item plus everything inside it.

Do not summarize a branch ("...then it branches into approval handling with a few notification steps") in place of listing it. Name every action. A flow with three top-level actions where one is an `If` containing four nested actions produces an inventory of at least seven lines, not three — if your inventory for a flow with visible branching, looping, or scopes has as few entries as it has top-level actions, you have not actually recursed into it yet.

Keep this inventory around — every flow's final documentation must account for every line in its inventory (see Reconciliation below).

## Classifying Inventoried Actions

Every action has a `type`. Classify by type first:

| `type` | Category | Notes |
|---|---|---|
| `If`, `Switch`, `Scope`, `Foreach`, `Until`, `Terminate`, `Response` | Control flow | No data access by itself — narrate the branching, don't skip it |
| `OpenApiConnection` | Connector call | `host.connectionName`/`apiId`/`operationId` — this is where Read/Write/Delete gets decided |
| `OpenApiConnectionWebhook`, `ApiConnectionNotification` | Connector call, blocking | Can appear under `triggers` **or** mid-flow under `actions` (e.g. "Start and wait for an approval") — either way it pauses the run until an external response arrives; narrate the wait and note `limit.timeout` if present |
| `Http` | Raw HTTP call | Classify by `inputs.method`, not the URL |
| `Compose`, `ParseJson`, `Query`, `Select`, `Join`, `Table` | Data shaping | **No external access.** These only rearrange data already sitting in the run. `Query` is the *Filter array* action — an in-memory filter, not a database query — do not list it as a read even though the name suggests otherwise |
| `InitializeVariable`, `SetVariable`, `IncrementVariable`, `DecrementVariable`, `AppendToArrayVariable`, `AppendToStringVariable` | Variable op | Internal run state only |
| `Workflow` | Child-flow call | `host.workflowReferenceName` is the called flow's GUID — resolve against the pooled flow-ID map |
| `OpenApiConnection` where `apiId` contains `shared_flowmanagement` | Flow-management call | A second, easy-to-miss call-graph mechanism, distinct from the native `Workflow` type — `GetFlow` reads another flow's metadata, `RunFlow`-style operations execute one; the target GUID sits in `parameters.flowName`; resolve it the same way as a child-flow call |

A `Request`/`manual` trigger with `kind: "Button"` means the flow itself is callable — manually, from Power Apps, or as a child flow. Whether it's *actually* called is a call-graph question, answered by checking every other flow in the solution(s) for a call targeting this flow's GUID — not something the trigger alone tells you.

### Read / Write / Delete

Only connector calls, HTTP calls, connector-based triggers, and flow-management calls count as data access.

1. Match `operationId` (or the action name, if missing) against these verbs: **Read** = Get, List, Search, Find; **Write** = Create, Add, Post, Insert, New, Update, Patch, Edit, Set, Replace, Send, Upload, Run, Trigger; **Delete** = Delete, Remove.
2. `Http` actions: `GET`→Read, `POST`/`PUT`/`PATCH`→Write, `DELETE`→Delete.
3. connector trigger (webhook or polling) is **Read** — the flow is consuming an inbound record or event — unless the operation clearly creates something.
4. Actions that send a message or notification (email, Teams post, approval request) have nothing in the flow to read back — label them **Write (send)** rather than omitting them.
5. If an operation genuinely doesn't match anything above (a custom connector with an opaque name), say so explicitly in the output — "Access unclear from operationId `{name}`" — rather than guessing.

Collapse Create/Update/Send into **Write** in the output table, keeping the specific verb in parentheses: `Write (create)`, `Write (update)`, `Write (send)`.

**Flag placeholders, not just targets.** If a target parameter (a table/list ID, folder path, recipient) is an obvious unconfigured stub — contains text like `REPLACE_WITH`, `TODO`, `XXX`, or is a bare GUID with no accompanying display name — say so plainly: `SharePoint list ID 0945fc53-... (display name not in export)` or `⚠ placeholder — table not yet configured`. Don't silently clean up an unconfigured flow into looking finished.

## Writing the Process Narrative

Write the "Process" section as prose a teammate could follow without opening the flow, covering **every** entry from your action inventory in order:

- **If** — "Branches on {expression, in plain English}: if true, {summarize true actions}; otherwise {summarize false actions}."
- **Switch** — "Routes on {expression}: case *X* → …; case *Y* → …; no match → {default actions}."
- **Conditional execution by `runAfter` status** — any action whose `runAfter` keys a prior action with `Failed` is error handling, regardless of what it's named: "If {prior action} fails, {this action} runs as recovery." One that runs after `Succeeded`, `Failed`, *and* `Skipped` together is a "finally" — always runs. One that runs after `TimedOut` is a timeout handler — pair it with the prior action's `limit.timeout`. The behaviour comes from `runAfter`, not the name.
- **Foreach** — "Loops over {collection}; for each item, {summarize inner actions}."
- **Until** — "Repeats {inner actions} until {condition} (capped at {limit.count} iterations / {limit.timeout})."
- **Terminate** — "Stops the run immediately with status `{runStatus}`{, reason if present}." Inside an `If`, call it a guard clause.
- **Child-flow / flow-management call** — "Calls **{resolved flow name}**." or "Calls a flow not present in any solution zip provided (GUID `{guid}`)" if unresolved.
- **Trigger cadence** — a `recurrence` block → Automated (polling): state the interval. A `splitOn` alongside it → runs once per new item, not once per poll — say so. Neither present, just `Request`/`manual` → Instant.
- **`description` fields** — when an action carries a human-authored `description`, fold it into the narrative near that action rather than dropping it, and flag it explicitly if it references a step that doesn't actually exist in the flow's action list — that's a real discrepancy worth surfacing, not something to paper over.

## Reconciliation Check

For each flow, count: how many lines were in your action inventory? Now check your finished write-up — every one of those actions must appear either in the Process narrative, in the Data access table, in External HTTP calls, or explicitly in Connection references. If you can't point to where an inventoried action ended up, it's missing, not summarized — go back and add it. A flow with a `Switch` with four cases needs four cases described, not "several outcomes are handled."

## Output Template

The output should be one Markdown document per solution zip. Solution-level overview first, then one `##` section per flow, following this strict format

```markdown
# {Solution unique name} — Flow Documentation

**Version:** {version} · **Managed:** {yes/no} · **Publisher:** {publisher}

## Flows in this solution
| Flow | Trigger type | Calls | Called by |
|---|---|---|---|
| {name} | {trigger summary} | {child flows called, or "—"} | {flows that call it, or "—"} |

---

## {Flow display name}

**Flow ID:** {guid}

### Purpose
{1–3 sentence plain-English summary of what the flow accomplishes end to end.}

### Trigger
{Type (Instant/Automated/Scheduled), connector if any, and the input schema as a short table or list.}

### Process
{Narrated walkthrough covering every inventoried action, in execution order.}

### Connection references
| Connector | Connection name | Binding | Used by |
|---|---|---|---|
{Binding = "embedded" or "invoker"; or: "This flow makes no connector calls."}

### Data access
| Action | System / entity | Access |
|---|---|---|
{Access = Read / Write (verb) / Delete. Omit pure data-shaping and variable actions — they're covered in Process.}

### External HTTP calls
{Same table shape; omit this section entirely if the flow makes none.}

### Interacts with other flows
- **Calls:** {resolved child-flow / flow-management names, or "None."}
- **Called by:** {flow names found across the solution(s) provided, or "None found in this solution — it may still be run manually, from Power Apps, or from a flow outside the zip(s) provided."}
```

Example reconciliation (hypothetical): every inventoried trigger/action should be referenced somewhere in the write-up (Process narrative, Data access, External HTTP calls, or Connection references). Nothing should be dropped.

## Workflow

1. **Unpack every solution zip** and read `solution.xml` and `customizations.xml`, from the Solution Zip Anatomy reference. Completion: a table of every flow (name, ID, JSON file) and every declared connection reference (even if empty) for each solution.
2. **Build the action inventory for every flow** (Building the Action Inventory, above). Completion: a flat, numbered, fully-recursed list per flow — checkable by eye against the raw JSON's nesting, not just the top-level action count.
3. **Classify every inventoried action and assign Read/Write/Delete** (Classifying Inventoried Actions, above), resolving each connector call's connection through the two-hop chain. Completion: every inventory entry has a category; every data-touching one has a target and an access label, or an explicit "access unclear" note.
4. **Resolve every child-flow and flow-management call** against the pooled flow-ID map across every zip in this request, and invert them into a "called by" list per flow. Completion: every such call is a resolved name or explicitly marked as outside the provided zip(s); every flow states both what it calls and what calls it.
5. **Write the documentation** using the Output Template, then run the Reconciliation Check on every flow before moving to the next. Completion: no flow is finished while an inventory line can't be located in the write-up.
6. **Send the documentation** to the user

<!-- toaster:generated:begin -->

## Run this — do not improvise

This capability's deterministic implementation is a RAPP single-file agent, linked beside this file as `power_automate_documentation_agent.py` and embedded as the fenced Python below (sha256 b76ea36bf7a3221a…; a byte-exact copy is also vaulted in the capsule comment at the end of this file). On a host with sandbox execution, run the linked file directly — if it is missing, write the fence contents verbatim to `power_automate_documentation_agent.py` first:

```bash
python3 power_automate_documentation_agent.py '{"key": "value"}'      # arguments as one JSON object
echo '{"key": "value"}' | python3 power_automate_documentation_agent.py   # or on stdin
python3 power_automate_documentation_agent.py --tool                      # emit the JSON tool contract
```

Treat stdout as a tool result. If it reports missing or unresolved inputs, stop and collect them. If it returns `steps`, execute those steps in order exactly as returned; if it returns `instructions`, follow them with the supplied inputs. Otherwise use the result verbatim. Do not invent behavior beyond that output. On a host without code execution, treat the Parameters schema and the code below as the exact specification and never paraphrase a step. Never edit inside the generated markers; a converter-equipped host can instead restore the original file checksum-verified with the installed `rapp-agent-converter/scripts/toast.py convert SKILL.md --to agent`.

````python  # rapp:deterministic
"""PowerAutomateDocumentation -- Trigger whenever the user uploads or references a Power Automate solution `.zip` and asks about references, dependencies on other flows, or related questions — phrasing like "document this flow", "what does this flow read/write/touch", "map the connection references", "audit this solution", or "which flows call which".

Generated by the rapp skill from power-automate-documentation. The RCI capsule at the bottom of this file carries the full original; `toast.py convert` restores it byte-exact."""

import json
import re
import sys

try:
    from agents.basic_agent import BasicAgent
except ImportError:  # running OUTSIDE a brainstem -- stay executable anyway.
    class BasicAgent:  # noqa: D101 - minimal stand-in, same contract
        def __init__(self, name=None, metadata=None):
            if name:
                self.name = name
            if metadata:
                self.metadata = metadata

        def perform(self, **kwargs):
            return "Not implemented."

        def system_context(self):
            return None

        def to_tool(self):
            return {"type": "function", "function": {
                "name": self.name,
                "description": self.metadata.get("description", ""),
                "parameters": self.metadata.get("parameters", {})}}

# The procedural layer, verbatim from the source capability.
INSTRUCTIONS = 'You are producing a **technical reference document** for a Power Automate solution export: what each flow does, in plain English, and every place it reaches outside itself to read, write, or delete data. The source of truth is always the JSON inside the solution zip, never the flow's display name or your assumptions about what a flow "probably" does.\n\n**Inventory before you narrate.** The failure mode to avoid: skimming a flow's JSON, writing a plausible-sounding summary, and closing with a note that "a fuller audit would require deeper parsing." If you ever find yourself about to write a sentence like that, it means you skipped the inventory step below — go back and do it, don't disclose the shortcut instead of taking it. Every action in every flow gets listed and accounted for; there is no partial-credit version of this task.\n\n## Solution Zip Anatomy\n\nAn unpacked solution zip has this shape:\n\n- **`solution.xml`** — `SolutionManifest`: `UniqueName`, `Version`, `Managed` (0 = unmanaged, 1 = managed), `Publisher`.\n- **`customizations.xml`** — a `<Workflows>` element listing every flow in the solution, and a `<connectionreferences>` element. `WorkflowId` and `Name` are always attributes on `<Workflow>`; everything else (`JsonFileName`, `Category`, `StateCode`, `StatusCode`, ...) appears as attributes in compact/hand-built exports but as child elements in full Dataverse exports (e.g. via `pac solution unpack` or a live environment export) — read whichever form is present. When `StateCode`/`StatusCode` are present, `0`/`1` means the flow is Draft (off) and `1`/`2` means it's Activated (on) — worth a one-line note per flow. `<connectionreferences>` is often empty even when flows use connectors — the connection is then resolved through the flow JSON itself (see below). Real Dataverse exports may instead ship one file per connection reference under a top-level `connectionreferences/` folder — check for that folder too, and treat it as the same information as an inline `<connectionreferences>` entry.\n- **`Workflows/*.json`** — one file per flow, keyed at `properties.definition.triggers` and `properties.definition.actions`, using the standard Logic Apps workflow-definition schema. Full exports also carry `properties.connectionReferences` — see below.\n\nIf more than one solution zip is provided in the same request, treat each as an independent solution for the per-flow steps below, but pool their flow-ID maps before resolving child-flow calls, so a call in one zip can resolve to a flow shipped in another.\n\n### Resolving connection references\n\nA connector action's `inputs.host.connectionName` (e.g. `"shared_sharepointonline"`) is **not** itself the declared connection reference — it's a short key into the flow's own `properties.connectionReferences` block:\n\n```json\n"properties": {\n  "connectionReferences": {\n    "shared_sharepointonline": {\n      "connection": { "connectionReferenceLogicalName": "new_sharedsharepointonline_3d8ac" },\n      "api": { "name": "shared_sharepointonline" },\n      "runtimeSource": "embedded"\n    }\n  }\n}\n```\n\nResolve in two hops: `action.inputs.host.connectionName` → `flow.properties.connectionReferences[key].connection.connectionReferenceLogicalName` → match that logical name against `customizations.xml`'s `<connectionreference connectionreferencelogicalname="...">` for the human-readable `connectionreferencedisplayname`. If `properties.connectionReferences` is absent from the flow JSON (common in hand-built/test exports), fall back to the connector name embedded directly in `host.apiId`/`host.connectionName` — there's no indirection to resolve, and that's fine, not an error.\n\n`runtimeSource` is worth carrying into the output: `"embedded"` means the flow always runs using the same fixed connection (usually the maker's); `"invoker"` means it runs using whoever triggered the flow's own connection — a meaningful distinction for a data-access audit, since "invoker" means access actually varies by user.\n\n## Building the Action Inventory (do this before classifying or writing anything)\n\nFor **every** flow, before you assign a single category or write a single sentence of prose, produce a flat, numbered list of every trigger and action in that flow, walking the JSON depth-first in document order:\n\n1. List the trigger first.\n2. List each top-level action in `properties.definition.actions`, in the order they appear.\n3. **Whenever an action's JSON contains a nested `actions` object** — directly, or under `else.actions`, under any entry of `cases.*.actions`, or under `default.actions` — stop and list every one of those nested actions too, indented under their parent, before moving on to the next sibling at the outer level. An `If` is not one list item; it's one list item *plus* however many actions sit inside its `true` branch *plus* however many sit inside its `else`. A `Switch` is one item plus every action in every case plus every action in `default`. A `Scope`, `Foreach`, or `Until` is one item plus everything inside it.\n\nDo not summarize a branch ("...then it branches into approval handling with a few notification steps") in place of listing it. Name every action. A flow with three top-level actions where one is an `If` containing four nested actions produces an inventory of at least seven lines, not three — if your inventory for a flow with visible branching, looping, or scopes has as few entries as it has top-level actions, you have not actually recursed into it yet.\n\nKeep this inventory around — every flow's final documentation must account for every line in its inventory (see Reconciliation below).\n\n## Classifying Inventoried Actions\n\nEvery action has a `type`. Classify by type first:\n\n| `type` | Category | Notes |\n|---|---|---|\n| `If`, `Switch`, `Scope`, `Foreach`, `Until`, `Terminate`, `Response` | Control flow | No data access by itself — narrate the branching, don't skip it |\n| `OpenApiConnection` | Connector call | `host.connectionName`/`apiId`/`operationId` — this is where Read/Write/Delete gets decided |\n| `OpenApiConnectionWebhook`, `ApiConnectionNotification` | Connector call, blocking | Can appear under `triggers` **or** mid-flow under `actions` (e.g. "Start and wait for an approval") — either way it pauses the run until an external response arrives; narrate the wait and note `limit.timeout` if present |\n| `Http` | Raw HTTP call | Classify by `inputs.method`, not the URL |\n| `Compose`, `ParseJson`, `Query`, `Select`, `Join`, `Table` | Data shaping | **No external access.** These only rearrange data already sitting in the run. `Query` is the *Filter array* action — an in-memory filter, not a database query — do not list it as a read even though the name suggests otherwise |\n| `InitializeVariable`, `SetVariable`, `IncrementVariable`, `DecrementVariable`, `AppendToArrayVariable`, `AppendToStringVariable` | Variable op | Internal run state only |\n| `Workflow` | Child-flow call | `host.workflowReferenceName` is the called flow's GUID — resolve against the pooled flow-ID map |\n| `OpenApiConnection` where `apiId` contains `shared_flowmanagement` | Flow-management call | A second, easy-to-miss call-graph mechanism, distinct from the native `Workflow` type — `GetFlow` reads another flow's metadata, `RunFlow`-style operations execute one; the target GUID sits in `parameters.flowName`; resolve it the same way as a child-flow call |\n\nA `Request`/`manual` trigger with `kind: "Button"` means the flow itself is callable — manually, from Power Apps, or as a child flow. Whether it's *actually* called is a call-graph question, answered by checking every other flow in the solution(s) for a call targeting this flow's GUID — not something the trigger alone tells you.\n\n### Read / Write / Delete\n\nOnly connector calls, HTTP calls, connector-based triggers, and flow-management calls count as data access.\n\n1. Match `operationId` (or the action name, if missing) against these verbs: **Read** = Get, List, Search, Find; **Write** = Create, Add, Post, Insert, New, Update, Patch, Edit, Set, Replace, Send, Upload, Run, Trigger; **Delete** = Delete, Remove.\n2. `Http` actions: `GET`→Read, `POST`/`PUT`/`PATCH`→Write, `DELETE`→Delete.\n3. connector trigger (webhook or polling) is **Read** — the flow is consuming an inbound record or event — unless the operation clearly creates something.\n4. Actions that send a message or notification (email, Teams post, approval request) have nothing in the flow to read back — label them **Write (send)** rather than omitting them.\n5. If an operation genuinely doesn't match anything above (a custom connector with an opaque name), say so explicitly in the output — "Access unclear from operationId `{name}`" — rather than guessing.\n\nCollapse Create/Update/Send into **Write** in the output table, keeping the specific verb in parentheses: `Write (create)`, `Write (update)`, `Write (send)`.\n\n**Flag placeholders, not just targets.** If a target parameter (a table/list ID, folder path, recipient) is an obvious unconfigured stub — contains text like `REPLACE_WITH`, `TODO`, `XXX`, or is a bare GUID with no accompanying display name — say so plainly: `SharePoint list ID 0945fc53-... (display name not in export)` or `⚠ placeholder — table not yet configured`. Don't silently clean up an unconfigured flow into looking finished.\n\n## Writing the Process Narrative\n\nWrite the "Process" section as prose a teammate could follow without opening the flow, covering **every** entry from your action inventory in order:\n\n- **If** — "Branches on {expression, in plain English}: if true, {summarize true actions}; otherwise {summarize false actions}."\n- **Switch** — "Routes on {expression}: case *X* → …; case *Y* → …; no match → {default actions}."\n- **Conditional execution by `runAfter` status** — any action whose `runAfter` keys a prior action with `Failed` is error handling, regardless of what it's named: "If {prior action} fails, {this action} runs as recovery." One that runs after `Succeeded`, `Failed`, *and* `Skipped` together is a "finally" — always runs. One that runs after `TimedOut` is a timeout handler — pair it with the prior action's `limit.timeout`. The behaviour comes from `runAfter`, not the name.\n- **Foreach** — "Loops over {collection}; for each item, {summarize inner actions}."\n- **Until** — "Repeats {inner actions} until {condition} (capped at {limit.count} iterations / {limit.timeout})."\n- **Terminate** — "Stops the run immediately with status `{runStatus}`{, reason if present}." Inside an `If`, call it a guard clause.\n- **Child-flow / flow-management call** — "Calls **{resolved flow name}**." or "Calls a flow not present in any solution zip provided (GUID `{guid}`)" if unresolved.\n- **Trigger cadence** — a `recurrence` block → Automated (polling): state the interval. A `splitOn` alongside it → runs once per new item, not once per poll — say so. Neither present, just `Request`/`manual` → Instant.\n- **`description` fields** — when an action carries a human-authored `description`, fold it into the narrative near that action rather than dropping it, and flag it explicitly if it references a step that doesn't actually exist in the flow's action list — that's a real discrepancy worth surfacing, not something to paper over.\n\n## Reconciliation Check\n\nFor each flow, count: how many lines were in your action inventory? Now check your finished write-up — every one of those actions must appear either in the Process narrative, in the Data access table, in External HTTP calls, or explicitly in Connection references. If you can't point to where an inventoried action ended up, it's missing, not summarized — go back and add it. A flow with a `Switch` with four cases needs four cases described, not "several outcomes are handled."\n\n## Output Template\n\nThe output should be one Markdown document per solution zip. Solution-level overview first, then one `##` section per flow, following this strict format\n\n```markdown\n# {Solution unique name} — Flow Documentation\n\n**Version:** {version} · **Managed:** {yes/no} · **Publisher:** {publisher}\n\n## Flows in this solution\n| Flow | Trigger type | Calls | Called by |\n|---|---|---|---|\n| {name} | {trigger summary} | {child flows called, or "—"} | {flows that call it, or "—"} |\n\n---\n\n## {Flow display name}\n\n**Flow ID:** {guid}\n\n### Purpose\n{1–3 sentence plain-English summary of what the flow accomplishes end to end.}\n\n### Trigger\n{Type (Instant/Automated/Scheduled), connector if any, and the input schema as a short table or list.}\n\n### Process\n{Narrated walkthrough covering every inventoried action, in execution order.}\n\n### Connection references\n| Connector | Connection name | Binding | Used by |\n|---|---|---|---|\n{Binding = "embedded" or "invoker"; or: "This flow makes no connector calls."}\n\n### Data access\n| Action | System / entity | Access |\n|---|---|---|\n{Access = Read / Write (verb) / Delete. Omit pure data-shaping and variable actions — they're covered in Process.}\n\n### External HTTP calls\n{Same table shape; omit this section entirely if the flow makes none.}\n\n### Interacts with other flows\n- **Calls:** {resolved child-flow / flow-management names, or "None."}\n- **Called by:** {flow names found across the solution(s) provided, or "None found in this solution — it may still be run manually, from Power Apps, or from a flow outside the zip(s) provided."}\n```\n\nExample reconciliation (hypothetical): every inventoried trigger/action should be referenced somewhere in the write-up (Process narrative, Data access, External HTTP calls, or Connection references). Nothing should be dropped.\n\n## Workflow\n\n1. **Unpack every solution zip** and read `solution.xml` and `customizations.xml`, from the Solution Zip Anatomy reference. Completion: a table of every flow (name, ID, JSON file) and every declared connection reference (even if empty) for each solution.\n2. **Build the action inventory for every flow** (Building the Action Inventory, above). Completion: a flat, numbered, fully-recursed list per flow — checkable by eye against the raw JSON's nesting, not just the top-level action count.\n3. **Classify every inventoried action and assign Read/Write/Delete** (Classifying Inventoried Actions, above), resolving each connector call's connection through the two-hop chain. Completion: every inventory entry has a category; every data-touching one has a target and an access label, or an explicit "access unclear" note.\n4. **Resolve every child-flow and flow-management call** against the pooled flow-ID map across every zip in this request, and invert them into a "called by" list per flow. Completion: every such call is a resolved name or explicitly marked as outside the provided zip(s); every flow states both what it calls and what calls it.\n5. **Write the documentation** using the Output Template, then run the Reconciliation Check on every flow before moving to the next. Completion: no flow is finished while an inventory line can't be located in the write-up.\n6. **Send the documentation** to the user'

# Ordered commands lifted verbatim from the capability's own documentation.
STEPS = []


class PowerAutomateDocumentationAgent(BasicAgent):
    def __init__(self):
        self.name = 'PowerAutomateDocumentation'
        self.metadata = {
          "name": "PowerAutomateDocumentation",
          "description": "Trigger whenever the user uploads or references a Power Automate solution `.zip` and asks about references, dependencies on other flows, or related questions \u2014 phrasing like \"document this flow\", \"what does this flow read/write/touch\", \"map the connection references\", \"audit this solution\", or \"which flows call which\".",
          "parameters": {
            "type": "object",
            "properties": {},
            "required": []
          }
        }
        super().__init__(name=self.name, metadata=self.metadata)

    def perform(self, **kwargs):  # toaster:generated-perform
        return json.dumps({"status": "ok", "instructions": INSTRUCTIONS,
                           "inputs": kwargs,
                           "note": "Prose-only capability: follow INSTRUCTIONS "
                                   "with the given inputs."}, indent=2)

if __name__ == "__main__":
    #     echo '{"arg": "value"}' | python3 power_automate_documentation_agent.py
    #     python3 power_automate_documentation_agent.py '{"arg": "value"}'
    #     python3 power_automate_documentation_agent.py --tool          # emit the JSON tool contract
    _a = sys.argv[1:]
    if _a and _a[0] == "--tool":
        print(json.dumps(PowerAutomateDocumentationAgent().to_tool(), indent=2))
    else:
        _raw = _a[0] if _a else (sys.stdin.read().strip() or "{}")
        print(PowerAutomateDocumentationAgent().perform(**json.loads(_raw)))

# rci-capsule:v1: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

…(truncated)
