darkprint 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.
@@ -0,0 +1,688 @@
1
+ ---
2
+ name: darkprint
3
+ description: Interview an author from the task they want done to a complete DarkPrint blueprint (topology.dot, cards/*.yaml, blueprint.yaml, README.md), searching the registry for a blueprint or cards to reuse before drawing anything, deriving the topology from declared ports, guarding every fork, and forcing an explicit decision on which node may see the acceptance criteria. Use when someone wants to design an agent pipeline as a typed graph, turn a workflow or a set of prompts into a DarkPrint bundle, write or repair node cards, add a capability from the registry to a blueprint that already exists (enrich mode), decide what a node must never receive, or validate a blueprint before publishing it. This skill writes files and validates them. It runs no graph, calls no model, publishes nothing by itself, and sends only the author's one-sentence task to the registry's search unless the author chooses to publish, to validate over HTTP, or to open an optional live preview on darkprint.io; each is a step they can decline.
4
+ metadata:
5
+ version: "0.1.0"
6
+ ---
7
+
8
+ # DarkPrint: author a blueprint
9
+
10
+ A DarkPrint blueprint is an agent pipeline written down as a **typed graph**: a DOT file
11
+ that says who is wired to whom, and one YAML card per node that says what that node does,
12
+ what it accepts, what it emits and **what it must never receive**. The engine reads the
13
+ pair statically and reports what the shape implies: where a person acts, what the risk
14
+ surface is, and above all whether the node that produces the work can see the criteria the
15
+ work will be judged against.
16
+
17
+ Your job in this skill is to get an author from *"here is a task I want done by agents"* to
18
+ that bundle, by interviewing them. Not by filling in a form for them, and not by guessing.
19
+
20
+ ## What this skill does and does not do
21
+
22
+ | | |
23
+ |---|---|
24
+ | Searches | the DarkPrint registry, before drawing anything, for a blueprint or cards that already do the job (Phase 1) |
25
+ | Writes | `topology.dot`, `cards/<id>@<version>.yaml`, `blueprint.yaml`, `README.md` in a directory the author names, plus `ontology/extensions.yaml` only when the author asked for a local term |
26
+ | Does not write | `factory.dot` or `AGENTS.md`. Neither is part of a published blueprint folder; duplicating either here would give an author a folder that disagrees with the registry's |
27
+ | Validates | with `darkprint validate <dir>` when the CLI is on the PATH, else with `npx -y darkprint validate <dir>` when Node and a network are, else by POSTing the files to `https://www.darkprint.io/api/validate/bundle`, else by asking the author to drop the folder on `/upload` |
28
+ | Does not do | run the graph, run any node, call a model on the author's behalf, start a server, or publish by itself |
29
+ | Sends | the author's one-sentence task to the registry's search in Phase 1, and nothing else until the author chooses to validate over HTTP, to publish, or to open a live preview; the preview posts the draft to darkprint.io under an unguessable link and nothing else. Those three are steps the author takes and can decline |
30
+
31
+ Say those plainly if the author asks what happens next. The registry has accounts
32
+ (`/welcome`), drafts (`/new`), per-bundle visibility, publishing from `/upload`, and API
33
+ keys from `/settings`. Do not describe any of them as unbuilt, and do not invent anything
34
+ beyond them.
35
+
36
+ ## Read these when you need them
37
+
38
+ - `references/ontology.md`: every term the validator resolves against, generated from the
39
+ engine. The five phases, the node types, the risk markers and what each costs, the data
40
+ types and their lattice, the twelve tool capabilities. **Read it before you type any
41
+ card**, and quote term ids from it rather than from memory.
42
+ - `references/card-schema.md`: the wire format, generated from the validator. Every accepted
43
+ key, what is required, what defaults to what, which of `cannot` and `will_not` the engine
44
+ checks, and what each kind of edit costs in version.
45
+ - `references/dot-and-attractor.md`: how to write the DOT so it loads clean, the edge guard
46
+ grammar, and what a card becomes when the bundle is compiled for Attractor.
47
+ - `references/preflight.md`: the checklist, keyed to diagnostic codes. Walk your own output
48
+ against it before you run the validator, so nothing the validator prints surprises you.
49
+ - `references/writing-cards.md`: the prose rules for `action`, `spec`, port descriptions,
50
+ `notes`, `README.md` and `blueprint.yaml`.
51
+ - `references/live-preview.md`: the optional live page on darkprint.io, the draft it takes
52
+ field by field, the three endpoints and the curl lines. Read it when the author opts in.
53
+ - `templates/`: a skeleton DOT, a skeleton card for a node instructed by prose, and a
54
+ skeleton card for a node that is a shell command. All three are annotated.
55
+
56
+ Both reference files marked *generated* are rendered from the engine's own source. If one
57
+ disagrees with something you remember, the reference is right.
58
+
59
+ ---
60
+
61
+ # The posture
62
+
63
+ This is a grill, not a form.
64
+
65
+ 1. **Recommend an answer with every question.** You have read the graph so far; say what you
66
+ would pick and why, then let the author overrule you. A question with no recommendation
67
+ makes the author do your work.
68
+ 2. **Look facts up; ask only for decisions.** Which MCP servers are installed, what the test
69
+ command is, what the repository is called, whether a criteria file already exists, and
70
+ whether the registry already holds a blueprint for this task: go and find out. Search the
71
+ filesystem, read the config, run the command, query the registry. What the author *wants*
72
+ is theirs to decide; what is *true about their machine and the registry* is yours to
73
+ discover.
74
+ 3. **Pace by phase.** Phase 0 and Phase 4 are one question per turn: ask it, stop, wait. Each
75
+ of those answers changes the next question. Phases 2, 3 and 5 are ledger work: ask the
76
+ whole frontier in one numbered round, every question whose prerequisites are settled, each
77
+ with your recommendation, and never a question whose answer another open question would
78
+ change.
79
+ 4. **Keep the tree visible.** At each phase boundary print three short lists: settled, open,
80
+ blocked on. Skip any question whose answer is already settled by an earlier one. The
81
+ interview ends when the open list is empty, not when the script runs out.
82
+ 5. **A question the author cannot answer is a prototype, not a guess.** "I don't know" is a
83
+ real answer. When it lands on something the graph depends on, offer the smallest graph
84
+ that would expose the answer (usually two nodes: a builder and one check) and stop
85
+ grilling until they have run it.
86
+ 6. **Do not write a single file until the author confirms shared understanding.** Show back
87
+ the three things at the end of Phase 5, get a yes, then write.
88
+ 7. **Say the engine consequence out loud.** Every question below carries a **FOR** line naming
89
+ what the answer decides and which diagnostic it prevents. When a choice costs something,
90
+ two points of security or a check that silently does not run, name the number before the
91
+ author chooses, not after.
92
+
93
+ Derive everything derivable. The author never types a card id, a DOT node id, a version, a
94
+ `dependencies` list, a port description, a `spec`, a slug or a `condition`. Those come out of
95
+ answers they already gave.
96
+
97
+ ---
98
+
99
+ # Live preview
100
+
101
+ The author can watch the blueprint take shape on darkprint.io while you interview them. It
102
+ is their choice, asked once before Q0.1, and declining costs nothing. When they arrived with
103
+ a live URL already (the tutorial at `https://www.darkprint.io/tutorial` hands them one, shaped
104
+ `https://www.darkprint.io/tutorial/live/<token>`), the answer is yes: take the token from the
105
+ last path segment, 32 URL-safe characters, and skip the opening call.
106
+
107
+ **Open a page.** One POST with an empty object; the answer carries `token` and `url`:
108
+
109
+ ```
110
+ curl -fsS -X POST https://www.darkprint.io/api/tutorial/live \
111
+ -H "content-type: application/json" --data '{}'
112
+ ```
113
+
114
+ Remember the token, print the `url` and ask the author to open it. The page is reachable only
115
+ through that token, it holds the last draft you sent and nothing else, and it expires 24 hours
116
+ after it was opened, refreshed by every accepted PUT. Each shell command you run may start in
117
+ a fresh shell, so an `export` in one command is gone by the next: set the variable at the head
118
+ of every command instead, `DARKPRINT_LIVE_TOKEN=<token>; curl …`, as the block below does.
119
+ When the URL the author handed you has an origin other than `https://www.darkprint.io` (a
120
+ preview deployment, a local server), use that origin in every line below; the page lives
121
+ where it was opened.
122
+
123
+ **Send the draft.** At every phase boundary, once more after the files are written, and
124
+ again after the folder is enriched or published, PUT one JSON `LiveDraft`:
125
+
126
+ ```
127
+ DARKPRINT_LIVE_TOKEN=<token>; \
128
+ curl -fsS -X PUT "https://www.darkprint.io/api/tutorial/live/$DARKPRINT_LIVE_TOKEN" \
129
+ -H "content-type: application/json" --data-binary @-
130
+ ```
131
+
132
+ with the JSON on stdin, so no file lands on disk before rule 6 allows it. The draft carries
133
+ `phase` (`need`, `reuse`, `nodes`, `ports`, `guards`, `risk`, `written`, `enriched` or
134
+ `published`), `task` (the Q0.1 sentence), `bundle` (`manifest` with `slug`, `title`, `summary`
135
+ and `tags`; `dot`; `cardFiles` keyed `cards/<id>@<version>.yaml`), `ledger` (the three lists
136
+ of posture rule 4: `settled`, `open`, `blocked`), `hits` (every registry hit you considered,
137
+ each `kind`, `ref`, `title`, `score`) and, after publishing, `publishedRef`. The bundle may
138
+ be partial: send the DOT as soon as the nodes have names and each card as soon as it is
139
+ drafted in memory. The page draws what the engine can resolve and lists the rest as still to
140
+ settle. `references/live-preview.md` has the shape field by field, and the provisional
141
+ manifest to send before Q5.3 has derived the real one.
142
+
143
+ A PUT that fails is reported to the author in one line and never blocks the interview: the
144
+ folder is the deliverable and the page is a window on it. Nothing else leaves the machine
145
+ because a page is open; the draft is the whole payload, and it holds only what the author
146
+ already said.
147
+
148
+ ---
149
+
150
+ # Phase 0: the need
151
+
152
+ No node exists yet. Three questions decide whether there is anything to draw. One per turn.
153
+
154
+ **Q0.1** *In one sentence: what do you want done? Not how. What exists at the end that does
155
+ not exist now.*
156
+ FOR: the run's entry input and its terminal artefact, the search sentence for Phase 1, and
157
+ `blueprint.yaml`'s `title` and `summary`.
158
+
159
+ **Q0.2** *When it is done, where does the thing land, and who or what put it there?*
160
+ FOR: the sink node, one input and `outputs: []`. Prevents `bundle/no-exit`. If the answer
161
+ names a place outside the author's control (a production system, a customer, a payment),
162
+ Q0.6's rollback question is already answered and a `human-gate` sits at that boundary.
163
+
164
+ **Q0.3, THE GATE.** *Name the command that exits non-zero when the work is wrong.*
165
+ FOR: whether the task is a candidate at all. Without a nameable check there is no
166
+ `validation` node, and the criteria check has nothing to anchor on. Say precisely what the
167
+ engine does then: with no `validation` node **and** no `acceptance-criteria` port it says
168
+ nothing at all; with one leg present and the other missing it warns
169
+ `analysis/criteria-leak-unanchored`. Either way a security level of 4 is silence, not a pass.
170
+
171
+ **Stop rule for Phase 0.** If the author cannot name the check, do not draw a graph. Offer the
172
+ two-node prototype instead: one node that produces the artefact and one `shell-tool` node
173
+ that runs whatever command they *think* would catch a wrong result, with nothing else. Ask
174
+ them to run it once by hand. The check they discover is worth more than a diagram, and the
175
+ interview resumes from Q0.3 when they have it.
176
+
177
+ Then, still one per turn:
178
+
179
+ **Q0.4** *Does that check already exist, or can you write it before the work starts?*
180
+ FOR: whether the acceptance criteria are an artefact a node **produces inside the graph**
181
+ (good: the criteria are on an edge and isolation is a property of the topology) or a file
182
+ that arrives with the run configuration (`analysis/criteria-out-of-band` if a `params` key
183
+ names it, and the check is blind to that channel). Name which of the two they have chosen and
184
+ what it costs.
185
+
186
+ **Q0.5** *Name three files, directories or systems you do not want touched.*
187
+ FOR: the `will_not` entries, and the boundary sentences in the builder's `spec`. An author
188
+ who cannot name three is still inventing the requirements, and the blueprint will execute
189
+ that ambiguity faithfully.
190
+
191
+ **Q0.6** *If this lands wrong, who finds out and how long do you have? Describe the rollback
192
+ in one sentence.* Skip it if Q0.2 already answered it.
193
+ FOR: whether a `human-gate` sits at the release boundary, and whether `irreversible-action`
194
+ (−1.5) and `unchecked-write` (−1.0) get declared. If the sentence contains the word
195
+ *incident*, the honest answer to "can this run unattended" is no.
196
+
197
+ ---
198
+
199
+ # Phase 1: reuse before you draw
200
+
201
+ The registry is part of the environment. Search it with the author's own sentence from Q0.1
202
+ **before** asking them to describe a single node, and do it yourself: this is a fact, not a
203
+ decision.
204
+
205
+ **How to search.** If the DarkPrint MCP server is configured in this session, call
206
+ `find_blueprints` with the task sentence, then `find_cards` with the same sentence. If it is
207
+ not, the same searches are anonymous HTTP GETs:
208
+
209
+ ```
210
+ GET https://www.darkprint.io/api/mcp/blueprints/find?task=<the sentence, URL-encoded>
211
+ GET https://www.darkprint.io/api/mcp/cards/find?task=<the sentence, URL-encoded>
212
+ ```
213
+
214
+ Both rank by similarity to the sentence and return hits with a score and the evidence they
215
+ matched on. A hit with a low score is not a hit. If the author wants the tools in every
216
+ session, the one-line setup is `claude mcp add --transport http darkprint
217
+ https://www.darkprint.io/api/mcp` for Claude Code and `codex mcp add darkprint --url
218
+ https://www.darkprint.io/api/mcp` for Codex.
219
+
220
+ **Q1.1, a whole blueprint.** When a blueprint hit covers the outcome (Q0.1) *and* the gate
221
+ (Q0.3), fetch it: `get_blueprint` with its owner and slug, or
222
+ `GET https://www.darkprint.io/api/mcp/blueprints/<owner>/<slug>/bundle`. Show the author its
223
+ node list and its **edge list, including the edges it deliberately does not draw**, in their
224
+ words, and ask:
225
+
226
+ > *Start from this, or from nothing? Starting from it means we keep its cards and its
227
+ > isolation decisions and change only what your task needs.*
228
+
229
+ Recommend one. If they start from it, the rest of the interview is a diff: Phase 2 asks only
230
+ about nodes the task adds or removes, Phase 3 re-runs the ledger over the changed ports, and
231
+ Phase 4 re-asks Q4.2 because it is never inherited. The `README.md` then names the blueprint
232
+ it started from, and the release records the lineage when they publish it as a fork.
233
+
234
+ **Q1.2, individual cards.** When no blueprint fits but `find_cards` returns a card that does
235
+ one of the jobs the author is about to describe, offer it by name and version and read it
236
+ first (`read_card`, or `GET https://www.darkprint.io/api/mcp/cards/<id>@<version>`). A card
237
+ you reuse is **pinned by `id@version`** in the DOT and copied verbatim into `cards/`, never
238
+ rewritten: rewriting it makes a new card with a stolen name, and the digest check on the
239
+ registry will say so.
240
+
241
+ If nothing fits, say so in one line and move on. The search cost one turn; a card written
242
+ from nothing that duplicates a published one costs the reader who meets both.
243
+
244
+ ---
245
+
246
+ # Phase 2: the nodes
247
+
248
+ **Q2.1** *Walk me through one run, from request to artefact, as a sequence of things that
249
+ happen. Your words, not node names.*
250
+
251
+ Then propose the split and put it back: *"I read that as four nodes: X, Y, Z, W. Is any of
252
+ those two jobs wearing one hat, or two of them the same job twice?"*
253
+
254
+ Then, per node, as one numbered round:
255
+
256
+ **Q2.2** *What is the one job of this node? If your answer contains an "and" that names two
257
+ different artefacts, it is two nodes.*
258
+ FOR: `action`. And the engine consequence when it bites: a node that emits both the
259
+ acceptance criteria **and** the artefact its judge reads fires `criteria-leak` off the
260
+ declarations alone, with no path to trace.
261
+
262
+ **Q2.3** *Who does that job?* Offer all ten answers in plain words, and map each to its type
263
+ yourself:
264
+
265
+ | the author says | `type` | what else it needs |
266
+ |---|---|---|
267
+ | a model reasoning its way to an answer | `agent` | a `spec` that is the whole prompt |
268
+ | a deterministic operation described in prose (compile, format, call an API) | `tool` | a `spec` |
269
+ | a fixed shell command the runner executes | `shell-tool` | **ask for the exact command**: it goes in `params.tool_command`, and a missing one is `card/missing-field` |
270
+ | a person approving or rejecting | `human-gate` | nothing; the type alone says a person acts here |
271
+ | a person supplying content or data | `human-input` | nothing; same |
272
+ | a switch that only routes | `decision` | guarded edges out of it (Phase 4, Q4.7) |
273
+ | a check that produces a verdict with evidence | `validation` | the criteria on an input port, evidence on an output port |
274
+ | the same step run on several inputs at once | `parallel`, then `parallel.fan-in` where the copies rejoin | both nodes, and a card each |
275
+ | a supervisor that polls a sub-run and decides whether to go round again | `manager-loop` | a cap on the loop it supervises |
276
+
277
+ FOR: `type`. Never type a node with an abstract category (`human-in-the-loop`, `evaluative`,
278
+ `orchestration`). Whether a person acts at a node is read from `type` and from nothing else:
279
+ there is no flag beside it, and getting it wrong puts a person on a node where nobody is, or
280
+ reads a staffed node as unattended, with nothing else on the card able to correct it.
281
+
282
+ **Q2.4** *Is this planning, implementation, testing, debugging or deployment, or none of
283
+ them?*
284
+ Offer the five with their meanings, and offer **"none" as a complete answer, not a gap**. An
285
+ intake, a retrieval step and a memory store sit in none of the five, and `phase: []` is
286
+ correct. Coverage is descriptive; a phase left to somebody else costs nothing. Never invent a
287
+ phase to fill a strip.
288
+
289
+ **Q2.5** *Of all these, which one's verdict decides the run is finished?*
290
+ FOR: forcing at least one node typed `validation`. Name the trap while you ask: the ontology
291
+ describes `tool` as "a deterministic operation: running tests, compiling…", which invites
292
+ typing the test runner `tool` or `shell-tool`. Do that and the generator set is empty and the
293
+ most important check in the system silently does not run. The test runner that decides the
294
+ verdict is `validation`; a `shell-tool` may run *under* it, feeding it evidence.
295
+
296
+ ---
297
+
298
+ # Phase 3: the ports and the ledger
299
+
300
+ This is where the topology comes from. Ports first, edges second. Ask each round as a
301
+ numbered frontier.
302
+
303
+ **Q3.1** *(per node)* *What does this node need in hand before it can start? Name each thing
304
+ separately.* Then, per item: *what kind of thing is it?*, offered as data types **by their
305
+ description**, never by their id.
306
+ FOR: `inputs` and their `type`.
307
+
308
+ **Q3.2** *What does it hand over when it is done? Name each. Handing over nothing is an
309
+ answer.*
310
+ FOR: `outputs`. `[]` is correct at the sink. But a node with `outputs: []` and an outgoing
311
+ edge is `bundle/port-mismatch`, an error: *"Edge X carries no data"*.
312
+
313
+ **Never type a port `any` when the author can describe it.** `any` matches every target, so
314
+ every edge passes, no `cannot` prohibition can be violated, and the criteria check has nothing
315
+ to anchor on. A bundle typed `any` throughout loads perfectly and checks nothing. That is the
316
+ single most damaging thing this skill can produce.
317
+
318
+ **Q3.3, THE LEDGER.** Match every declared input against every declared output yourself, and
319
+ put back **only the unmatched**: *"Nothing in this graph produces `evidence`. Which node
320
+ produces it, or does it arrive with the run?"*
321
+ FOR: this is where edges come from. An input with no producer is either an entry port, which
322
+ makes that node a source and is a legitimate answer, or an unreachable node waiting to
323
+ happen.
324
+
325
+ **Q3.4, THE TYPE RECONCILIATION.** *"`writer` emits `markdown`. `packager` accepts `report`.
326
+ Nothing carries between them. Which of the two is wrong?"*
327
+ FOR: preventing `bundle/type-mismatch`. Explain the rule: the source may be **narrower** than
328
+ the target, never broader.
329
+
330
+ **Q3.5, THE AMBIGUITY.** *"`checker` emits both `findings` and `cleared`, and `publisher`
331
+ accepts both. Which one does this edge carry?"*
332
+ FOR: writing `out=` / `in=` pins on the edge. Prevents `bundle/port-ambiguous`, and more
333
+ importantly stops the resolver's declaration-order guess from being the thing the prohibition
334
+ check reasons about.
335
+
336
+ **Q3.6, THE CRITERIA PORT.** *"The thing your check reads to decide pass or fail: who writes
337
+ it, and is it a separate artefact from the instructions the work is built from?"*
338
+ FOR: exactly one output port typed **`acceptance-criteria`**, and the plan and the criteria
339
+ as **two ports, not one**. Without that port type the producer set is empty and
340
+ `analysis/criteria-leak-unanchored` reports that the check never ran.
341
+
342
+ ---
343
+
344
+ # Phase 4: isolation, and the guards
345
+
346
+ This is the centre of the grill and the reason the format exists. One question per turn.
347
+
348
+ **Q4.1** Show the derived edge list, **including the pairs you did not draw**: *"These are
349
+ the edges your ports imply. These three are type-compatible pairs I did not draw. Read them."*
350
+
351
+ **Q4.2, THE QUESTION THAT MUST BE ANSWERED DELIBERATELY.**
352
+
353
+ > *Does the node that produces the work see the criteria the work will be judged against?
354
+ > Yes or no. There is no default and I will not pick one.*
355
+
356
+ - **No**: write `cannot: [acceptance-criteria]` on that node, and say what you just did:
357
+ *"That absence is now a rule the engine holds the graph to, in two places. Draw that edge and
358
+ the resolver refuses it outright, `bundle/prohibition-violated`, an error, because with no
359
+ `out=` pin the carriers are every output of the producer and the criteria are among them. Pin
360
+ it to a different port to get past that and the analyzer charges `criteria-leak` anyway,
361
+ −2.0, because its walk reads the graph at node level and does not care which port an edge
362
+ carries."*
363
+ - **Yes**: state the price before writing anything: `criteria-leak`, −2.0, security 4 → 2,
364
+ and the reason. Whoever writes the work must not see the acceptance tests, because if they
365
+ see them they write toward them. Then ask once more.
366
+
367
+ **The rule that decides where the brief comes from.** The topological half of the check reads
368
+ the graph at **node** level: *any* edge at all from the criteria producer into a node whose
369
+ work is judged establishes the marker, **whichever port that edge carries**. So a planner that
370
+ emits both a brief and the criteria must not have an edge to the builder even to hand over the
371
+ brief. The brief is handed to the builder when the graph is instantiated: the builder is a
372
+ source node with an input port and no incoming edge. Say this out loud when it comes up; it
373
+ looks like an oversight and it is the whole design.
374
+
375
+ **Q4.3** *What else must never reach this node?*, asked of every node. **Sort the answers into
376
+ the two fields and show the sort.** An entry naming a data type goes in `cannot` and the
377
+ resolver enforces it; everything else goes in `will_not` and is a sentence a reader reads and
378
+ the instantiated agent is told, which nothing checks. An author who believes "never opens a
379
+ shell" is enforced has been misled by you.
380
+
381
+ **Q4.4** *When the check fails, what exactly goes back to whoever fixes it: the raw failure
382
+ output, or the criteria that failed?*
383
+ FOR: the `report`-typed evidence port, and the prose of the judge's spec. The engine cannot
384
+ tell the two apart. `judge → fixer → judge` (endorsed) and `judge → builder → judge`
385
+ (forbidden) are the same shape, which is why it emits
386
+ `analysis/criteria-relayed-through-judge` and declines to decide. This interview is the only
387
+ place it gets decided.
388
+
389
+ **Q4.5** *Does the fix go back to the check, or back to the node that built the thing?*
390
+ FOR: the cycle shape. Back to the builder re-exposes it to the failure surface for the whole
391
+ run, one slice of the acceptance criteria per iteration.
392
+
393
+ **Q4.6** *(only if a cycle exists: detect it, do not ask whether one exists)* *How many
394
+ attempts in all before it stops?*
395
+ FOR: the iteration cap on one member of the cycle, **top level** in `params`, never nested.
396
+ Without it, `unbounded-loop` charges −1.5 on every member.
397
+
398
+ **Write `params.max_retries`, and write the number Attractor counts.** The engine accepts
399
+ `max_iterations`, `maxIterations` and `max_retries` and compiles all three to Attractor's
400
+ `max_retries`, which counts the attempts **after** the first: `max_retries: 2` is three
401
+ attempts in all. Ask for the total, write the total minus one, and state the total in the
402
+ `spec` so the two agree. A card whose prose says "stop after three attempts" and whose params
403
+ say `3` has authorised four.
404
+
405
+ **Q4.7, THE GUARD.** *(every node with two or more outgoing edges: the check, every
406
+ `decision`, every `manager-loop`)* *Which of these edges is taken when this node succeeds,
407
+ and which when it does not?*
408
+ FOR: a `condition` on every arm of the fork. A runner picks an edge in a fixed order: a
409
+ `condition` that holds first; then an edge whose `label` matches the label the node asked
410
+ for; then a next id the node suggested; then the higher `weight`; and on a tie, **the target
411
+ id that sorts first**. Two bare edges out of one node are a branch decided by the spelling of
412
+ the node names, and a loop whose fix arm sorts before its ship arm repairs until the cap is
413
+ spent and never ships.
414
+
415
+ Write `condition="outcome=success"` on the arm taken when the node succeeds and
416
+ `condition="outcome!=success"` on the other. The grammar is small and the linter holds you to
417
+ it (`attractor/condition-syntax`, which a runner grades as an error and refuses the whole
418
+ pipeline over): keys are `outcome`, `preferred_label` or `context.<dotted.path>`; operators are
419
+ `=` and `!=`; clauses join with `&&`; nothing else parses. No `||`, no `!`, no comparison, no
420
+ `contains`. One key against its own negation is the only total split it can make, so a
421
+ verdict that is neither success nor failure lands on the arm that does not ship. A fork the
422
+ author wants decided by preference rather than outcome takes `weight=<integer>` on the
423
+ preferred arm instead, and you say which of the two they chose.
424
+
425
+ ---
426
+
427
+ # Phase 5: risk and identity
428
+
429
+ Per node, fast, as one numbered round. Look up what you can instead of asking.
430
+
431
+ **Q5.1, the risk sheet.**
432
+
433
+ | ask | writes | costs |
434
+ |---|---|---|
435
+ | *Does it reach the web, an API, a database or CI?* | `tools`: `web-search`, `http-fetch`, `sql`, `ci` | `unvalidated-external-access` −1.0, **inferred**, unless every successor is a `validation` node |
436
+ | *Does it run shell or code that was not fixed in advance?* | `tools`: `shell`, `python-sandbox`; `risk_markers`: `arbitrary-code-execution` | −2.0. A `shell-tool` whose command is fixed in `params.tool_command` carries `tools: [shell]` and no marker; one whose command runs something a previous node wrote carries the marker |
437
+ | *Does it read or write files, commit to a repository, query a vector store, post to a chat channel, or wait on a person's review?* | `tools`: `file-io`, `git`, `vector-store`, `messaging`, `human-review` | 0. Nothing is inferred from these; they are the permission list the card publishes, and a node that commits with `tools: []` is lying |
438
+ | *Does it write anywhere with no check between it and the write?* | `risk_markers`: `unchecked-write` | −1.0 |
439
+ | *Does it handle credentials, keys or tokens?* | `risk_markers`: `secret-access` | −1.0 |
440
+ | *Can anything it does not be undone: publishing, sending, deleting?* | `risk_markers`: `irreversible-action` | −1.5, and this is where Q0.6's answer becomes a `human-gate` or does not |
441
+ | *(look it up)* which MCP servers are installed | `mcp`: free text, checked against nothing | 0 |
442
+
443
+ One warning to carry into this phase. The engine's own remediation hint for
444
+ `unvalidated-external-access` is *"insert a validation node between X and its consumers"*.
445
+ Following it blindly can drop a judge onto the criteria path, where it **absorbs** the criteria
446
+ walk: the security level improves while the criteria still reach the builder, and the only
447
+ trace is an `analysis/criteria-relayed-through-judge` warning nobody reads. Do not add a
448
+ validation node to silence a marker.
449
+
450
+ **Q5.2, names.** *A short name for the graph, and one for each node.* You derive **both**
451
+ identifiers and explain why they differ:
452
+
453
+ - DOT node id must match `[A-Za-z_][A-Za-z0-9_]*` → `code_builder`
454
+ - card id must match `^(?:ns/)?[a-z0-9]+(-[a-z0-9]+)*$` → `code-builder`
455
+ - The two grammars are **incompatible for any multi-word name**, so always pin
456
+ `card="code-builder@1.0.0"` and never rely on the bare `version=` fallback.
457
+ - Reject `digraph edge graph node strict subgraph` as node ids; avoid `start`, `Start`,
458
+ `exit`, `end`, which Attractor resolves as pipeline boundaries. No diagnostic warns about
459
+ those four: the exporter renames the node and records the original in `dp_node`.
460
+
461
+ **Q5.3, the manifest.** `blueprint.yaml` is derived, not asked, and shown back:
462
+
463
+ | field | from |
464
+ |---|---|
465
+ | `slug` | the graph name from Q5.2, in card-id grammar; also the folder name, and the name the first publish creates under their handle |
466
+ | `title` | Q0.1, as a title |
467
+ | `summary` | Q0.1, one sentence. It becomes the compiled pipeline's `goal`, so it is the one line the runner reads |
468
+ | `description` | Q2.1's walk-through, in prose, ending with the edge that is deliberately absent |
469
+ | `category` | one word for the domain (Software, Content, Data, Operations, Research); ask only if none is obvious |
470
+ | `tags` | three to six lowercase words, from the outcome, the domain and the shape (`isolation`, `human-gate`, `loop`) |
471
+
472
+ **Q5.4, versions.** `1.0.0` on every card you wrote for a first emit, and the version the
473
+ registry gave it on every card you reused. Not asked.
474
+
475
+ **Q5.5** *Anything a reader should know that no field above says?* → `notes`. Optional, and
476
+ absence carries no judgement.
477
+
478
+ ---
479
+
480
+ # Enrich mode: add to an existing blueprint
481
+
482
+ This is the skill's enrich mode, the words the tutorial's prompts use for it. The author has
483
+ a folder already and wants to add a capability to it: observability on top of a node, or a
484
+ review gate before the release. The interview is a diff, the way Q1.1 makes it one, and the
485
+ registry is searched first for the same reason.
486
+
487
+ 1. **Read the folder.** `topology.dot`, every card in `cards/`, `blueprint.yaml`, `README.md`.
488
+ Which node the addition attaches to, and what that node emits, are facts; look them up.
489
+ What the addition should do is the author's sentence; take it as the task.
490
+ 2. **Search with the addition as the task.** `find_blueprints` with that sentence, or the
491
+ anonymous GET from Phase 1. Show the top hits in the author's words, `ref`, `title` and
492
+ `score`, and recommend one. When a live page is open, send them as `hits`. The tutorial's
493
+ case, observability on a node, is `darkprint/pipeline-observability`.
494
+ 3. **Fetch the one they chose.** `get_blueprint` with its owner and slug, or
495
+ `GET https://www.darkprint.io/api/mcp/blueprints/<owner>/<slug>/bundle`. The answer lists
496
+ the release's files by path, with the version and the digest that name those bytes.
497
+ 4. **Merge, in this order.**
498
+ - Copy its `cards/<id>@<version>.yaml` files into `cards/` byte for byte, pinned by
499
+ `id@version`. A file already there under the same name is either the same bytes or a
500
+ conflict you report; it is never overwritten.
501
+ - Add its nodes and edges to `topology.dot`, keeping every `card=` pin. A DOT id that
502
+ collides with one already in the graph is renamed in the DOT only; the card keeps its id.
503
+ - Wire the addition to the graph by data type: an input of the new nodes is fed by an
504
+ output of the existing node whose type carries into it, source narrower than target as
505
+ Q3.4 says, never broader. Name the port on both ends with `out=` and `in=`. An input
506
+ nothing in the graph produces goes back to the author, as Q3.3 does.
507
+ - Re-ask Q4.2 over the new edges: does the node that produces the work now see the
508
+ criteria through anything you just drew? Guard every new fork as Q4.7 says. The existing
509
+ `cannot` and `will_not` entries stand; the addition never loosens one.
510
+ - Show back the three things, get a yes, then write.
511
+ 5. **Validate as in "After writing"**, and read every warning back before the author sees it.
512
+ 6. **Name the lineage in `README.md`**: which blueprint was merged in, at which version and
513
+ digest, and which of its nodes now sit in this graph.
514
+ 7. **When a live page is open**, PUT phase `enriched` with the grown bundle and the `hits`.
515
+
516
+ The fetched cards are reused, never rewritten: a card you edit is a new card with a new id at
517
+ `1.0.0`, as Q1.2 says. Nothing here runs the graph or the nodes it gained.
518
+
519
+ ---
520
+
521
+ # Where you stop asking and start writing
522
+
523
+ The stop condition is **the closing of the port ledger, with every fork guarded and Q4.2
524
+ answered**. Ask until all seven of these hold, and not one question longer:
525
+
526
+ 1. every declared input has a named producing node, or an explicit *"arrives with the run"*;
527
+ 2. every declared output has a consumer, or is terminal on a node with `outputs: []`;
528
+ 3. every edge that exists has one unambiguous carried pair, pinned wherever more than one was
529
+ possible;
530
+ 4. every cycle has a cap on one member, written as the number Attractor counts;
531
+ 5. every node with two or more outgoing edges has a `condition` on every arm, or a `weight`
532
+ the author chose;
533
+ 6. every `shell-tool` has its command;
534
+ 7. **Q4.2 has an explicit yes or no.**
535
+
536
+ The first four and the last are the facts `loadBundle` refuses a bundle over. The fifth is
537
+ refused by nothing: an unguarded fork is legal to every validator, and a runner decides it by
538
+ the spelling of the target ids, which is why it is on this list at all. Only a malformed
539
+ `condition` is refused, as `attractor/condition-syntax`. The sixth fails later still: a
540
+ `shell-tool` without its command loads cleanly and fails the first time the runner reaches it.
541
+ Everything past these seven is prose, and prose is your job.
542
+
543
+ ## What you write without asking
544
+
545
+ `action`, `spec`, every port `description`, `dependencies` (**derived from the confirmed edge
546
+ list, never asked**: a declared dependency with no edge is an error and an edge with no
547
+ declaration is a warning, so asking twice invites the two to disagree), `notes`, `version`,
548
+ every `condition`, `blueprint.yaml`, the DOT and `README.md`.
549
+
550
+ Rules for the prose are in `references/writing-cards.md`. The one that is load-bearing: a
551
+ generator's `spec` must not paraphrase the criteria producer's. Under 0.35 3-gram Jaccard, no
552
+ criterion named, no threshold quoted. An absent edge with the criteria written into the prose
553
+ is a false isolation, and the engine measures it.
554
+
555
+ ## Show back exactly three things
556
+
557
+ Not the whole bundle. These three, because they are the three no author can delegate:
558
+
559
+ 1. **the edge list, including the edges deliberately absent and the guard on every fork**,
560
+ with one line per absence saying why it is deliberate;
561
+ 2. **the `cannot` and `will_not` entries, and why each one is in the field it is in**;
562
+ 3. **the `spec` of every node whose work is judged**: the one place a leak can still hide
563
+ after the topology is clean.
564
+
565
+ Get a yes. Then write the files.
566
+
567
+ ## The layout
568
+
569
+ Ask where it goes, then write exactly this:
570
+
571
+ ```
572
+ <slug>/
573
+ topology.dot one digraph, every node pinned card="id@version"
574
+ cards/
575
+ <card-id>@<version>.yaml one per node; reused cards byte for byte
576
+ blueprint.yaml slug, title, summary, description, category, tags
577
+ README.md for a person opening the folder
578
+ ontology/
579
+ extensions.yaml ONLY when the author asked for a local term, and it defines it
580
+ ```
581
+
582
+ Nothing else. No `factory.dot`, no `AGENTS.md`, no second `.dot`, and no card named
583
+ `blueprint.yaml` or `extensions.yaml`: the registry reads roles off filenames and those two
584
+ names are claimed by the manifest and the local vocabulary, so a card called either silently
585
+ disappears from the bundle.
586
+
587
+ `templates/topology.dot`, `templates/card.yaml` and `templates/shell-tool-card.yaml` are
588
+ annotated skeletons. Read them once, then write from the answers rather than filling in
589
+ blanks.
590
+
591
+ ---
592
+
593
+ # After writing
594
+
595
+ ## 1. Validate, in this order
596
+
597
+ Walk `references/preflight.md` first, so you already know what the validator will say. Then
598
+ run it. Three ways, and you take the first that is available:
599
+
600
+ **a. The CLI.** `command -v darkprint` answers; then `darkprint validate <slug>` prints every
601
+ finding with its code and exits 1 only when one of them is an error. When it does not answer
602
+ and the machine has Node and a network, `npx -y darkprint validate <slug>` runs the same
603
+ check: npx fetches the `darkprint` package from npm on the first run and keeps it in its
604
+ cache, and nothing else is installed. Say that in one line before you run it, because it is a
605
+ download. Offline, or without Node, go to b.
606
+
607
+ **b. The registry's validator, anonymous, over HTTP.** Ask first: this sends the whole folder
608
+ to darkprint.io. The registry stores nothing from it and needs no account, but the files
609
+ leave the machine, so say that in one line and get a yes. Then POST them as JSON:
610
+
611
+ ```
612
+ POST https://www.darkprint.io/api/validate/bundle
613
+ content-type: application/json
614
+
615
+ {
616
+ "dot": "<contents of topology.dot>",
617
+ "cardFiles": { "cards/<card-id>@<version>.yaml": "<contents>", ... },
618
+ "manifest": { "slug": "...", "title": "...", "summary": "...", "description": "...", "category": "...", "tags": [...] },
619
+ "vocabulary": "<contents of ontology/extensions.yaml, only if it exists>"
620
+ }
621
+ ```
622
+
623
+ It answers 200 with `diagnostics` (each with `code`, `severity`, `message`, `hint` and a
624
+ `location`) plus the resolved `blueprint` and its `analysis` when the bundle loaded. Nothing
625
+ is stored. A 400 means the body was not that shape; a 413 means it was too large.
626
+
627
+ **c. The upload page.** Ask the author to drop the folder on
628
+ `https://www.darkprint.io/upload`. It runs the same engine in the browser tab and shows the
629
+ same diagnostics beside the drawn graph.
630
+
631
+ Loop on errors: fix, re-run, until no diagnostic has `severity: error`. Then read the warnings
632
+ back to the author **before they see them**, one line each, saying why each is intended:
633
+
634
+ > *"One warning, `analysis/criteria-relayed-through-judge`, because your fixer sits downstream
635
+ > of the judge that holds the criteria. That is the endorsed loop and the engine declines to
636
+ > decide between the two shapes. It charges nothing."*
637
+
638
+ A warning you cannot explain is a defect you have not found yet.
639
+
640
+ ## 2. Hand-off
641
+
642
+ Publishing is the author's step, and it needs their account. Tell them the ways below and
643
+ which one you recommend; for a first release the short path is usually it:
644
+
645
+ **The short path, private by default.** Sign in at `https://www.darkprint.io/welcome`, drop
646
+ the folder on `https://www.darkprint.io/upload`, and press Publish. Visibility defaults to
647
+ private there, so the blueprint is theirs alone until they change it from the blueprint list
648
+ on their profile, and a slug never published before is created by that first publish. When a
649
+ live page is open, PUT phase `published` with `publishedRef` set to `<handle>/<slug>` once the
650
+ upload page reports the release.
651
+
652
+ **From the browser, naming it first.** Sign in at `https://www.darkprint.io/welcome`; create the slug at
653
+ `https://www.darkprint.io/new`, choosing public or private; then drop the folder on
654
+ `https://www.darkprint.io/upload?owner=<handle>&slug=<slug>` and press Publish. The upload
655
+ page re-runs the validator and refuses only errors; every warning you predicted appears
656
+ there exactly as you said it would.
657
+
658
+ **From the terminal, with a write-scoped API key.** The author creates one at
659
+ `https://www.darkprint.io/settings`, and it stays theirs: never ask them to paste it into
660
+ the conversation, and never write it into a file. With the key in their shell as
661
+ `$DARKPRINT_KEY`, the same JSON as the validator's body, plus `ownerHandle`, `slug`,
662
+ `version` and an optional `visibility`, publishes a release:
663
+
664
+ ```
665
+ curl -X POST https://www.darkprint.io/api/bundles \
666
+ -H "Authorization: Bearer $DARKPRINT_KEY" \
667
+ -H "content-type: application/json" \
668
+ --data @publish.json
669
+ ```
670
+
671
+ where `publish.json` is `{ "ownerHandle": "<handle>", "slug": "<slug>", "version": "1.0.0",
672
+ "visibility": "private", "dot": ..., "cardFiles": ..., "manifest": ..., "vocabulary": ... }`.
673
+ The response names the release and its digest, and says whether this call created the
674
+ bundle or appended a release to one that existed: a slug never published before is created
675
+ by its first publish, so this path does not need `/new` first. A 401 is a missing or
676
+ read-only key. A 404 never means "no such slug"; it means `ownerHandle` is not a handle the
677
+ key's account holds, which is the same answer for a misspelled handle and for somebody
678
+ else's, on purpose.
679
+
680
+ Changing visibility afterwards is a separate call.
681
+ `PATCH /api/bundles/<handle>/<slug>/visibility` with `{ "visibility": "public" }`, or
682
+ `"private"`, answers the updated bundle. That route takes a signed-in session rather than a
683
+ key, so the author flips it from the blueprint list on their profile.
684
+
685
+ Either way, say the rest plainly: this skill wrote the files and checked them. It ran no
686
+ node, and it published nothing itself. What happens to the folder from here is the author's
687
+ choice, and a folder they never publish is still a valid blueprint they can hand to whoever
688
+ runs it.