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.
- package/README.md +25 -0
- package/dist/cli.js +15656 -0
- package/package.json +21 -0
- package/skill/darkprint/SKILL.md +688 -0
- package/skill/darkprint/references/card-schema.md +544 -0
- package/skill/darkprint/references/dot-and-attractor.md +300 -0
- package/skill/darkprint/references/live-preview.md +95 -0
- package/skill/darkprint/references/ontology.md +242 -0
- package/skill/darkprint/references/preflight.md +206 -0
- package/skill/darkprint/references/writing-cards.md +178 -0
- package/skill/darkprint/templates/card.yaml +84 -0
- package/skill/darkprint/templates/shell-tool-card.yaml +52 -0
- package/skill/darkprint/templates/topology.dot +66 -0
|
@@ -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.
|