@coldtea/pr-lens-agent-skill 0.1.1 → 0.1.2
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/LICENSE +1 -1
- package/README.md +1 -1
- package/SKILL.md +25 -14
- package/package.json +4 -4
- package/references/config.md +2 -2
- package/references/example.graph.json +580 -0
- package/references/graph-document.md +4 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The PR Lens skill for coding agents. It teaches an agent to draw the change it just made: author a graph document from the diff, validate it against the contract, render it, attach it to the pull request, and to fix a repository's map by writing corrections rather than editing generated output.
|
|
4
4
|
|
|
5
|
-
MIT ©
|
|
5
|
+
MIT © Coldtea AI.
|
|
6
6
|
|
|
7
7
|
## Install it
|
|
8
8
|
|
package/SKILL.md
CHANGED
|
@@ -7,25 +7,34 @@ description: Draw a code change as an architecture or data-flow diagram with PR
|
|
|
7
7
|
|
|
8
8
|
PR Lens turns a diff into one JSON document (lanes, nodes, edges, ordered flows) and renders it as an animated SVG that reads inside a GitHub pull request comment. The document is the whole contract: if it validates, it renders.
|
|
9
9
|
|
|
10
|
-
You are usually the model. Rather than calling out to a provider, read the diff and write the document yourself, then let the tooling check it.
|
|
10
|
+
You are usually the model. Rather than calling out to a provider, read the diff and write the document yourself, then let the tooling check it. Nothing on this path needs a model key or names a model: whichever one you are running is the one doing the reasoning.
|
|
11
11
|
|
|
12
12
|
## The loop
|
|
13
13
|
|
|
14
14
|
1. **Read the diff.** `git diff --find-renames <base>...<head>`. The base is the merge base, not the tip of the base branch.
|
|
15
|
-
2. **Write the document** to
|
|
16
|
-
`node_modules/@coldtea/pr-lens-schema/json-schema/graph-doc.schema.json`
|
|
15
|
+
2. **Write the document** to `.pr-lens/graph.json`, following `references/graph-document.md`. That page is the whole shape: every field, every enum, every limit, and the four rules a JSON Schema cannot express. `references/example.graph.json` is a document that validates — 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.
|
|
17
16
|
3. **Validate, and fix what it names.**
|
|
18
17
|
```bash
|
|
19
|
-
npx @coldtea/pr-lens-cli validate pr-lens/graph.json
|
|
18
|
+
npx @coldtea/pr-lens-cli validate .pr-lens/graph.json
|
|
20
19
|
```
|
|
21
20
|
Each failure is a path into your document, a reason and a code. Fix every one and run it again. Do not render an invalid document; do not "work around" a failure by deleting the element it names.
|
|
22
|
-
4. **Render
|
|
21
|
+
4. **Render.**
|
|
23
22
|
```bash
|
|
24
|
-
npx @coldtea/pr-lens-cli render pr-lens/graph.json
|
|
23
|
+
npx @coldtea/pr-lens-cli render .pr-lens/graph.json
|
|
25
24
|
```
|
|
26
|
-
|
|
25
|
+
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 a preview, rebuilt from the diff whenever anyone wants them again; the comment on the pull request is the thing you are making.
|
|
26
|
+
5. **Attach.** Put the SVGs somewhere durable — an orphan branch, a release asset — then compose the comment from the document that was drawn:
|
|
27
|
+
```bash
|
|
28
|
+
npx @coldtea/pr-lens-cli comment \
|
|
29
|
+
--graph .pr-lens/drawn.graph.json \
|
|
30
|
+
--manifest .pr-lens/manifest.json \
|
|
31
|
+
--asset-base-url https://raw.githubusercontent.com/<owner>/<repo>/<branch>/<dir>
|
|
32
|
+
```
|
|
33
|
+
The markdown goes to stdout, with each diagram as a `<picture>` pair so it reads in both GitHub themes. Posting it is your business.
|
|
34
|
+
|
|
35
|
+
Both flags matter. `--graph` takes `drawn.graph.json`, not the document you wrote: 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.
|
|
27
36
|
|
|
28
|
-
If
|
|
37
|
+
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.
|
|
29
38
|
|
|
30
39
|
## What makes a document worth reading
|
|
31
40
|
|
|
@@ -72,12 +81,14 @@ A `match` beginning with `id:` addresses one node exactly; anything else is a pa
|
|
|
72
81
|
|
|
73
82
|
`pr-lens render` says so when a correction matched nothing, which is how a config that has drifted, because the file it named moved or was deleted, becomes visible instead of quietly doing nothing.
|
|
74
83
|
|
|
75
|
-
##
|
|
84
|
+
## What ships with this skill
|
|
76
85
|
|
|
77
|
-
|
|
86
|
+
Everything you need is beside this page. Nothing here asks you to install a package first.
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
| | |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `references/graph-document.md` | the document, field by field: enums, limits, and where documents actually go wrong |
|
|
91
|
+
| `references/config.md` | `.github/pr-lens.yml`, the correction overlay, in full |
|
|
92
|
+
| `references/example.graph.json` | one complete document that validates, to read and to copy the shape of |
|
|
82
93
|
|
|
83
|
-
|
|
94
|
+
The same document ships as `postmark-refactor.graph.json` in `@coldtea/pr-lens-schema`, and the JSON Schema the validator enforces is published at `https://unpkg.com/@coldtea/pr-lens-schema/json-schema/graph-doc.schema.json`. Neither is something you need to fetch to write a document.
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldtea/pr-lens-agent-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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
|
-
"author": "
|
|
6
|
+
"author": "Coldtea AI",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/coldteadotai/pr-lens.git",
|
|
@@ -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-renderer": "^0.1.
|
|
38
|
-
"@coldtea/pr-lens-schema": "^0.1.
|
|
37
|
+
"@coldtea/pr-lens-renderer": "^0.1.2",
|
|
38
|
+
"@coldtea/pr-lens-schema": "^0.1.2"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"skill:sync": "tsx scripts/sync.ts",
|
package/references/config.md
CHANGED
|
@@ -21,10 +21,10 @@ map:
|
|
|
21
21
|
group: broadcast-lib
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Every field except `schemaVersion` is optional, and the file itself is optional.
|
|
24
|
+
Every field except `schemaVersion` is optional, and the file itself is optional. For editor autocomplete, point at the published JSON Schema — no install needed:
|
|
25
25
|
|
|
26
26
|
```jsonc
|
|
27
|
-
{ "$ref": "
|
|
27
|
+
{ "$ref": "https://unpkg.com/@coldtea/pr-lens-schema/json-schema/config.schema.json" }
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## Selectors
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "0.1.0",
|
|
3
|
+
"kind": "graph",
|
|
4
|
+
"generatedAt": "2026-08-19T18:24:00.000Z",
|
|
5
|
+
"title": "Batch broadcast sending through Postmark",
|
|
6
|
+
"summary": "Broadcast delivery moves from one Postmark request per recipient to batched requests of 500, with suppression filtering pulled in front of the send and the payload builder extracted into a shared library.",
|
|
7
|
+
"lenses": [
|
|
8
|
+
"architecture",
|
|
9
|
+
"data-flow"
|
|
10
|
+
],
|
|
11
|
+
"provenance": {
|
|
12
|
+
"repo": {
|
|
13
|
+
"owner": "ohansemmanuel",
|
|
14
|
+
"name": "bestregards",
|
|
15
|
+
"host": "github.com"
|
|
16
|
+
},
|
|
17
|
+
"base": {
|
|
18
|
+
"sha": "3f5c1ab9d24e7f08c6b1a5d3e9074c2b8a6f1d40",
|
|
19
|
+
"ref": "main"
|
|
20
|
+
},
|
|
21
|
+
"head": {
|
|
22
|
+
"sha": "b71e0d4c8a92f5361de7c0b4a8f2593d6c1e8a77",
|
|
23
|
+
"ref": "batch-broadcast-send"
|
|
24
|
+
},
|
|
25
|
+
"pullRequest": {
|
|
26
|
+
"number": 128,
|
|
27
|
+
"title": "Send broadcasts in batches of 500",
|
|
28
|
+
"url": "https://github.com/ohansemmanuel/bestregards/pull/128"
|
|
29
|
+
},
|
|
30
|
+
"generator": {
|
|
31
|
+
"name": "pr-lens-examples",
|
|
32
|
+
"version": "0.1.0"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"lanes": [
|
|
36
|
+
{
|
|
37
|
+
"id": "web",
|
|
38
|
+
"label": "Next.js",
|
|
39
|
+
"subtitle": "Vercel",
|
|
40
|
+
"order": 0
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "functions",
|
|
44
|
+
"label": "Cloud Functions",
|
|
45
|
+
"subtitle": "Firebase",
|
|
46
|
+
"order": 1
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "external",
|
|
50
|
+
"label": "External",
|
|
51
|
+
"subtitle": "Postmark",
|
|
52
|
+
"order": 2
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"nodes": [
|
|
56
|
+
{
|
|
57
|
+
"id": "broadcast-composer",
|
|
58
|
+
"label": "Broadcast composer",
|
|
59
|
+
"kind": "ui",
|
|
60
|
+
"delta": "unchanged",
|
|
61
|
+
"lane": "web",
|
|
62
|
+
"subtitle": "app/broadcasts/new",
|
|
63
|
+
"summary": "Where an author writes a broadcast and hits send. Untouched by this change.",
|
|
64
|
+
"files": [
|
|
65
|
+
{
|
|
66
|
+
"path": "app/broadcasts/new/page.tsx"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"badges": []
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "queue-route",
|
|
73
|
+
"label": "POST /api/broadcasts/queue",
|
|
74
|
+
"kind": "route",
|
|
75
|
+
"delta": "modified",
|
|
76
|
+
"lane": "web",
|
|
77
|
+
"summary": "Writes the queue document. Now stamps the recipient count and batch size the sender will use instead of leaving batching to the worker.",
|
|
78
|
+
"files": [
|
|
79
|
+
{
|
|
80
|
+
"path": "app/api/broadcasts/queue/route.ts",
|
|
81
|
+
"startLine": 24,
|
|
82
|
+
"endLine": 96
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"badges": [
|
|
86
|
+
"+38 / -12"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "broadcast-queue",
|
|
91
|
+
"label": "broadcastQueue",
|
|
92
|
+
"kind": "datastore",
|
|
93
|
+
"delta": "modified",
|
|
94
|
+
"lane": "functions",
|
|
95
|
+
"subtitle": "Firestore collection",
|
|
96
|
+
"summary": "Queue documents gained batchSize and suppressedCount fields, and results are now written back per batch rather than per recipient.",
|
|
97
|
+
"files": [
|
|
98
|
+
{
|
|
99
|
+
"path": "functions/src/broadcast/schema.ts",
|
|
100
|
+
"startLine": 12,
|
|
101
|
+
"endLine": 48
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"badges": []
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "send-broadcast-bulk",
|
|
108
|
+
"label": "sendBroadcastBulk",
|
|
109
|
+
"kind": "function",
|
|
110
|
+
"delta": "added",
|
|
111
|
+
"lane": "functions",
|
|
112
|
+
"subtitle": "onWrite trigger",
|
|
113
|
+
"summary": "New trigger handler. Fetches suppressions once, builds batched payloads, and posts them to Postmark in chunks of 500.",
|
|
114
|
+
"files": [
|
|
115
|
+
{
|
|
116
|
+
"path": "functions/src/broadcast/sendBroadcastBulk.ts",
|
|
117
|
+
"startLine": 1,
|
|
118
|
+
"endLine": 142
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"badges": [
|
|
122
|
+
"new"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "build-bulk-payload",
|
|
127
|
+
"label": "buildBulkPayload",
|
|
128
|
+
"kind": "function",
|
|
129
|
+
"delta": "added",
|
|
130
|
+
"lane": "functions",
|
|
131
|
+
"summary": "Turns a broadcast and its recipient slice into a Postmark batch request body.",
|
|
132
|
+
"files": [
|
|
133
|
+
{
|
|
134
|
+
"path": "packages/broadcast-lib/src/buildBulkPayload.ts",
|
|
135
|
+
"startLine": 1,
|
|
136
|
+
"endLine": 74
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"badges": []
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"id": "get-suppressed-emails",
|
|
143
|
+
"label": "getSuppressedEmails",
|
|
144
|
+
"kind": "function",
|
|
145
|
+
"delta": "added",
|
|
146
|
+
"lane": "functions",
|
|
147
|
+
"summary": "Pulls the Postmark suppression dump once per broadcast so suppressed addresses are filtered before any batch is sent.",
|
|
148
|
+
"files": [
|
|
149
|
+
{
|
|
150
|
+
"path": "packages/broadcast-lib/src/getSuppressedEmails.ts",
|
|
151
|
+
"startLine": 1,
|
|
152
|
+
"endLine": 58
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
"badges": []
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "broadcast-lib",
|
|
159
|
+
"label": "broadcast-lib",
|
|
160
|
+
"kind": "package",
|
|
161
|
+
"delta": "added",
|
|
162
|
+
"lane": "functions",
|
|
163
|
+
"subtitle": "packages/broadcast-lib",
|
|
164
|
+
"summary": "New shared package so the queue route and the sender agree on payload shape and batch size.",
|
|
165
|
+
"files": [
|
|
166
|
+
{
|
|
167
|
+
"path": "packages/broadcast-lib/src/index.ts"
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"badges": [
|
|
171
|
+
"new package"
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "process-broadcast",
|
|
176
|
+
"label": "processBroadcast",
|
|
177
|
+
"kind": "function",
|
|
178
|
+
"delta": "removed",
|
|
179
|
+
"lane": "functions",
|
|
180
|
+
"subtitle": "onWrite trigger",
|
|
181
|
+
"summary": "The per-recipient loop this change replaces.",
|
|
182
|
+
"files": [
|
|
183
|
+
{
|
|
184
|
+
"path": "functions/src/broadcast/processBroadcast.ts",
|
|
185
|
+
"startLine": 1,
|
|
186
|
+
"endLine": 118,
|
|
187
|
+
"revision": "base"
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"badges": []
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "send-single-email",
|
|
194
|
+
"label": "sendSingleEmail",
|
|
195
|
+
"kind": "function",
|
|
196
|
+
"delta": "removed",
|
|
197
|
+
"lane": "functions",
|
|
198
|
+
"summary": "One Postmark request per recipient. Gone with the loop that called it.",
|
|
199
|
+
"files": [
|
|
200
|
+
{
|
|
201
|
+
"path": "functions/src/broadcast/sendSingleEmail.ts",
|
|
202
|
+
"startLine": 1,
|
|
203
|
+
"endLine": 46,
|
|
204
|
+
"revision": "base"
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
"badges": []
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "postmark",
|
|
211
|
+
"label": "Postmark",
|
|
212
|
+
"kind": "external",
|
|
213
|
+
"delta": "modified",
|
|
214
|
+
"lane": "external",
|
|
215
|
+
"subtitle": "Email API",
|
|
216
|
+
"summary": "Same provider, different endpoints: the batch endpoint and the suppression dump replace repeated single sends.",
|
|
217
|
+
"files": [],
|
|
218
|
+
"badges": []
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"edges": [
|
|
222
|
+
{
|
|
223
|
+
"id": "composer-to-queue",
|
|
224
|
+
"from": "broadcast-composer",
|
|
225
|
+
"to": "queue-route",
|
|
226
|
+
"kind": "http",
|
|
227
|
+
"delta": "unchanged",
|
|
228
|
+
"label": "send broadcast",
|
|
229
|
+
"emphasis": "normal",
|
|
230
|
+
"animated": false,
|
|
231
|
+
"files": []
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"id": "queue-to-firestore",
|
|
235
|
+
"from": "queue-route",
|
|
236
|
+
"to": "broadcast-queue",
|
|
237
|
+
"kind": "data",
|
|
238
|
+
"delta": "modified",
|
|
239
|
+
"label": "enqueue job",
|
|
240
|
+
"emphasis": "normal",
|
|
241
|
+
"animated": false,
|
|
242
|
+
"files": []
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "queue-to-lib",
|
|
246
|
+
"from": "queue-route",
|
|
247
|
+
"to": "broadcast-lib",
|
|
248
|
+
"kind": "dependency",
|
|
249
|
+
"delta": "added",
|
|
250
|
+
"label": "batch size",
|
|
251
|
+
"emphasis": "normal",
|
|
252
|
+
"animated": false,
|
|
253
|
+
"files": []
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"id": "firestore-to-bulk",
|
|
257
|
+
"from": "broadcast-queue",
|
|
258
|
+
"to": "send-broadcast-bulk",
|
|
259
|
+
"kind": "event",
|
|
260
|
+
"delta": "added",
|
|
261
|
+
"label": "onWrite",
|
|
262
|
+
"emphasis": "normal",
|
|
263
|
+
"animated": false,
|
|
264
|
+
"files": []
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"id": "firestore-to-process",
|
|
268
|
+
"from": "broadcast-queue",
|
|
269
|
+
"to": "process-broadcast",
|
|
270
|
+
"kind": "event",
|
|
271
|
+
"delta": "removed",
|
|
272
|
+
"label": "onWrite",
|
|
273
|
+
"emphasis": "normal",
|
|
274
|
+
"animated": false,
|
|
275
|
+
"files": []
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"id": "process-to-single",
|
|
279
|
+
"from": "process-broadcast",
|
|
280
|
+
"to": "send-single-email",
|
|
281
|
+
"kind": "call",
|
|
282
|
+
"delta": "removed",
|
|
283
|
+
"label": "per recipient",
|
|
284
|
+
"emphasis": "normal",
|
|
285
|
+
"animated": false,
|
|
286
|
+
"files": []
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"id": "single-to-postmark",
|
|
290
|
+
"from": "send-single-email",
|
|
291
|
+
"to": "postmark",
|
|
292
|
+
"kind": "http",
|
|
293
|
+
"delta": "removed",
|
|
294
|
+
"label": "POST /email · 1 msg/call",
|
|
295
|
+
"emphasis": "normal",
|
|
296
|
+
"animated": false,
|
|
297
|
+
"files": []
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"id": "bulk-to-payload",
|
|
301
|
+
"from": "send-broadcast-bulk",
|
|
302
|
+
"to": "build-bulk-payload",
|
|
303
|
+
"kind": "call",
|
|
304
|
+
"delta": "added",
|
|
305
|
+
"emphasis": "normal",
|
|
306
|
+
"animated": false,
|
|
307
|
+
"files": []
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"id": "bulk-to-suppressions",
|
|
311
|
+
"from": "send-broadcast-bulk",
|
|
312
|
+
"to": "get-suppressed-emails",
|
|
313
|
+
"kind": "call",
|
|
314
|
+
"delta": "added",
|
|
315
|
+
"emphasis": "normal",
|
|
316
|
+
"animated": false,
|
|
317
|
+
"files": []
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"id": "bulk-to-lib",
|
|
321
|
+
"from": "send-broadcast-bulk",
|
|
322
|
+
"to": "broadcast-lib",
|
|
323
|
+
"kind": "dependency",
|
|
324
|
+
"delta": "added",
|
|
325
|
+
"emphasis": "normal",
|
|
326
|
+
"animated": false,
|
|
327
|
+
"files": []
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "suppressions-to-postmark",
|
|
331
|
+
"from": "get-suppressed-emails",
|
|
332
|
+
"to": "postmark",
|
|
333
|
+
"kind": "http",
|
|
334
|
+
"delta": "added",
|
|
335
|
+
"label": "GET suppression dump",
|
|
336
|
+
"emphasis": "normal",
|
|
337
|
+
"animated": true,
|
|
338
|
+
"files": []
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"id": "bulk-to-postmark",
|
|
342
|
+
"from": "send-broadcast-bulk",
|
|
343
|
+
"to": "postmark",
|
|
344
|
+
"kind": "http",
|
|
345
|
+
"delta": "added",
|
|
346
|
+
"label": "500 msgs/call",
|
|
347
|
+
"emphasis": "hero",
|
|
348
|
+
"animated": true,
|
|
349
|
+
"summary": "The change in one edge: a broadcast to 10,000 recipients drops from 10,000 requests to 20.",
|
|
350
|
+
"files": []
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"id": "bulk-to-firestore",
|
|
354
|
+
"from": "send-broadcast-bulk",
|
|
355
|
+
"to": "broadcast-queue",
|
|
356
|
+
"kind": "data",
|
|
357
|
+
"delta": "added",
|
|
358
|
+
"label": "write results",
|
|
359
|
+
"emphasis": "normal",
|
|
360
|
+
"animated": false,
|
|
361
|
+
"files": []
|
|
362
|
+
}
|
|
363
|
+
],
|
|
364
|
+
"flows": [
|
|
365
|
+
{
|
|
366
|
+
"id": "send-pipeline",
|
|
367
|
+
"title": "Sending a broadcast",
|
|
368
|
+
"summary": "The path a queued broadcast takes now, from enqueue to per-message results.",
|
|
369
|
+
"delta": "modified",
|
|
370
|
+
"participants": [
|
|
371
|
+
{
|
|
372
|
+
"node": "queue-route",
|
|
373
|
+
"label": "queue route"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"node": "broadcast-queue",
|
|
377
|
+
"label": "Firestore"
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"node": "send-broadcast-bulk",
|
|
381
|
+
"label": "sendBroadcastBulk"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"node": "postmark",
|
|
385
|
+
"label": "Postmark"
|
|
386
|
+
}
|
|
387
|
+
],
|
|
388
|
+
"messages": [
|
|
389
|
+
{
|
|
390
|
+
"id": "enqueue",
|
|
391
|
+
"from": "queue-route",
|
|
392
|
+
"to": "broadcast-queue",
|
|
393
|
+
"label": "enqueue broadcast job",
|
|
394
|
+
"kind": "async",
|
|
395
|
+
"delta": "modified",
|
|
396
|
+
"animated": true,
|
|
397
|
+
"files": []
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"id": "trigger",
|
|
401
|
+
"from": "broadcast-queue",
|
|
402
|
+
"to": "send-broadcast-bulk",
|
|
403
|
+
"label": "onWrite trigger",
|
|
404
|
+
"kind": "async",
|
|
405
|
+
"delta": "added",
|
|
406
|
+
"animated": true,
|
|
407
|
+
"files": []
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"id": "suppressions-request",
|
|
411
|
+
"from": "send-broadcast-bulk",
|
|
412
|
+
"to": "postmark",
|
|
413
|
+
"label": "GET suppression dump",
|
|
414
|
+
"kind": "sync",
|
|
415
|
+
"delta": "added",
|
|
416
|
+
"animated": true,
|
|
417
|
+
"files": []
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"id": "suppressions-response",
|
|
421
|
+
"from": "postmark",
|
|
422
|
+
"to": "send-broadcast-bulk",
|
|
423
|
+
"label": "suppressed addresses",
|
|
424
|
+
"kind": "return",
|
|
425
|
+
"delta": "added",
|
|
426
|
+
"animated": true,
|
|
427
|
+
"note": "Fetched once per broadcast, not once per recipient.",
|
|
428
|
+
"files": []
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"id": "batch-post",
|
|
432
|
+
"from": "send-broadcast-bulk",
|
|
433
|
+
"to": "postmark",
|
|
434
|
+
"label": "POST /email/batch · 500 msgs",
|
|
435
|
+
"kind": "sync",
|
|
436
|
+
"delta": "added",
|
|
437
|
+
"animated": true,
|
|
438
|
+
"repeat": 4,
|
|
439
|
+
"note": "One request per 500 recipients; four for this 2,000-recipient broadcast.",
|
|
440
|
+
"files": []
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"id": "batch-results",
|
|
444
|
+
"from": "postmark",
|
|
445
|
+
"to": "send-broadcast-bulk",
|
|
446
|
+
"label": "per-message results",
|
|
447
|
+
"kind": "return",
|
|
448
|
+
"delta": "added",
|
|
449
|
+
"animated": true,
|
|
450
|
+
"files": []
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"id": "write-results",
|
|
454
|
+
"from": "send-broadcast-bulk",
|
|
455
|
+
"to": "broadcast-queue",
|
|
456
|
+
"label": "write results",
|
|
457
|
+
"kind": "async",
|
|
458
|
+
"delta": "added",
|
|
459
|
+
"animated": true,
|
|
460
|
+
"files": []
|
|
461
|
+
}
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
],
|
|
465
|
+
"stats": {
|
|
466
|
+
"filesChanged": 14,
|
|
467
|
+
"additions": 486,
|
|
468
|
+
"deletions": 212,
|
|
469
|
+
"chips": [
|
|
470
|
+
{
|
|
471
|
+
"label": "Postmark calls",
|
|
472
|
+
"value": "500× fewer",
|
|
473
|
+
"tone": "hero"
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"label": "New",
|
|
477
|
+
"value": "4 units",
|
|
478
|
+
"tone": "added"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"label": "Retired",
|
|
482
|
+
"value": "2 units",
|
|
483
|
+
"tone": "removed"
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
},
|
|
487
|
+
"views": [
|
|
488
|
+
{
|
|
489
|
+
"id": "overview",
|
|
490
|
+
"title": "Architecture — blast radius",
|
|
491
|
+
"lens": "architecture",
|
|
492
|
+
"summary": "Everything this change touches, across all three lanes.",
|
|
493
|
+
"scope": {
|
|
494
|
+
"kind": "all"
|
|
495
|
+
},
|
|
496
|
+
"defaultOpen": true,
|
|
497
|
+
"children": [
|
|
498
|
+
{
|
|
499
|
+
"id": "new-batch-path",
|
|
500
|
+
"title": "The new batch path",
|
|
501
|
+
"lens": "architecture",
|
|
502
|
+
"summary": "What replaced the per-recipient loop.",
|
|
503
|
+
"scope": {
|
|
504
|
+
"kind": "selection",
|
|
505
|
+
"lanes": [],
|
|
506
|
+
"nodes": [
|
|
507
|
+
"send-broadcast-bulk",
|
|
508
|
+
"build-bulk-payload",
|
|
509
|
+
"get-suppressed-emails",
|
|
510
|
+
"broadcast-lib",
|
|
511
|
+
"postmark"
|
|
512
|
+
],
|
|
513
|
+
"edges": [
|
|
514
|
+
"bulk-to-payload",
|
|
515
|
+
"bulk-to-suppressions",
|
|
516
|
+
"bulk-to-lib",
|
|
517
|
+
"suppressions-to-postmark",
|
|
518
|
+
"bulk-to-postmark",
|
|
519
|
+
"bulk-to-firestore"
|
|
520
|
+
],
|
|
521
|
+
"flows": []
|
|
522
|
+
},
|
|
523
|
+
"defaultOpen": false,
|
|
524
|
+
"children": []
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"id": "retired-path",
|
|
528
|
+
"title": "What was retired",
|
|
529
|
+
"lens": "architecture",
|
|
530
|
+
"summary": "The single-send path, kept visible so a reviewer can confirm nothing else called it.",
|
|
531
|
+
"scope": {
|
|
532
|
+
"kind": "selection",
|
|
533
|
+
"lanes": [],
|
|
534
|
+
"nodes": [
|
|
535
|
+
"process-broadcast",
|
|
536
|
+
"send-single-email"
|
|
537
|
+
],
|
|
538
|
+
"edges": [
|
|
539
|
+
"firestore-to-process",
|
|
540
|
+
"process-to-single",
|
|
541
|
+
"single-to-postmark"
|
|
542
|
+
],
|
|
543
|
+
"flows": []
|
|
544
|
+
},
|
|
545
|
+
"defaultOpen": false,
|
|
546
|
+
"children": []
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"id": "send-pipeline-view",
|
|
552
|
+
"title": "Data flow — sending a broadcast",
|
|
553
|
+
"lens": "data-flow",
|
|
554
|
+
"scope": {
|
|
555
|
+
"kind": "selection",
|
|
556
|
+
"lanes": [],
|
|
557
|
+
"nodes": [],
|
|
558
|
+
"edges": [],
|
|
559
|
+
"flows": [
|
|
560
|
+
"send-pipeline"
|
|
561
|
+
]
|
|
562
|
+
},
|
|
563
|
+
"defaultOpen": false,
|
|
564
|
+
"children": []
|
|
565
|
+
}
|
|
566
|
+
],
|
|
567
|
+
"layout": {
|
|
568
|
+
"direction": "right",
|
|
569
|
+
"laneOrder": [
|
|
570
|
+
"web",
|
|
571
|
+
"functions",
|
|
572
|
+
"external"
|
|
573
|
+
],
|
|
574
|
+
"rank": {
|
|
575
|
+
"queue-route": 0,
|
|
576
|
+
"send-broadcast-bulk": 1,
|
|
577
|
+
"postmark": 2
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Authoring a graph document
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This page is the whole shape, and what a schema cannot tell you besides: which parts matter, and where documents actually go wrong. `references/example.graph.json` is one document that validates, if you would rather read than be told.
|
|
4
|
+
|
|
5
|
+
The validator enforces the same thing from a JSON Schema, published at `https://unpkg.com/@coldtea/pr-lens-schema/json-schema/graph-doc.schema.json` if you want it machine-readable.
|
|
4
6
|
|
|
5
7
|
Every schema here is **strict**: an unknown key is a rejection, not a warning. A field with a default may be left out.
|
|
6
8
|
|
|
@@ -162,7 +164,7 @@ Labels 120 characters, summaries 2000, chip values 32. They are display fields:
|
|
|
162
164
|
## Then validate
|
|
163
165
|
|
|
164
166
|
```bash
|
|
165
|
-
npx @coldtea/pr-lens-cli validate pr-lens/graph.json
|
|
167
|
+
npx @coldtea/pr-lens-cli validate .pr-lens/graph.json
|
|
166
168
|
```
|
|
167
169
|
|
|
168
170
|
Every problem is reported at once, with a path into the document. Fix them all and run it again until it is clean.
|