@pipefy/pipefy-process-coder 0.1.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 (92) hide show
  1. package/.agents/skills/ppc-pipefy-flow-authoring/SKILL.md +262 -0
  2. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-danfe-consulta.json +247 -0
  3. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-webhook-retorno-consulta.json +589 -0
  4. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-4-recebimento-barramento.json +1391 -0
  5. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-2-danfe-retorno.json +623 -0
  6. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-4-criacao-operacao.json +636 -0
  7. package/.agents/skills/ppc-pipefy-flow-authoring/examples/03-subflow-4-criacao-titulo.json +3642 -0
  8. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-cedente.json +863 -0
  9. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-sacado.json +799 -0
  10. package/.agents/skills/ppc-pipefy-flow-authoring/examples/README.md +42 -0
  11. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-acompanhamento-cobranca.json +581 -0
  12. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-nfe-monitoramento.json +503 -0
  13. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-retorno-bancario.json +562 -0
  14. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-cedente.json +557 -0
  15. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-sacado.json +609 -0
  16. package/.agents/skills/ppc-pipefy-pipe-authoring/SKILL.md +146 -0
  17. package/.agents/skills/ppc-pipefy-process-design/SKILL.md +127 -0
  18. package/.agents/skills/ppc-pipefy-workspace/SKILL.md +91 -0
  19. package/AGENTS.md +456 -0
  20. package/README.md +808 -0
  21. package/bin/pipe.js +13 -0
  22. package/package.json +35 -0
  23. package/src/apply/adopt.ts +150 -0
  24. package/src/apply/agentops.ts +71 -0
  25. package/src/apply/compile.ts +875 -0
  26. package/src/apply/execute.ts +336 -0
  27. package/src/apply/flowops.ts +399 -0
  28. package/src/apply/idmap.ts +241 -0
  29. package/src/apply/mutations.ts +955 -0
  30. package/src/apply/registry.ts +430 -0
  31. package/src/apply/types.ts +134 -0
  32. package/src/cli/args.ts +88 -0
  33. package/src/cli.ts +211 -0
  34. package/src/codec/flow.ts +199 -0
  35. package/src/codec/pack.ts +103 -0
  36. package/src/codec/roundtrip.ts +94 -0
  37. package/src/codec/unpack.ts +198 -0
  38. package/src/commands/agents.ts +184 -0
  39. package/src/commands/apply.ts +1144 -0
  40. package/src/commands/context.ts +119 -0
  41. package/src/commands/create.ts +79 -0
  42. package/src/commands/diff.ts +314 -0
  43. package/src/commands/flows.ts +414 -0
  44. package/src/commands/misc.ts +644 -0
  45. package/src/commands/plan.ts +331 -0
  46. package/src/commands/pull.ts +567 -0
  47. package/src/commands/runs.ts +83 -0
  48. package/src/commands/skills.ts +137 -0
  49. package/src/commands/verify.ts +253 -0
  50. package/src/config.ts +168 -0
  51. package/src/diff/agents.ts +122 -0
  52. package/src/diff/diff.ts +1130 -0
  53. package/src/diff/flow.ts +318 -0
  54. package/src/diff/html.ts +322 -0
  55. package/src/diff/render.ts +101 -0
  56. package/src/model/payload.ts +154 -0
  57. package/src/model/tree.ts +99 -0
  58. package/src/model/volatile.ts +55 -0
  59. package/src/pipefy/agents.ts +165 -0
  60. package/src/pipefy/automations.ts +219 -0
  61. package/src/pipefy/capability.ts +119 -0
  62. package/src/pipefy/client.ts +267 -0
  63. package/src/pipefy/discovery.ts +209 -0
  64. package/src/pipefy/internal.ts +380 -0
  65. package/src/pipefy/ipaas.ts +365 -0
  66. package/src/pipefy/reconstruct.ts +775 -0
  67. package/src/pipefy/reference.ts +251 -0
  68. package/src/pipefy/snapshot.ts +245 -0
  69. package/src/pipefy/toolkit.ts +200 -0
  70. package/src/pipefy/toolkit_bearer.py +137 -0
  71. package/src/report/integrations.ts +231 -0
  72. package/src/report/run.ts +475 -0
  73. package/src/util/fsx.ts +45 -0
  74. package/src/util/git.ts +32 -0
  75. package/src/util/json.ts +55 -0
  76. package/src/util/log.ts +76 -0
  77. package/src/util/pool.ts +48 -0
  78. package/src/util/slug.ts +26 -0
  79. package/src/util/tui.ts +335 -0
  80. package/src/validate/index.ts +123 -0
  81. package/src/validate/integrity.ts +387 -0
  82. package/src/validate/reference.ts +136 -0
  83. package/src/validate/schema.ts +328 -0
  84. package/src/workspace/agents.ts +290 -0
  85. package/src/workspace/docs.ts +407 -0
  86. package/src/workspace/flows.ts +191 -0
  87. package/src/workspace/layout.ts +165 -0
  88. package/src/workspace/lock.ts +148 -0
  89. package/src/workspace/read.ts +165 -0
  90. package/src/workspace/reference.ts +24 -0
  91. package/src/workspace/stamp.ts +301 -0
  92. package/src/workspace/write.ts +225 -0
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: ppc-pipefy-pipe-authoring
3
+ description: Use when writing or editing the JSON of a Pipefy pipe's structure in a pipefy-process-coder workspace — phases, fields, automations, field conditions, labels, webhooks, pipe relations, preferences, public form. Covers the file layout, referencing entities that do not exist yet, and the specific edits that fail silently because a mutation accepts them and ignores them.
4
+ ---
5
+
6
+ # Authoring pipe structure JSON
7
+
8
+ Read `ppc-pipefy-workspace` first for the loop and the identity rules. This is the
9
+ detail: what goes in which file, and which edits look fine and are not.
10
+
11
+ ## Where things live
12
+
13
+ ```
14
+ pipes/<id>--<name>/
15
+ pipe.json the repo row: name, noun, public, title_field_id
16
+ phases/NN--name.json the phase, its fields[], jump_target_ids[], team_member_ids[]
17
+ automations/<name>.json the automation, its condition, field_maps, response_schemas
18
+ field-conditions/<name>.json the condition and its actions
19
+ labels.json webhooks.json public-form.json preferences.json relations.json
20
+ _readonly/ _non-snapshot/ _meta.json
21
+ ```
22
+
23
+ **Fields live inside their phase file.** That is where they make sense and where
24
+ you will edit them most. Ordering comes from the `index` value inside the file,
25
+ never from the filename — renumbering works without renaming.
26
+
27
+ The start form is a real phase at `index: 0`. Deleting or reindexing it is
28
+ catastrophic; the validator refuses.
29
+
30
+ ## Creating something new
31
+
32
+ ```json
33
+ { "id": null, "_new": true, "uuid": "<fresh v4>", "label": "Severity", "type_id": "select", … }
34
+ ```
35
+
36
+ A new field nested in an existing phase file needs no `phase_id` — it inherits its
37
+ owner's. A field in a *new* phase leaves it null; the compiler substitutes the
38
+ phase's real id after the phase is created.
39
+
40
+ **Referencing something you are creating in the same edit:** leave
41
+ `%{_new:<its uuid>}` and the applier substitutes the real id once it exists. This
42
+ works in automation `field_maps`, condition `field_address`, `jump_target_ids`,
43
+ and field-condition action targets.
44
+
45
+ ## What cannot be changed at all
46
+
47
+ | Edit | Why |
48
+ | --- | --- |
49
+ | a field's `type_id` | `updatePhaseField` has no `type_id`. Delete+create destroys every existing value. |
50
+ | a field's `phase_id` | same — no input, and moving it would destroy the data |
51
+ | a phase's `index` | `createPhase` takes an index, `updatePhase` does not |
52
+ | a pipe's `description` | `UpdatePipeInput` has no `description` field at all |
53
+ | `start_form_title` | no input on any preference mutation |
54
+ | anything under `_readonly/` | no public write path exists |
55
+
56
+ If a request needs one of these, say so and stop. `pipe plan` refuses them per
57
+ column, with the reason.
58
+
59
+ ## The silent failures — every one of these cost a live incident
60
+
61
+ These are edits the API *accepts* and does not apply, or applies wrongly. They are
62
+ guarded now, but understanding them stops you writing them.
63
+
64
+ **A mutation can succeed and change nothing.** Arguments are filtered against the
65
+ live schema, so an argument the mutation has no input for is dropped and the call
66
+ returns success. `description`, `anyone_can_create_card` and `create_card_label`
67
+ all did this on `updatePipe`. That is why the registry names settable columns per
68
+ entity rather than trusting the mutation.
69
+
70
+ **Some arguments want a uuid where the payload stores an id.**
71
+ `updatePipe.title_field_id` takes the field's **uuid** or slug, never the numeric
72
+ id the payload holds. `archiveField` takes `{ uuid }`. Both are translated for you.
73
+
74
+ **`phaseSettings` reports failure for changes it has already made.** Observed on
75
+ three separate runs, with the row present in the read-back and timestamped to the
76
+ failing call. Its errors are inconclusive — re-read before assuming nothing
77
+ happened.
78
+
79
+ **`hidden_top_buttons` and `hidden_start_form_attributes` are closed sets:**
80
+ `Assignees, Attachments, Checklist, Comments, DueDate, Labels`. Anything else is
81
+ rejected mid-apply. Checked offline now.
82
+
83
+ **Every event and action accepts a fixed parameter list, and nothing enforces it.**
84
+ Read `REFERENCE.md` in the workspace before writing an automation. It is generated
85
+ by `pipe pull` from the API and lists each event's `event_params`, each action's
86
+ `action_params`, and which pairings are refused.
87
+
88
+ The reason it needs reading: the schema publishes these lists, then types
89
+ `event_id`, `action_id` and `type_id` as `ID` and `String`, so GraphQL validates
90
+ none of them. A wrong value returns `is invalid` or `All fields must be filled
91
+ properly.` — naming nothing — partway through an apply. And the mistakes are
92
+ near-misses, not nonsense:
93
+
94
+ - `card_moved` takes only `to_phase_id`. `in_phase_id` is real, on
95
+ `card_inbox_received_email`.
96
+ - `card_id` is accepted on `update_card_field` and refused on `move_single_card`.
97
+
98
+ `pipe validate` catches all of it offline and names the parameter, so run it before
99
+ `plan`. Treat it as the backstop, not the reference.
100
+
101
+ **Automation params mix naming conventions.** `AutomationActionParamsInput` has
102
+ `card_id`, `to_phase_id` and `field_map` in snake_case, but `taskParams`,
103
+ `aiParams` and `httpMethod` in camelCase. Getting one wrong deleted a live
104
+ automation once, because the update strategy is delete-then-recreate.
105
+
106
+ **Pipe relation flags are spelled differently in the mutation and the payload.**
107
+ `createPipeRelation` says `canCreateNewItems`; the payload row says
108
+ `can_create_connected_cards`. Set both, or read the one that is there.
109
+
110
+ **A phase's jump set is set-replacing.** `jump_target_ids` is written whole. Never
111
+ append blindly to a set you have not fully read.
112
+
113
+ ## Cross-surface obligations
114
+
115
+ **Deleting a field obliges you to fix every reference to it.** Automations and
116
+ conditions hold bare numeric ids inside opaque JSON — `"field_address": "<fieldId>"`,
117
+ `"trigger_field_ids": ["<fieldId>"]`, `"%{<fieldId>}"`. `pipe validate` fails on a
118
+ dangling reference by design. `MAPPING.md` translates every id.
119
+
120
+ **If the pipe has iPaaS flows, a field may be referenced there too**, by slug,
121
+ and the tool cannot yet see it. Check `flows/` if it exists before deleting a
122
+ field. See `ppc-pipefy-flow-authoring`.
123
+
124
+ ## A worked shape: creating a phase with fields and a jump
125
+
126
+ The numbers below are made up — use the real ids from `MAPPING.md`.
127
+
128
+ ```json
129
+ // phases/09--customer-validation.json
130
+ {
131
+ "id": null, "_new": true, "uuid": "<fresh v4>",
132
+ "name": "Customer Validation", "index": 9, "done": false,
133
+ "repo_id": 100200300,
134
+ "fields": [
135
+ { "id": null, "_new": true, "uuid": "<fresh v4>", "label": "Customer acceptance",
136
+ "type_id": "select", "options": ["Accepted", "Needs rework", "Declined"],
137
+ "index": 0, "required": false, "repo_id": 100200300 }
138
+ ],
139
+ "jump_target_ids": [400500600],
140
+ "team_member_ids": []
141
+ }
142
+ ```
143
+
144
+ Set `index` on new phases explicitly and leave existing ones alone — their index
145
+ cannot be edited. Creating a phase auto-links jumps, so state the jump set you
146
+ want or the platform's default stands.
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: ppc-pipefy-process-design
3
+ description: Use when deciding HOW to build a requirement in Pipefy — which mechanism a need calls for (phase, field condition, automation, iPaaS flow, pipe relation, phase jump, connector field), and how to shape a process so it survives contact with real users. Read before authoring, when the question is "what should this look like" rather than "what JSON does this need".
4
+ ---
5
+
6
+ # Designing a process in Pipefy
7
+
8
+ Most bad Pipefy builds are not bad JSON. They are the right JSON for the wrong
9
+ mechanism — an automation doing what a field condition should, a `CODE` node doing
10
+ what a piece action does, a flow built where a phase would have been enough.
11
+
12
+ Pick the mechanism first.
13
+
14
+ ## Choosing the mechanism
15
+
16
+ Work down this list and stop at the first one that fits. Each rung costs more to
17
+ build, more to explain, and more to debug than the one above it.
18
+
19
+ | Need | Mechanism | Why it is the cheapest fit |
20
+ | --- | --- | --- |
21
+ | Show or hide a field depending on another field | **field condition** | Instant, no run history, no failure mode. Never build this as an automation. |
22
+ | Constrain where a card can move | **phase jump set** | Structural. The board enforces it; no rule can be out of date. |
23
+ | Stamp a timestamp, copy a value, set a label, move a card, send a task or email — **within one pipe** | **automation** | Native, visible in the pipe, no external dependency, no separate credential |
24
+ | Compute something from fields in the same card | **automation with a formula, or an AI action** | Still inside the pipe |
25
+ | Link cards between two pipes so people can navigate between them | **pipe relation + connector field** | Structural and bidirectional. An automation that "creates a card" does not link anything. |
26
+ | Hold a reusable list people pick from — departments, products, suppliers, services | **a database**, not a pipe | A database has columns and records but no workflow. Reach it with a `connector` field. Reaching for a pipe here gives you phases nobody moves cards through. |
27
+ | Store rows that a process reads but nobody works on | **a database** | If a row has no owner and no next step, it is reference data. Cards are for work that moves. |
28
+ | Create a card in another pipe when something happens | **automation with `create_card` and `action_repo_id`** | Cross-pipe *writes* are within the automation engine's reach |
29
+ | **Read** a pipe other than the one that triggered | **iPaaS flow** | The automation engine cannot. This is the real dividing line. |
30
+ | Touch anything outside Pipefy — Slack, HTTP, Sheets, an ERP | **iPaaS flow** | Only iPaaS has pieces |
31
+ | Aggregate across many cards, or run on a schedule | **iPaaS flow** | No automation trigger does either |
32
+ | Anything else, plus arbitrary logic | **iPaaS flow with a `CODE` node** | Last rung. See the ladder below. |
33
+
34
+ ### The dividing line worth memorising
35
+
36
+ **An automation can write to another pipe. It cannot read one.** That single fact
37
+ decides most automation-versus-flow questions. "When X completes, open a card in
38
+ Y" is an automation. "When X completes, check whether the card in Y exists" is a
39
+ flow.
40
+
41
+ ### Inside a flow, the same discipline applies
42
+
43
+ 1. A **piece action** that already does it — check the catalogue first.
44
+ 2. An **inline formula expression** in a free-text input.
45
+ 3. A **`CODE` node**, only when no piece fits and the logic exceeds a formula.
46
+
47
+ A `CODE` node is where the destination of a write can be computed at runtime,
48
+ which means no static check can see it. That is the most expensive kind of bug in
49
+ this domain — prefer a piece.
50
+
51
+ ## Shaping the process itself
52
+
53
+ **Name the phases after states, not actions.** "Qualification & Prioritization",
54
+ not "Qualify". A card *is in* a state; it does not *do* a phase.
55
+
56
+ **Every phase needs an exit.** A phase whose `jump_target_ids` is empty is a dead
57
+ end — cards arrive and cannot leave. Author the jump set explicitly; creating a
58
+ phase auto-links jumps, and the platform's default is rarely the process you meant.
59
+
60
+ **Make required fields answerable at the moment they are asked.** An intake form
61
+ that requires a value only known after triage is a form nobody can submit. Real
62
+ example: a Customer Success intake required "Account tier" and "Customer impact",
63
+ which meant an automated handoff from another pipe could never satisfy it — the
64
+ form was blocked until both became triage-time fields.
65
+
66
+ **Distinguish intake from work.** The start form is what the requester knows; the
67
+ first phase is what the team adds. Do not put "Triage owner" on the start form.
68
+
69
+ **Let the last phase mean something.** Set `done: true` on the phase that ends the
70
+ process. Webhooks and `cardDone` triggers depend on it, and a pipe whose final
71
+ phase is not marked done cannot fire either.
72
+
73
+ **Give closure a reason.** A "Rejected / Closed" phase with a `select` for the
74
+ reason turns a dead card into data. Without it, nobody can answer "why do we
75
+ decline things".
76
+
77
+ ## Designing the integration, not just the flow
78
+
79
+ **Silence on success, noise on failure.** A flow that messages someone every time
80
+ things go right gets muted, and then the one that matters is invisible too. If both
81
+ outcomes are worth saying, say different things — and make the failure louder.
82
+
83
+ **Assume the other side can be missing.** A cross-pipe read returns an empty list
84
+ when the thing you expected was never created. That empty list is usually the most
85
+ interesting outcome — handle it in a `ROUTER` branch rather than letting the flow
86
+ run on with nothing.
87
+
88
+ **Let a search fail without stopping the flow.** Set `continueOnFailure` on the
89
+ step that can legitimately find nothing, so the branch that reports "not found"
90
+ is reachable.
91
+
92
+ **Never assume a connection exists in another workspace.** Connections are
93
+ pipe-scoped. A flow that works in one pipe will not import into another that has
94
+ no connection for the same piece.
95
+
96
+ ## Reviewing a process someone else built
97
+
98
+ Ask these in order. Each has caught a real problem.
99
+
100
+ 1. **Does every phase have an exit?** Check every `jump_target_ids`.
101
+ 2. **Can the start form actually be submitted** by the people and systems that
102
+ need to submit it?
103
+ 3. **Is any required field only knowable later?**
104
+ 4. **Does the final phase have `done: true`?**
105
+ 5. **Does anything reference a field that no longer exists?** `pipe validate`
106
+ answers this; a dangling reference is an error by design.
107
+ 6. **If the pipe has flows, does any of them read a field the pipe is about to
108
+ lose?** The tool cannot check this yet — look at `flows/` yourself.
109
+ 7. **Is any automation doing a field condition's job?** Those are the first
110
+ things to delete.
111
+ 8. **Are there duplicate automations?** Two identical rules on one trigger is
112
+ almost always the residue of a failed apply, not a decision.
113
+
114
+ ## Two failure modes specific to this tool
115
+
116
+ **A partial apply leaves created entities looking new.** If an apply half-fails,
117
+ the files may still say `"id": null, "_new": true` for things that now exist.
118
+ Re-planning would create them twice. `pipe apply` repairs this automatically, and
119
+ `pipe verify --adopt` does it on demand — but if you see a diff proposing to create
120
+ something you know exists, that is the cause.
121
+
122
+ **A clean `pipe diff` covers flows too** — but only where they were readable.
123
+ Check `pipes/<id>--<name>/flows/_meta.json`: `coverage: "complete"` means the
124
+ flows were compared and match. **`coverage: "unknown"` means iPaaS could not be
125
+ reached for that pipe, so its flows were not compared at all** — and a pipe can
126
+ diff clean while its actual behaviour changed entirely, because the change was in
127
+ a flow nobody looked at. That is the one case where a clean diff is not proof.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: ppc-pipefy-workspace
3
+ description: Use when working in a pipefy-process-coder workspace — pulling a Pipefy pipe to disk, editing its JSON, diffing, planning and applying changes back. Covers the loop, which command to run when, what each generated doc is for, and the safety rules that make an apply trustworthy. Start here before editing anything.
4
+ ---
5
+
6
+ # Working in a Pipefy workspace
7
+
8
+ A workspace is a Pipefy pipe pulled to disk as JSON. You edit the files; the tool
9
+ turns your edits into API calls. Nothing about a pipe is edited through this tool
10
+ except by changing a file and applying it.
11
+
12
+ ## The loop, and why each step exists
13
+
14
+ ```bash
15
+ pipe pull <pipeId> --out ./my-pipe # the pipe AND its iPaaS flows
16
+ cd my-pipe
17
+ # edit the JSON
18
+ pipe validate # shape + referential integrity. Offline, instant.
19
+ pipe diff # what you changed — pipe structure and flows
20
+ pipe plan --dry-run # is every change applicable? refuses if not
21
+ pipe apply # snapshot, apply step by step, verify, adopt
22
+ ```
23
+
24
+ **One loop covers both halves of a process.** A pipe's iPaaS flows are entities of
25
+ that pipe — its own webhook URLs carry their ids — so they are pulled, diffed,
26
+ planned and applied by the same commands, and land under
27
+ `pipes/<id>--<name>/flows/`. `--no-flows` opts out of the flow half.
28
+
29
+ **`pipe validate && pipe plan --dry-run` must both be clean before you propose an
30
+ apply.** Both are free — validate needs no network, plan sends no writes — so
31
+ iterate against them yourself rather than asking the user to run an apply and see.
32
+
33
+ `pipe apply` does four things in order: snapshots the pipe as a rollback point,
34
+ executes step by step persisting each status so a failure is resumable, reads the
35
+ pipe back and diffs it against your intent, then adopts the result into the files
36
+ so server-assigned ids are real. If it half-succeeds, read the output — it tells
37
+ you whether to `--resume` or re-plan.
38
+
39
+ ## Read these first, in this order
40
+
41
+ Every workspace generates three docs. They are not filler.
42
+
43
+ | | |
44
+ | --- | --- |
45
+ | `AGENTS.md` | the editing rules for *this* workspace, including what its read path could and could not see |
46
+ | `MAPPING.md` | every field id → label, slug, type and phase. **You need this constantly**, because automations and conditions reference fields by bare numeric id |
47
+ | `CAPABILITIES.md` | what this endpoint can actually write, and the reason for every gap |
48
+ | `REFERENCE.md` | what the API accepts wherever a value comes from a fixed list — automation event and action parameters, field `type_id`, colours. **Read it before writing an automation**: those lists are published by the schema and enforced by nothing, so a wrong value is only ever reported as `is invalid` |
49
+
50
+ `pipe capabilities` prints the same registry with reasons. When a change is
51
+ refused, that is where the answer is.
52
+
53
+ ## The rules that are not style preferences
54
+
55
+ - **`id` is server-owned.** Never invent one, never edit one, never reuse one.
56
+ - **A new entity is `"id": null` plus `"_new": true`.** For phases, fields,
57
+ automations and labels — the only four arrays with one — also add a fresh v4
58
+ `uuid`. Everything else is identified by its owner.
59
+ - **To delete, remove the object or the file.** Never a tombstone.
60
+ - **`_`-prefixed keys are tool metadata.** Leave them alone.
61
+ - **Never edit under `_readonly/` or `_non-snapshot/`.** No write path exists;
62
+ plan refuses them by path alone.
63
+
64
+ ## Coverage: absence is not always absence
65
+
66
+ Every entity group carries `complete`, `partial` or `unknown` coverage in
67
+ `_meta.json`. An `unknown` group means the read path could not see it — not that
68
+ the pipe has none. Such a group is never diffed and never applied.
69
+
70
+ This matters most on a reconstructed pull (no snapshots): webhooks or automations
71
+ may be `unknown`, and adding to a list you cannot fully see risks duplicating what
72
+ is already there. Check `_meta.json` before concluding a pipe lacks something.
73
+
74
+ ## Where to go next
75
+
76
+ | Task | Skill |
77
+ | --- | --- |
78
+ | Editing phases, fields, automations, conditions, labels | `ppc-pipefy-pipe-authoring` |
79
+ | Editing or building an iPaaS (Advanced Automations) flow | `ppc-pipefy-flow-authoring` |
80
+ | Deciding *which* Pipefy mechanism a requirement needs | `ppc-pipefy-process-design` |
81
+
82
+ ## Two habits worth keeping
83
+
84
+ **Diff before you explain.** `pipe diff` on a real change costs ~150 tokens and
85
+ tells you exactly what you altered. Reading the whole pipe back to check your work
86
+ costs 50× that and is less reliable.
87
+
88
+ **Believe the refusal.** When `pipe plan` says a change has no write path, it is
89
+ because the mutation genuinely lacks that input — the registry records the reason
90
+ per column. Do not route around it with a delete and a create; that destroys data.
91
+ Say so plainly and stop.