@coldtea/pr-lens-agent-skill 0.1.5 → 0.3.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.
- package/SKILL.md +138 -13
- package/package.json +3 -3
- package/references/config.md +10 -2
- package/references/example.graph.json +106 -1
- package/references/graph-document.md +81 -3
package/SKILL.md
CHANGED
|
@@ -11,16 +11,18 @@ The diff or code is represented as one JSON document (lanes, nodes, edges, order
|
|
|
11
11
|
|
|
12
12
|
## Operating manual
|
|
13
13
|
|
|
14
|
+
Decide where the diagram lands before you write it: a canvas, or an SVG and a pull request comment. Only a canvas draws `payload`, the sample request and response on a flow step. A late decision costs another pass through steps 2 and 3.
|
|
15
|
+
|
|
14
16
|
1. **Read the diff.** When asked to represent a code change: `git diff --find-renames <base>...<head>`. The base is the merge base, not the tip of the base branch.
|
|
15
17
|
|
|
16
18
|
If not expressing a code diff, read the code to be visually represented
|
|
17
19
|
|
|
18
|
-
2. **Write the document** to `.pr-lens/graph.json`, following `references/graph-document.md`. `references/example.graph.json` is valid reference with three lanes, all four delta states, a hero edge, a seven-step flow, a nested drill-down tree. Read it before you write your first one. It is quicker than reading the reference.
|
|
20
|
+
2. **Write the document** to `.pr-lens/graph.json`, following `references/graph-document.md`. `references/example.graph.json` is valid reference with three lanes, all four delta states, a hero edge, a seven-step flow, a nested drill-down tree and a six-step walkthrough. Read it before you write your first one. It is quicker than reading the reference. If it is going to a canvas, give every flow step (`messages`) that moves data a `payload` as you write it. "Sample traffic on a flow step" below says what goes in one. Only a flow step carries one. Flows need the `data-flow` lens, so an architecture view draws none.
|
|
19
21
|
|
|
20
22
|
3. **Validate, and fix**
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
|
-
npx @coldtea/pr-lens-cli validate .pr-lens/graph.json
|
|
25
|
+
npx @coldtea/pr-lens-cli@latest validate .pr-lens/graph.json
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
Fix every failure and run it again. Do not render an invalid document; do not "work around" a failure by deleting the element it names.
|
|
@@ -28,15 +30,15 @@ The diff or code is represented as one JSON document (lanes, nodes, edges, order
|
|
|
28
30
|
4. **Render.**
|
|
29
31
|
|
|
30
32
|
```bash
|
|
31
|
-
npx @coldtea/pr-lens-cli render .pr-lens/graph.json --theme
|
|
33
|
+
npx @coldtea/pr-lens-cli@latest render .pr-lens/graph.json --theme light
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
Render
|
|
36
|
+
Render light by default unless the user requests another theme. The SVGs, the manifest and `drawn.graph.json` land in `.pr-lens/`, which the CLI adds to the repository's .gitignore. Do not commit any of it. These files are rebuilt from the diff whenever anyone wants them again. Each SVG is named after its view, the theme and a content hash; `manifest.json` lists them by lens and view, so read the names from there or from the directory.
|
|
35
37
|
|
|
36
38
|
If the user asked for a diagram, an explanation or a picture of the architecture and nothing more, put it on a canvas and hand back the link:
|
|
37
39
|
|
|
38
40
|
```bash
|
|
39
|
-
npx @coldtea/pr-lens-cli canvas push
|
|
41
|
+
npx @coldtea/pr-lens-cli@latest canvas push
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
This pushes `.pr-lens/drawn.graph.json` and prints three links. Give the user the view link, `https://prlens.dev/c/{id}`: that is the diagram, full screen, every view on one page, and it opens without a login. The edit link, the one ending in `#w=…`, lets its holder push over the canvas, so leave it out of the reply unless they ask, and never paste it anywhere public. The embed link serves the top view as an SVG for a README.
|
|
@@ -50,12 +52,12 @@ The diff or code is represented as one JSON document (lanes, nodes, edges, order
|
|
|
50
52
|
```markdown
|
|
51
53
|
Moves bulk sending off the per-recipient trigger and onto a batch endpoint.
|
|
52
54
|
|
|
53
|
-

|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
```bash
|
|
57
59
|
gh pr create --title "Batch broadcast sends" --body-file .pr-lens/body.md \
|
|
58
|
-
--attach .pr-lens/overview-
|
|
60
|
+
--attach .pr-lens/overview-light-4f9bd6c1.svg
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
On a pull request that already exists, `gh pr edit <number>` with the same two flags puts the diagram in the description, and `gh pr comment <number>` puts it in a comment. Repeat `--attach` for each diagram the body references.
|
|
@@ -70,7 +72,7 @@ The diff or code is represented as one JSON document (lanes, nodes, edges, order
|
|
|
70
72
|
When `--attach` is not an option, publish the SVGs somewhere durable and let the CLI compose the comment instead:
|
|
71
73
|
|
|
72
74
|
```bash
|
|
73
|
-
npx @coldtea/pr-lens-cli comment \
|
|
75
|
+
npx @coldtea/pr-lens-cli@latest comment \
|
|
74
76
|
--graph .pr-lens/drawn.graph.json \
|
|
75
77
|
--manifest .pr-lens/manifest.json \
|
|
76
78
|
--asset-base-url https://raw.githubusercontent.com/<owner>/<repo>/<branch>/<dir>
|
|
@@ -78,7 +80,7 @@ The diff or code is represented as one JSON document (lanes, nodes, edges, order
|
|
|
78
80
|
|
|
79
81
|
`--graph` takes `drawn.graph.json`, not the document you wrote, because corrections change what the diagrams show and the CLI refuses a document its manifest does not describe. `--asset-base-url` is where you published the SVGs; leave it out and the markdown points at local paths no reader can fetch. The markdown goes to stdout, with each diagram as a `<picture>` pair; posting it is your business.
|
|
80
82
|
|
|
81
|
-
If you would rather not author the document yourself, `npx @coldtea/pr-lens-cli analyze --base <ref>` does steps 1 and 2 by asking a provider — Gemini, OpenAI, or any endpoint speaking `/chat/completions` — with a key of your own. That is the only path here that needs one.
|
|
83
|
+
If you would rather not author the document yourself, `npx @coldtea/pr-lens-cli@latest analyze --base <ref>` does steps 1 and 2 by asking a provider — Gemini, OpenAI, or any endpoint speaking `/chat/completions` — with a key of your own. That is the only path here that needs one.
|
|
82
84
|
|
|
83
85
|
## The pull request body, when there is one
|
|
84
86
|
|
|
@@ -101,25 +103,148 @@ Every child moves down one level and covers a materially narrower scope. Skip em
|
|
|
101
103
|
|
|
102
104
|
Keep data-flow views as separate roots rather than nesting them in the architecture tree. Set `defaultOpen: true` on the highest useful architecture view. Lower levels should normally keep the default, `false`.
|
|
103
105
|
|
|
106
|
+
## Writing a walkthrough
|
|
107
|
+
|
|
108
|
+
A walkthrough is a short guided tour of the diagrams. It has two to twelve steps. Each step shows one diagram, points at one part of it, and says a few words about it. A canvas plays it, and the reader scrolls through it.
|
|
109
|
+
|
|
110
|
+
The contract leaves a walkthrough optional. Write one anyway for anything that is not trivial: more than one diagram, a diagram with several changed parts, or any flow. Skip it only when the document is one small diagram whose single step would just repeat the title.
|
|
111
|
+
|
|
112
|
+
Aim for three to seven steps.
|
|
113
|
+
|
|
114
|
+
A walkthrough is the fastest read of a pull request. Each step is one change: something added, changed, removed or moved, in the order a reviewer needs it. A step is never a description of the diagram.
|
|
115
|
+
|
|
116
|
+
What counts as a step: a behaviour change, an API change, an architecture change, a data-flow change, or an addition. Unchanged parts appear only where a step needs them to make sense. The headline change is step one. An overview of everything touched, if there is one, is the last step.
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
"walkthrough": {
|
|
120
|
+
"steps": [
|
|
121
|
+
{
|
|
122
|
+
"id": "four-batch-calls",
|
|
123
|
+
"heading": "Postmark now gets 500 emails per call",
|
|
124
|
+
"body": "One call per batch, and Postmark answers with a result for each message.",
|
|
125
|
+
"stage": { "kind": "flow", "flow": "send-pipeline" },
|
|
126
|
+
"focus": { "kind": "selection", "messages": ["batch-post", "batch-results"] }
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"id": "blast-radius",
|
|
130
|
+
"heading": "4 parts added, 2 removed, across 3 lanes",
|
|
131
|
+
"body": "A 2,000-person broadcast used to make 2,000 calls to Postmark. It now makes 4.",
|
|
132
|
+
"stage": { "kind": "view", "view": "overview" }
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Each step has:
|
|
139
|
+
|
|
140
|
+
- `heading`: the thing and what happened to it, up to 48 characters, in sentence case. Build it from change words: added, removed, replaced, now, moved, split. If a heading could have been true before the pull request, it is not a change heading.
|
|
141
|
+
- `body`: one line under the heading, up to 140 characters, on what the change means for behaviour: what happens now that did not before, or what stops happening, with the numbers when they matter. Not a restatement of the heading, and not a description of the code. A heading with no body reads as unfinished, so the body is required.
|
|
142
|
+
- `stage`: which diagram to show. A document can have several diagrams: its views (the drill-down diagrams) and its flows (the sequence diagrams). `{ "kind": "view", "view": "overview" }` shows the view called `overview`. `{ "kind": "flow", "flow": "send-pipeline" }` shows the flow called `send-pipeline`. Leave `stage` out and the step uses the diagram the reader is already on. Open on the widest view with the focus left out, so the reader sees the whole thing before it narrows.
|
|
143
|
+
- `focus`: what to zoom in on inside that diagram. `{ "kind": "all" }`, the default, means the whole diagram. A selection means "just these things": name any lanes, nodes, edges or flow steps (`messages`) by id, and the camera zooms to them while everything else dims. Focus the elements the step's change touched, so the veil lights the change. Point at two or three of them. A step that lights half the diagram has not said anything.
|
|
144
|
+
|
|
145
|
+
Write every word for a smart twelve-year-old: short common words, one idea per line, active voice, things named as the diagram names them, numbers as digits. If a line needs a second read, rewrite it. Words like leverages, orchestrates, asynchronous pipeline and fan-out never belong in a step. This holds in whatever language the document is written in.
|
|
146
|
+
|
|
147
|
+
The same three steps, written well and written badly. Heading first, then the body after the slash:
|
|
148
|
+
|
|
149
|
+
| Write this | Not this |
|
|
150
|
+
| -------- | -------- |
|
|
151
|
+
| Route now queues the job instead of sending / The API call finishes at once. A worker sends the mail later. | Broadcast fan-out moves behind the queue / The API route now enqueues broadcast jobs for asynchronous batch processing instead of sending emails inline. |
|
|
152
|
+
| Postmark now gets 500 emails per call / One call per batch instead of one call per person. | Batched delivery replaces single sends / The worker leverages the shared library to send emails in chunks of 500 via Postmark's batch endpoint. |
|
|
153
|
+
| processBroadcast and sendSingleEmail removed / sendBroadcastBulk does their job for whole batches. | Single send functions are retired / sendBroadcastBulk replaces processBroadcast and sendSingleEmail to handle bulk deliveries in chunks. |
|
|
154
|
+
|
|
155
|
+
Keep consecutive steps on the same stage together. Every change of stage flies the camera across the canvas, so a tour that alternates between two diagrams spends its time travelling.
|
|
156
|
+
|
|
157
|
+
The validator checks:
|
|
158
|
+
|
|
159
|
+
- Every id you name exists in the document. A flow step you name must belong to the flow the stage shows, because flow step ids are only unique inside their own flow.
|
|
160
|
+
- `messages` needs a stage that shows a flow. Leave it out when the stage is an architecture view.
|
|
161
|
+
- Step ids are unique within the walkthrough. Two steps minimum, twelve maximum.
|
|
162
|
+
- A stored map never carries a walkthrough. A map describes the system; a walkthrough tells the story of one change.
|
|
163
|
+
|
|
164
|
+
The field arrived with contract 0.1.1. A CLI older than 0.4.0 does not know it and rejects the whole document as an invented field, so validate with a current one.
|
|
165
|
+
|
|
166
|
+
## Sample traffic on a flow step
|
|
167
|
+
|
|
168
|
+
A flow step can carry a `payload`: what travels on it. Only the canvas draws it, in the rail that opens when a reader clicks a step. Nothing in an SVG or a pull request comment changes. Write it when the document is going to a canvas (step 4, `canvas push`) and leave it out otherwise. Six payloads on the reference document add half its length again, so this is not a field to fill by default.
|
|
169
|
+
|
|
170
|
+
On a canvas document, add it to a step that moves data: a request body, a job record, a query, a result. Leave it off a step that only signals, such as a trigger with nothing attached.
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"id": "batch-post",
|
|
175
|
+
"from": "send-broadcast-bulk",
|
|
176
|
+
"to": "postmark",
|
|
177
|
+
"label": "POST /email/batch",
|
|
178
|
+
"kind": "sync",
|
|
179
|
+
"delta": "added",
|
|
180
|
+
"repeat": 4,
|
|
181
|
+
"payload": {
|
|
182
|
+
"request": {
|
|
183
|
+
"type": "EmailBatch[500]",
|
|
184
|
+
"shape": "Email[] // max 500\nEmail = { From: string; To: string; Subject: string; HtmlBody: string; MessageStream: \"broadcast\"; Metadata: { campaignId: string; batchId: string } }",
|
|
185
|
+
"sample": [
|
|
186
|
+
{
|
|
187
|
+
"From": "news@example.com",
|
|
188
|
+
"To": "ada@example.com",
|
|
189
|
+
"Subject": "The batching issue, fixed",
|
|
190
|
+
"HtmlBody": "<!doctype html><html><body>…",
|
|
191
|
+
"MessageStream": "broadcast",
|
|
192
|
+
"Metadata": { "campaignId": "cmp_0001", "batchId": "b_0001" }
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
"before": [
|
|
196
|
+
{
|
|
197
|
+
"From": "news@example.com",
|
|
198
|
+
"To": "ada@example.com",
|
|
199
|
+
"Subject": "The batching issue, fixed",
|
|
200
|
+
"HtmlBody": "<!doctype html><html><body>…",
|
|
201
|
+
"Metadata": { "campaignId": "cmp_0001" }
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"source": { "path": "tests/fixtures/postmark-batch.json" }
|
|
205
|
+
},
|
|
206
|
+
"response": {
|
|
207
|
+
"type": "BatchResult[500]",
|
|
208
|
+
"shape": "SendResult[] // one per Email, same order",
|
|
209
|
+
"sample": [{ "ErrorCode": 0, "Message": "OK", "To": "ada@example.com", "MessageID": "b7fa5c1e-…" }]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A payload has a `request` side, a `response` side, or both. Each side has:
|
|
216
|
+
|
|
217
|
+
- `type`: the name a reader of the code would recognise. Put the count in it when the step carries a collection: `EmailBatch[500]`, not `EmailBatch`. Write `{ "type": "void" }` for a side that carries nothing, such as the answer to a fire and forget call.
|
|
218
|
+
- `shape`: the type signature as text, taken from the code's own types. Up to 2048 bytes.
|
|
219
|
+
- `sample`: one exemplar instance after the change, written inline as JSON. It is a JSON value, not a JSON string: `"sample": [{ "To": "ada@example.com" }]`, never `"sample": "[{\"To\": ...}]"`. A string here is rejected. Every key once, one element in any array, long strings cut with an ellipsis. At most 8 levels deep and 4096 bytes once serialised. The parser refuses a sample over either cap rather than trimming it.
|
|
220
|
+
- `before`: the same exemplar as it was before the change, when it differs. Same rules as `sample`, and it needs a `sample` to differ from.
|
|
221
|
+
- `source`: the fixture or type the shape and sample came from, as a file reference. It becomes the permalink.
|
|
222
|
+
|
|
223
|
+
Use placeholder values: `ada@example.com`, `cmp_0001`. Never copy a value from a fixture that could belong to a real person or unlock something, even in test data.
|
|
224
|
+
|
|
225
|
+
Do not write `changedPaths`. The paths that differ between `before` and `sample` are worked out when the document is stored. A list you write is discarded.
|
|
226
|
+
|
|
227
|
+
The field arrived with contract 0.2.0. A CLI built before it rejects the whole document as an invented field, so validate with a current one.
|
|
228
|
+
|
|
104
229
|
## What the validator will catch
|
|
105
230
|
|
|
106
231
|
Read `references/graph-document.md` before writing. The four failures that account for nearly everything:
|
|
107
232
|
|
|
108
233
|
| Code | What you did |
|
|
109
234
|
| ---------------------------- | -------------------------------------------------------------------- |
|
|
110
|
-
| `BROKEN_REFERENCE` | an edge, a flow step or a
|
|
235
|
+
| `BROKEN_REFERENCE` | an edge, a flow step, a view or a walkthrough step names an id you never declared |
|
|
111
236
|
| `INVALID_DOCUMENT` | an invented field; the schemas are strict, unknown keys are rejected |
|
|
112
237
|
| `DUPLICATE_ID` | two nodes, edges or views sharing an id |
|
|
113
238
|
| `UNSUPPORTED_SCHEMA_VERSION` | `schemaVersion` is not the contract version installed |
|
|
114
239
|
|
|
115
|
-
|
|
240
|
+
Seven rules cannot be expressed in JSON Schema and are checked only by the parser, so structured output alone does not make a document valid: referential integrity, a line range that ends before it starts, a `self` message whose endpoints disagree, a patch whose two commits are the same, more views than a render manifest could describe, a walkthrough step focusing flow steps the diagram on its stage does not draw, and sample traffic past its depth or byte caps. Always validate.
|
|
116
241
|
|
|
117
242
|
## Fixing a map instead of writing one
|
|
118
243
|
|
|
119
244
|
When someone says the diagram is wrong (a node is misnamed, a folder should not be on it, something sits in the wrong lane), do not edit the generated document. It is regenerated on every run. Write the correction into `.github/pr-lens.yml`, which is an overlay applied over fresh inference every time:
|
|
120
245
|
|
|
121
246
|
```yaml
|
|
122
|
-
schemaVersion: 0.
|
|
247
|
+
schemaVersion: 0.2.0
|
|
123
248
|
map:
|
|
124
249
|
rename:
|
|
125
250
|
- match: functions/src/broadcast/sendBroadcastBulk.ts
|
|
@@ -131,7 +256,7 @@ map:
|
|
|
131
256
|
lane: functions
|
|
132
257
|
```
|
|
133
258
|
|
|
134
|
-
`references/config.md` has the full format and the recipes. Validate it the same way: `npx @coldtea/pr-lens-cli validate .github/pr-lens.yml`.
|
|
259
|
+
`references/config.md` has the full format and the recipes. Validate it the same way: `npx @coldtea/pr-lens-cli@latest validate .github/pr-lens.yml`.
|
|
135
260
|
|
|
136
261
|
A `match` beginning with `id:` addresses one node exactly; anything else is a path glob matched against a node's file paths. Prefer the glob, because it keeps holding when the next run names the node differently. A lane pin may name a lane the document never declared: the band is created, and takes the id for its label, so give it one a reader would want to see.
|
|
137
262
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldtea/pr-lens-agent-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The PR Lens skill for coding agents: author a graph document from a diff, validate it, render it, and correct a repository's map.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Coldtea AI",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"typescript": "7.0.2",
|
|
35
35
|
"vitest": "4.1.11",
|
|
36
36
|
"yaml": "^2.8.1",
|
|
37
|
-
"@coldtea/pr-lens-
|
|
38
|
-
"@coldtea/pr-lens-
|
|
37
|
+
"@coldtea/pr-lens-schema": "^0.3.0",
|
|
38
|
+
"@coldtea/pr-lens-renderer": "^0.2.5"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"skill:sync": "tsx scripts/sync.ts",
|
package/references/config.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The generated document is regenerated on every run, so editing it is pointless. Corrections live in `.github/pr-lens.yml`, an overlay applied over fresh inference every time. Inference never writes back into this file, which is why a correction keeps holding as the code moves.
|
|
4
4
|
|
|
5
5
|
```yaml
|
|
6
|
-
schemaVersion: 0.
|
|
6
|
+
schemaVersion: 0.2.0 # required
|
|
7
7
|
lenses: [architecture, data-flow]
|
|
8
8
|
branding: true
|
|
9
9
|
map:
|
|
@@ -81,10 +81,18 @@ map:
|
|
|
81
81
|
lenses: [architecture]
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
## Hosted GitHub App comments
|
|
85
|
+
|
|
86
|
+
The hosted App reads `github` settings from the PR's head commit. Other options apply to the CLI.
|
|
87
|
+
|
|
88
|
+
| Setting | Default | Effect |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `github.comment.collapsed` | `false` | Start diagrams and details closed. Drawing still runs automatically. |
|
|
91
|
+
|
|
84
92
|
## Check it
|
|
85
93
|
|
|
86
94
|
```bash
|
|
87
|
-
npx @coldtea/pr-lens-cli validate .github/pr-lens.yml
|
|
95
|
+
npx @coldtea/pr-lens-cli@latest validate .github/pr-lens.yml
|
|
88
96
|
```
|
|
89
97
|
|
|
90
98
|
`pr-lens render` reports any correction that changed nothing about the document it drew. That is a config that has drifted out of date, usually because the file a selector named has moved or gone. It is not an error and nothing stops, but it is worth fixing: a correction that matches nothing is a correction nobody is getting.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion": "0.
|
|
2
|
+
"schemaVersion": "0.2.0",
|
|
3
3
|
"kind": "graph",
|
|
4
4
|
"generatedAt": "2026-08-19T18:24:00.000Z",
|
|
5
5
|
"title": "Batch broadcast sending through Postmark",
|
|
@@ -564,6 +564,111 @@
|
|
|
564
564
|
"children": []
|
|
565
565
|
}
|
|
566
566
|
],
|
|
567
|
+
"walkthrough": {
|
|
568
|
+
"steps": [
|
|
569
|
+
{
|
|
570
|
+
"id": "batches-of-500",
|
|
571
|
+
"heading": "sendBroadcastBulk and buildBulkPayload added",
|
|
572
|
+
"body": "Nothing loops over recipients any more. The sender works on a whole batch at a time.",
|
|
573
|
+
"stage": {
|
|
574
|
+
"kind": "view",
|
|
575
|
+
"view": "overview"
|
|
576
|
+
},
|
|
577
|
+
"focus": {
|
|
578
|
+
"kind": "selection",
|
|
579
|
+
"lanes": [],
|
|
580
|
+
"nodes": [
|
|
581
|
+
"send-broadcast-bulk",
|
|
582
|
+
"build-bulk-payload",
|
|
583
|
+
"postmark"
|
|
584
|
+
],
|
|
585
|
+
"edges": [],
|
|
586
|
+
"messages": []
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
"id": "suppression-first",
|
|
591
|
+
"heading": "getSuppressedEmails added before the send",
|
|
592
|
+
"body": "It pulls the blocked addresses once, before any batch is built.",
|
|
593
|
+
"stage": {
|
|
594
|
+
"kind": "view",
|
|
595
|
+
"view": "new-batch-path"
|
|
596
|
+
},
|
|
597
|
+
"focus": {
|
|
598
|
+
"kind": "selection",
|
|
599
|
+
"lanes": [],
|
|
600
|
+
"nodes": [
|
|
601
|
+
"get-suppressed-emails",
|
|
602
|
+
"postmark"
|
|
603
|
+
],
|
|
604
|
+
"edges": [],
|
|
605
|
+
"messages": []
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
"id": "old-path-goes-dark",
|
|
610
|
+
"heading": "processBroadcast and sendSingleEmail removed",
|
|
611
|
+
"body": "sendBroadcastBulk does their job for whole batches.",
|
|
612
|
+
"stage": {
|
|
613
|
+
"kind": "view",
|
|
614
|
+
"view": "overview"
|
|
615
|
+
},
|
|
616
|
+
"focus": {
|
|
617
|
+
"kind": "selection",
|
|
618
|
+
"lanes": [],
|
|
619
|
+
"nodes": [
|
|
620
|
+
"process-broadcast",
|
|
621
|
+
"send-single-email"
|
|
622
|
+
],
|
|
623
|
+
"edges": [],
|
|
624
|
+
"messages": []
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
"id": "sequence-start-to-finish",
|
|
629
|
+
"heading": "The send sequence gained 6 new steps",
|
|
630
|
+
"body": "The queue write is the only step that was there before, and it now stamps the batch size.",
|
|
631
|
+
"stage": {
|
|
632
|
+
"kind": "flow",
|
|
633
|
+
"flow": "send-pipeline"
|
|
634
|
+
},
|
|
635
|
+
"focus": {
|
|
636
|
+
"kind": "all"
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
"id": "four-batch-calls",
|
|
641
|
+
"heading": "Postmark now gets 500 emails per call",
|
|
642
|
+
"body": "One call per batch, and Postmark answers with a result for each message.",
|
|
643
|
+
"stage": {
|
|
644
|
+
"kind": "flow",
|
|
645
|
+
"flow": "send-pipeline"
|
|
646
|
+
},
|
|
647
|
+
"focus": {
|
|
648
|
+
"kind": "selection",
|
|
649
|
+
"lanes": [],
|
|
650
|
+
"nodes": [],
|
|
651
|
+
"edges": [],
|
|
652
|
+
"messages": [
|
|
653
|
+
"batch-post",
|
|
654
|
+
"batch-results"
|
|
655
|
+
]
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
"id": "blast-radius",
|
|
660
|
+
"heading": "4 parts added, 2 removed, across 3 lanes",
|
|
661
|
+
"body": "A 2,000-person broadcast used to make 2,000 calls to Postmark. It now makes 4.",
|
|
662
|
+
"stage": {
|
|
663
|
+
"kind": "view",
|
|
664
|
+
"view": "overview"
|
|
665
|
+
},
|
|
666
|
+
"focus": {
|
|
667
|
+
"kind": "all"
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
]
|
|
671
|
+
},
|
|
567
672
|
"layout": {
|
|
568
673
|
"direction": "right",
|
|
569
674
|
"laneOrder": [
|
|
@@ -10,7 +10,7 @@ Every schema here is **strict**: an unknown key is a rejection, not a warning. A
|
|
|
10
10
|
|
|
11
11
|
```json
|
|
12
12
|
{
|
|
13
|
-
"schemaVersion": "0.
|
|
13
|
+
"schemaVersion": "0.2.0",
|
|
14
14
|
"kind": "graph",
|
|
15
15
|
"title": "Batch broadcast sending through Postmark",
|
|
16
16
|
"summary": "One paragraph answering: what does this change do?",
|
|
@@ -114,6 +114,39 @@ Up to 16, for the data-flow lens.
|
|
|
114
114
|
- `kind` is `sync`, `async`, `return` or `self`. `self` requires `from === to`, and no other kind may have them equal.
|
|
115
115
|
- Both endpoints must be participants of that flow, not merely nodes of the document.
|
|
116
116
|
- `repeat` says a step happens more than once per run, e.g. 4 batched requests.
|
|
117
|
+
- `payload`: what travels on the step. Optional. Only a canvas draws it, so leave it out of a document that is not going to one.
|
|
118
|
+
|
|
119
|
+
### Sample traffic
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"id": "send",
|
|
124
|
+
"from": "send-broadcast-bulk",
|
|
125
|
+
"to": "postmark",
|
|
126
|
+
"label": "POST /email/bulk",
|
|
127
|
+
"kind": "sync",
|
|
128
|
+
"delta": "added",
|
|
129
|
+
"payload": {
|
|
130
|
+
"request": {
|
|
131
|
+
"type": "EmailBatch[500]",
|
|
132
|
+
"shape": "Email[] // max 500\nEmail = { From: string; To: string; Subject: string }",
|
|
133
|
+
"sample": [{ "From": "news@example.com", "To": "ada@example.com", "Subject": "The batching issue, fixed" }],
|
|
134
|
+
"before": [{ "From": "news@example.com", "To": "ada@example.com", "Cc": "ops@example.com", "Subject": "The batching issue, fixed" }],
|
|
135
|
+
"source": { "path": "tests/fixtures/postmark-batch.json" }
|
|
136
|
+
},
|
|
137
|
+
"response": { "type": "void" }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- A payload has `request`, `response` or both. One with neither is rejected.
|
|
143
|
+
- `type` is required on a side: a name a reader of the code would know, with the count in it for a collection (`EmailBatch[500]`). `void` for a side that carries nothing.
|
|
144
|
+
- `shape` is the type signature as text, up to 2048 bytes.
|
|
145
|
+
- `sample` and `before` are JSON values written inline, not JSON strings. A string where a value belongs is rejected. Each is at most 8 levels deep and 4096 bytes once serialised. The parser refuses a value over either cap rather than truncating it.
|
|
146
|
+
- `before` needs a `sample` to differ from.
|
|
147
|
+
- `source` is a file reference, the fixture or type the side was taken from.
|
|
148
|
+
- `changedPaths` is filled in when the document is stored, from `before` and `sample`. Do not write it. Up to 64 paths of the form `Metadata.batchId`, `[0].Cc` or `headers["Content-Type"]`.
|
|
149
|
+
- Use placeholder values in samples: `ada@example.com`, `cmp_0001`. Never one that could belong to a real person or unlock anything.
|
|
117
150
|
|
|
118
151
|
## Stats
|
|
119
152
|
|
|
@@ -204,6 +237,51 @@ This compact fragment shows the shape. The selected ids refer to elements declar
|
|
|
204
237
|
}
|
|
205
238
|
```
|
|
206
239
|
|
|
240
|
+
## Walkthrough
|
|
241
|
+
|
|
242
|
+
Optional in the format, but write one for anything that is not trivial: more than one diagram, a diagram with several changed parts, or any flow. Skip it only when the document is one small diagram whose single step would just repeat the title. A canvas or a share page plays it.
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"walkthrough": {
|
|
247
|
+
"steps": [
|
|
248
|
+
{
|
|
249
|
+
"id": "four-batch-calls",
|
|
250
|
+
"heading": "Postmark now gets 500 emails per call",
|
|
251
|
+
"body": "One call per batch, and Postmark answers with a result for each message.",
|
|
252
|
+
"stage": { "kind": "flow", "flow": "send-pipeline" },
|
|
253
|
+
"focus": { "kind": "selection", "messages": ["batch-post", "batch-results"] }
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"id": "blast-radius",
|
|
257
|
+
"heading": "4 parts added, 2 removed, across 3 lanes",
|
|
258
|
+
"body": "A 2,000-person broadcast used to make 2,000 calls to Postmark. It now makes 4.",
|
|
259
|
+
"stage": { "kind": "view", "view": "overview" },
|
|
260
|
+
"focus": { "kind": "all" }
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
A walkthrough is a short guided tour of the diagrams. It has two to twelve steps. Each step shows one diagram, points at one part of it, and says a few words about it.
|
|
268
|
+
|
|
269
|
+
Every step is one change, never a description of the diagram: the heading names the thing and what happened to it, built from change words such as added, removed, replaced, now, moved and split, and the body is one line on what that means for behaviour, with the numbers when they matter. The headline change is step one. Write it all for a smart twelve-year-old, in short common words and active voice. The skill page has the rule in full, with examples of a step written well and the same step written badly.
|
|
270
|
+
|
|
271
|
+
Each step has:
|
|
272
|
+
|
|
273
|
+
- `heading`: the thing and what happened to it, up to 48 characters, in sentence case. For example "Postmark now gets 500 emails per call".
|
|
274
|
+
- `body`: one line under the heading, up to 140 characters, on what the change means for behaviour. For example "One call per batch instead of one call per person". Required: a heading with no body reads as unfinished.
|
|
275
|
+
- `stage`: which diagram to show. A document can have several diagrams: its views (the drill-down diagrams) and its flows (the sequence diagrams). `{ "kind": "view", "view": "overview" }` shows the view called `overview`. `{ "kind": "flow", "flow": "send-pipeline" }` shows the flow called `send-pipeline`. Leave `stage` out and the step uses the diagram the reader is already on.
|
|
276
|
+
- `focus`: what to zoom in on inside that diagram. `{ "kind": "all" }`, the default, means the whole diagram. A selection means "just these things": name any lanes, nodes, edges or flow steps (`messages`) by id, and the camera zooms to them while everything else dims. A selection must name at least one thing.
|
|
277
|
+
|
|
278
|
+
The validator checks:
|
|
279
|
+
|
|
280
|
+
- Every id you name exists in the document. A flow step you name must belong to the flow the stage shows, because flow step ids are only unique inside their own flow.
|
|
281
|
+
- `messages` needs a stage that shows a flow. Leave it out when the stage is an architecture view.
|
|
282
|
+
- Step ids are unique within the walkthrough. Two steps minimum, twelve maximum.
|
|
283
|
+
- A stored map never carries a walkthrough. A map describes the system; a walkthrough tells the story of one change.
|
|
284
|
+
|
|
207
285
|
## Layout
|
|
208
286
|
|
|
209
287
|
```json
|
|
@@ -222,12 +300,12 @@ Repository-relative POSIX paths: no leading `/`, no drive letter, no backslash,
|
|
|
222
300
|
|
|
223
301
|
## Length limits
|
|
224
302
|
|
|
225
|
-
Labels 120 characters, summaries 2000, chip values 32. They are display fields: a label that needs 120 characters is a label the diagram cannot draw.
|
|
303
|
+
Labels 120 characters, summaries 2000, chip values 32. They are display fields: a label that needs 120 characters is a label the diagram cannot draw. On a payload side, `shape` 2048 bytes, `sample` and `before` 4096 bytes each once serialised and 8 levels deep, `changedPaths` 64 entries.
|
|
226
304
|
|
|
227
305
|
## Then validate
|
|
228
306
|
|
|
229
307
|
```bash
|
|
230
|
-
npx @coldtea/pr-lens-cli validate .pr-lens/graph.json
|
|
308
|
+
npx @coldtea/pr-lens-cli@latest validate .pr-lens/graph.json
|
|
231
309
|
```
|
|
232
310
|
|
|
233
311
|
Every problem is reported at once, with a path into the document. Fix them all and run it again until it is clean.
|