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,206 @@
|
|
|
1
|
+
# Pre-flight
|
|
2
|
+
|
|
3
|
+
The checklist you walk your own output against **before** you run the validator, so that
|
|
4
|
+
nothing it prints surprises you. It is not a substitute for running it: `SKILL.md`'s
|
|
5
|
+
"After writing" names the three ways, and you take the first one available.
|
|
6
|
+
|
|
7
|
+
Work down it in order. Anything marked **error** means the bundle will not load and will
|
|
8
|
+
not publish, and you fix it. Anything marked *warning* the author will see, on the upload
|
|
9
|
+
page or in the validator's output, so **predict it out loud** before they do: an author
|
|
10
|
+
surprised by the upload screen has been failed by the interview.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. The DOT parses
|
|
15
|
+
|
|
16
|
+
- [ ] `digraph`, not `graph`. Every edge `->`, never `--`. `dot/not-directed` **error**
|
|
17
|
+
- [ ] one graph in the file. *`attractor/multiple-graphs`, `dot/unsupported`*
|
|
18
|
+
- [ ] not `strict digraph`. *`attractor/strict-graph`*
|
|
19
|
+
- [ ] `//` or `/* */` comments only. *`attractor/hash-comment`*
|
|
20
|
+
- [ ] attributes comma-separated. *`attractor/attr-separator`*
|
|
21
|
+
- [ ] node ids match `[A-Za-z_][A-Za-z0-9_]*`, unquoted, not a statement keyword.
|
|
22
|
+
*`attractor/bad-node-id`, `attractor/quoted-node-id`*
|
|
23
|
+
- [ ] no node named `start`, `Start`, `exit` or `end`. **No diagnostic**: the exporter renames
|
|
24
|
+
such a node and records the original in `dp_node`, so the compiled file stops saying
|
|
25
|
+
what the topology says. Check by eye
|
|
26
|
+
- [ ] **no `type=` on any node.** *`attractor/reserved-attribute`*, and it is the only reserved
|
|
27
|
+
node attribute the linter reports
|
|
28
|
+
- [ ] no other reserved Attractor node attribute either (`prompt`, `max_retries`, `llm_model`,
|
|
29
|
+
`label`, `shape`, `class`, `timeout`, `goal_gate`, `fidelity`, `thread_id`,
|
|
30
|
+
`retry_target`, `fallback_retry_target`, `llm_provider`, `reasoning_effort`,
|
|
31
|
+
`auto_status`, `allow_partial`). **No diagnostic**: each is written straight into the
|
|
32
|
+
compiled file and configures the run silently. Grep for them yourself
|
|
33
|
+
- [ ] no node declared twice with different attributes. *`dot/duplicate-node`*
|
|
34
|
+
- [ ] no self-loops. *`dot/self-loop`*
|
|
35
|
+
|
|
36
|
+
## 2. Every node finds its card
|
|
37
|
+
|
|
38
|
+
- [ ] every node carries `card="<id>@<MAJOR.MINOR.PATCH>"`. `bundle/unpinned-card` **error**
|
|
39
|
+
- [ ] every pin names a card file the bundle actually carries. `bundle/missing-card` **error**
|
|
40
|
+
- [ ] every card file is instantiated by some node. *`bundle/orphan-card`*
|
|
41
|
+
- [ ] no two card files declare the same `id@version` with different content.
|
|
42
|
+
`bundle/digest-mismatch` **error**
|
|
43
|
+
- [ ] a card reused from the registry is byte for byte the file the registry serves for that
|
|
44
|
+
`id@version`. Same check, same error, on publish
|
|
45
|
+
- [ ] exactly one `.dot`; `blueprint.yaml` at the root is the manifest; no card named
|
|
46
|
+
`blueprint.yaml` or `extensions.yaml`
|
|
47
|
+
|
|
48
|
+
## 3. Every card validates
|
|
49
|
+
|
|
50
|
+
- [ ] YAML parses. `card/parse-error` **error**. The one that bites in practice: a plain
|
|
51
|
+
scalar containing `": "` is read as a nested mapping. `description: Where it failed: the
|
|
52
|
+
line and the text` fails with *"nested mappings are not allowed in compact mappings"*.
|
|
53
|
+
**Quote any value containing a colon-space**, or write it as a `>-` folded block
|
|
54
|
+
- [ ] `id` matches `^(?:ns/)?[a-z0-9]+(-[a-z0-9]+)*$`. `card/bad-id` **error**
|
|
55
|
+
- [ ] `name`, `action`, `spec` present and non-blank; `inputs` and `outputs` **present**, even
|
|
56
|
+
when empty. `card/missing-field` **error**
|
|
57
|
+
- [ ] `version` is full semver. `card/bad-version` **error**
|
|
58
|
+
- [ ] `type` is one concrete `node-type`, never `human-in-the-loop`, `evaluative` or
|
|
59
|
+
`orchestration`. `card/unknown-term`, `card/wrong-term-kind` **error**
|
|
60
|
+
- [ ] every node where a person acts carries a `type` subsumed by `human-in-the-loop`, and
|
|
61
|
+
no node where nobody acts does. `type` is the only field that says it. Writing a
|
|
62
|
+
`requires_human` key is `card/retired-field`, a **warning**, and it is ignored
|
|
63
|
+
- [ ] every `shell-tool` carries a non-blank `params.tool_command`. Missing is
|
|
64
|
+
*`card/missing-field`*, blank is *`card/bad-type`*, and either way the node fails on its
|
|
65
|
+
first execution
|
|
66
|
+
- [ ] every `phase` entry is one of the five, never namespaced, never repeated.
|
|
67
|
+
`card/unknown-phase`, `card/namespaced-phase` **error**, *`card/duplicate-phase`*
|
|
68
|
+
- [ ] every `tools` entry is a `tool` term; every `risk_markers` entry is a `risk-marker` term
|
|
69
|
+
- [ ] port names unique within their side. `card/duplicate-port` **error**
|
|
70
|
+
- [ ] `required:` only on inputs
|
|
71
|
+
- [ ] every `spec` over 40 characters, and written as an instruction rather than a label.
|
|
72
|
+
*`card/spec-too-thin`*
|
|
73
|
+
- [ ] the card names no vocabulary version. There is one vocabulary and every card is read
|
|
74
|
+
against it, so writing an `ontology_version` key is `card/retired-field`, a
|
|
75
|
+
**warning**, and it is ignored
|
|
76
|
+
- [ ] no key outside the accepted set (`references/card-schema.md`). A typo is an `info` and
|
|
77
|
+
is **silently ignored**, so check the spelling of `risk_markers` and `will_not` by eye
|
|
78
|
+
|
|
79
|
+
## 4. Every edge carries something
|
|
80
|
+
|
|
81
|
+
- [ ] no edge out of a node whose `outputs` is `[]`, and no edge into a node whose `inputs` is
|
|
82
|
+
`[]`. `bundle/port-mismatch` **error**, *"carries no data"*
|
|
83
|
+
- [ ] every `out=` / `in=` pin names a port the card actually declares.
|
|
84
|
+
`bundle/port-mismatch` **error**
|
|
85
|
+
- [ ] every edge has at least one compatible pair. Source narrower than target, or equal, or
|
|
86
|
+
either side `any`. `bundle/type-mismatch` **error**
|
|
87
|
+
- [ ] every edge where more than one pair would fit is **pinned**. *`bundle/port-ambiguous`*,
|
|
88
|
+
and an unpinned ambiguous edge means the prohibition check is reasoning about the
|
|
89
|
+
resolver's declaration-order guess rather than about your intent
|
|
90
|
+
|
|
91
|
+
## 5. Every fork is guarded
|
|
92
|
+
|
|
93
|
+
- [ ] every node with two or more outgoing edges has a `condition` on every arm, or a
|
|
94
|
+
`weight` the author chose on purpose. **No diagnostic**: an unguarded fork is legal
|
|
95
|
+
and the runner decides it by the spelling of the target ids, so a loop whose fix arm
|
|
96
|
+
sorts before its ship arm never ships. Read `references/dot-and-attractor.md`, Part 2
|
|
97
|
+
- [ ] every `condition` uses only the keys `outcome`, `preferred_label` or `context.<path>`,
|
|
98
|
+
the operators `=` and `!=`, and `&&` between clauses. *`attractor/condition-syntax`*
|
|
99
|
+
here, and an error to the runner, which refuses the whole pipeline over it
|
|
100
|
+
- [ ] no `condition=""`. It is no condition at all, and the edge falls back to the order
|
|
101
|
+
above without anything saying so
|
|
102
|
+
- [ ] the check's success arm is `outcome=success` and its other arm is `outcome!=success`,
|
|
103
|
+
so a verdict that is neither lands on the arm that does not ship
|
|
104
|
+
|
|
105
|
+
## 6. The prohibitions
|
|
106
|
+
|
|
107
|
+
- [ ] every `cannot` entry names a `data-type` term id. A sentence here is
|
|
108
|
+
`card/unknown-term` **error** and the card does not load
|
|
109
|
+
- [ ] every `will_not` entry is a sentence. A `data-type` here is
|
|
110
|
+
*`card/prohibition-misfiled`*, and it means a rule the resolver could have enforced
|
|
111
|
+
is sitting where nothing reads it
|
|
112
|
+
- [ ] no edge carries into a node a type its `cannot` refuses. `bundle/prohibition-violated`
|
|
113
|
+
**error**. Two directions to keep straight:
|
|
114
|
+
- **subsumption**: `cannot: [structured]` refuses an incoming `acceptance-criteria`;
|
|
115
|
+
`cannot: [acceptance-criteria]` does **not** refuse an incoming `structured`;
|
|
116
|
+
- **the carrier**: with no `out=` pin, the carriers are *every output of the source
|
|
117
|
+
card*, so the edge is refused if any of them is prohibited. With an `out=` pin, only
|
|
118
|
+
that one port counts, so a pinned edge can slip past a prohibition the unpinned one
|
|
119
|
+
would have tripped. It still gets charged by the analyzer, which reads the graph at
|
|
120
|
+
node level; `cannot` is the tripwire, not the whole guard
|
|
121
|
+
- [ ] no output typed `any` upstream of a node with a narrower prohibition. `any` never
|
|
122
|
+
violates anything and the whole mechanism goes inert
|
|
123
|
+
|
|
124
|
+
## 7. Dependencies
|
|
125
|
+
|
|
126
|
+
- [ ] every `dependencies` entry has a matching incoming edge. `bundle/missing-dependency`
|
|
127
|
+
**error**
|
|
128
|
+
- [ ] every incoming edge is listed in the target card's `dependencies`.
|
|
129
|
+
*`bundle/undeclared-dependency`*
|
|
130
|
+
- [ ] entries name the supplier's **card id** or its **DOT node id**; either satisfies the check
|
|
131
|
+
|
|
132
|
+
## 8. Structure
|
|
133
|
+
|
|
134
|
+
- [ ] at least one node with no incoming edge. *`bundle/no-entry`*
|
|
135
|
+
- [ ] at least one node with no outgoing edge, and it declares `outputs: []`. *`bundle/no-exit`*
|
|
136
|
+
- [ ] every node reachable from a source. *`bundle/unreachable-node`*
|
|
137
|
+
- [ ] the graph is not empty. *`analysis/empty-graph`*
|
|
138
|
+
|
|
139
|
+
## 9. The checks that decide the security level
|
|
140
|
+
|
|
141
|
+
- [ ] **at least one node typed `validation`.** Without it the generator set is empty and the
|
|
142
|
+
criteria check does not run. *`analysis/criteria-leak-unanchored`* when a producer
|
|
143
|
+
exists, silence when neither leg does, and a 4 is silence either way
|
|
144
|
+
- [ ] **at least one output port typed `acceptance-criteria`.** Without it the producer set is
|
|
145
|
+
empty, same outcome
|
|
146
|
+
- [ ] **no edge from the criteria producer into any node whose work is judged, whatever port
|
|
147
|
+
that edge carries.** The topological walk reads the graph at node level. A brief that
|
|
148
|
+
needs to reach the builder arrives with the run, not over an edge
|
|
149
|
+
- [ ] the criteria producer does not also emit the artefact the judge reads. One node writing
|
|
150
|
+
both is charged off the declarations alone
|
|
151
|
+
- [ ] every generator's `spec` shares under 0.35 3-gram Jaccard with the producer's, names no
|
|
152
|
+
criterion and quotes no threshold. *`analysis/criteria-leak-suspected`*
|
|
153
|
+
- [ ] no `params` key matching `/criteri/i` naming something nothing in the graph produces.
|
|
154
|
+
*`analysis/criteria-out-of-band`*
|
|
155
|
+
- [ ] every cycle has `max_retries` as a **top-level** key of `params` on one member, holding
|
|
156
|
+
a non-negative integer that counts the attempts **after** the first. `max_iterations`
|
|
157
|
+
and `maxIterations` are accepted aliases for the same number. Nested, it is not read,
|
|
158
|
+
and the cycle takes `unbounded-loop` on every member
|
|
159
|
+
- [ ] every node with `web-search`, `http-fetch`, `sql` or `ci` in `tools` either has no
|
|
160
|
+
successors or hands to `validation` nodes only. Otherwise
|
|
161
|
+
`unvalidated-external-access` is inferred, −1.0, whether or not it is declared
|
|
162
|
+
- [ ] you have **not** inserted a validation node purely to silence a marker
|
|
163
|
+
|
|
164
|
+
## 10. The manifest
|
|
165
|
+
|
|
166
|
+
- [ ] `blueprint.yaml` carries `slug`, `title`, `summary`, `description`, `category` and
|
|
167
|
+
`tags`, and `slug` is the folder name and the name the author creates at `/new`
|
|
168
|
+
- [ ] `summary` is one sentence, because it becomes the compiled pipeline's `goal`
|
|
169
|
+
|
|
170
|
+
## 11. Predict the validator
|
|
171
|
+
|
|
172
|
+
Write out, for the author, the warnings you expect and why each is intended. The common
|
|
173
|
+
honest ones:
|
|
174
|
+
|
|
175
|
+
| warning | when it is fine |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `analysis/criteria-relayed-through-judge` | the fixer sits downstream of the judge that holds the criteria. The engine cannot tell `judge → fixer → judge` from `judge → builder → judge` and declines to decide. Charges nothing |
|
|
178
|
+
| `bundle/undeclared-dependency` | never fine. Fix it |
|
|
179
|
+
| `bundle/no-entry` / `bundle/no-exit` | never fine in a first emit. A blueprint with no source and no sink is a loop with no way in |
|
|
180
|
+
| `bundle/port-ambiguous` | never fine. Pin the edge |
|
|
181
|
+
| `attractor/condition-syntax` | never fine. The runner refuses the whole pipeline over it |
|
|
182
|
+
|
|
183
|
+
And name the two figures the author will read. The autonomy class is read twice off `type`
|
|
184
|
+
and the weaker reading is the one that lands in a band: the share of all nodes not subsumed
|
|
185
|
+
by `human-in-the-loop`, and the same share taken over the **control points** only (the nodes
|
|
186
|
+
under `evaluative` or `orchestration`, plus `human-gate`), which decides a band only from two
|
|
187
|
+
control points up. A graph with one `human-gate` among its two control points reads 0.5 on
|
|
188
|
+
the second axis and lands a band lower than the first axis alone would say. The bands are in
|
|
189
|
+
`references/ontology.md`. The security level is 4 minus the sum of the distinct markers,
|
|
190
|
+
each charged once.
|
|
191
|
+
|
|
192
|
+
## 12. Re-emitting
|
|
193
|
+
|
|
194
|
+
If this is a second pass over a bundle that already exists:
|
|
195
|
+
|
|
196
|
+
- [ ] a card whose content changed has a **new version**, and the old file is **deleted**, not
|
|
197
|
+
left beside it. `bundle/digest-mismatch` **error**, *`bundle/orphan-card`*
|
|
198
|
+
- [ ] the bump is large enough. `card/version-bump-too-small` **error**. The full table is in
|
|
199
|
+
`references/card-schema.md`; the rows that bite in a second pass are these. **Major**:
|
|
200
|
+
a port removed, renamed or retyped, an input made required or a required input added,
|
|
201
|
+
`type` or `id` changed, a `cannot` entry added, a `will_not` entry withdrawn. **Minor**:
|
|
202
|
+
`spec`, `model` or `skill` changed, an optional port added, a tool, MCP server, param
|
|
203
|
+
key, risk marker or dependency added, a phase added or dropped, a `cannot` withdrawn, a
|
|
204
|
+
`will_not` stated. **Patch**: wording, a param's value, a reorder, anything withdrawn
|
|
205
|
+
from `tools`, `mcp`, `risk_markers`, `dependencies` or `params`
|
|
206
|
+
- [ ] every DOT pin updated to the new version
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Writing the prose
|
|
2
|
+
|
|
3
|
+
The author decides the shape. You write the words. Four card fields carry prose (`action`,
|
|
4
|
+
`spec`, port `description`, `notes`), and two files beside the cards do (`blueprint.yaml`,
|
|
5
|
+
`README.md`).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## `action`
|
|
10
|
+
|
|
11
|
+
One sentence, imperative, naming the operation and the artefact. It is the line a reader
|
|
12
|
+
scans; it is not the instruction the agent receives.
|
|
13
|
+
|
|
14
|
+
> Turn the incoming request into two separate artefacts: an ordered brief, and the acceptance
|
|
15
|
+
> criteria the finished work will be judged against.
|
|
16
|
+
|
|
17
|
+
If it contains an "and" naming two different artefacts *and* the node emits both, check
|
|
18
|
+
against the criteria rule below before you keep it.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## `spec`, the one that matters
|
|
23
|
+
|
|
24
|
+
**`spec` is compiled into the node's `prompt`.** It is delivered verbatim to an agent that
|
|
25
|
+
does not see the rest of the graph, so it has to be self-sufficient, and it has to respect
|
|
26
|
+
the isolation the topology declares, because an absent edge with the criteria paraphrased into
|
|
27
|
+
the prose is a false isolation. The engine measures that; it does not take your word for it.
|
|
28
|
+
|
|
29
|
+
Aim for **150 to 400 words**. Under 40 characters is `card/spec-too-thin`. Under three words is
|
|
30
|
+
excluded from the similarity comparison entirely, so a placeholder card is both useless and
|
|
31
|
+
unchecked.
|
|
32
|
+
|
|
33
|
+
Write it as:
|
|
34
|
+
|
|
35
|
+
1. **What arrives.** Name the input ports and say what is in them.
|
|
36
|
+
2. **What to do**, in the order it is done.
|
|
37
|
+
3. **What to emit, on which port**, and in what form.
|
|
38
|
+
4. **What not to do**: the boundaries from Q0.5 and Q4.3, in the second person, the same
|
|
39
|
+
sentences that went into `will_not`.
|
|
40
|
+
|
|
41
|
+
For a `shell-tool`, the `spec` still says what arrives and what is emitted; the command in
|
|
42
|
+
`params.tool_command` is what runs. For a node inside a cycle, the `spec` states the total
|
|
43
|
+
number of attempts in words, and `params.max_retries` holds that total minus one.
|
|
44
|
+
|
|
45
|
+
### The rule you cannot break
|
|
46
|
+
|
|
47
|
+
A node whose work is judged must not paraphrase the criteria producer's `spec`. The engine
|
|
48
|
+
computes 3-gram Jaccard similarity between the two and warns above **0.35**
|
|
49
|
+
(`analysis/criteria-leak-suspected`).
|
|
50
|
+
|
|
51
|
+
Concretely, in a generator's `spec`:
|
|
52
|
+
|
|
53
|
+
- name no criterion,
|
|
54
|
+
- quote no threshold,
|
|
55
|
+
- do not restate the planner's sentences in your own words,
|
|
56
|
+
- do not describe how the work will be checked.
|
|
57
|
+
|
|
58
|
+
The starter blueprint's builder spec measures **0.0356** against its planner's. That is what
|
|
59
|
+
independent prose looks like. If you find yourself writing "make sure it passes the checks",
|
|
60
|
+
you have just written the leak into the file.
|
|
61
|
+
|
|
62
|
+
The reason, stated to the author when they push back: whoever writes the work must not see the
|
|
63
|
+
acceptance tests, because a node that sees them writes toward them, and the check stops
|
|
64
|
+
measuring the work and starts measuring the aim.
|
|
65
|
+
|
|
66
|
+
### Do not write one house style and vary it per node
|
|
67
|
+
|
|
68
|
+
Six specs sharing a third of their trigrams cross the threshold against each other. Write each
|
|
69
|
+
one about its own job, in its own words.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Port `description`
|
|
74
|
+
|
|
75
|
+
One line. What is in it, and who it came from, not what the receiving node should do with it.
|
|
76
|
+
|
|
77
|
+
> The ordered build steps the run was instantiated with, the only thing this node sees.
|
|
78
|
+
|
|
79
|
+
A one-line description is where the YAML trap lands, because a colon is the natural way to
|
|
80
|
+
write one. A plain scalar containing `": "` is parsed as a nested mapping and the whole card
|
|
81
|
+
fails with `card/parse-error`. Quote it, or fold it:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
description: Where it failed: the line and the text # BREAKS the card
|
|
85
|
+
description: "Where it failed: the line and the text" # fine
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## `notes`
|
|
91
|
+
|
|
92
|
+
Optional, and absence carries no judgement. Use it for the thing a reader would otherwise
|
|
93
|
+
misread as an oversight: the edge you did not draw, the marker you deliberately did not
|
|
94
|
+
declare and why, the cap that is load-bearing twice over.
|
|
95
|
+
|
|
96
|
+
This is the right place to record a judgement call that could have gone the other way, with
|
|
97
|
+
what it would cost if it did:
|
|
98
|
+
|
|
99
|
+
> No `irreversible-action`, deliberately: this node writes one tagged artefact to the run's own
|
|
100
|
+
> release target, sends nothing to a third party and deletes nothing. If a reviewer decides a
|
|
101
|
+
> release target counts as publication, the marker costs this blueprint 1.5 and takes its
|
|
102
|
+
> security level from 4 to 3.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## `blueprint.yaml`
|
|
107
|
+
|
|
108
|
+
The manifest, derived from the interview (SKILL.md, Q5.3) and shown back before it is
|
|
109
|
+
written:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
slug: help-centre-line
|
|
113
|
+
title: Help Centre Article Line
|
|
114
|
+
summary: >-
|
|
115
|
+
Outline, draft and check help-centre articles against acceptance criteria the drafter
|
|
116
|
+
never sees, with one human approval before anything publishes.
|
|
117
|
+
description: |-
|
|
118
|
+
One paragraph per node in the order a run visits them, in the author's words from Q2.1,
|
|
119
|
+
ending with the edge that is deliberately absent and why.
|
|
120
|
+
category: Content
|
|
121
|
+
tags:
|
|
122
|
+
- help-centre
|
|
123
|
+
- editorial
|
|
124
|
+
- isolation
|
|
125
|
+
- human-gate
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`summary` is one sentence, because it becomes the compiled pipeline's `goal`: the one line a
|
|
129
|
+
runner reads about the whole graph. `slug` is the folder name, the name the author creates
|
|
130
|
+
at `/new`, and the graph name in card-id grammar. Nothing else goes in the file; the
|
|
131
|
+
registry fills in the author and the dates when it publishes.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## `README.md`
|
|
136
|
+
|
|
137
|
+
For a person opening the folder. Written from the answers, not from a template with the
|
|
138
|
+
blanks filled, and in the shape the registry writes for every published bundle, so a folder
|
|
139
|
+
that came from this skill and a folder downloaded from the site read the same way:
|
|
140
|
+
|
|
141
|
+
1. **What the blueprint does**, in the author's own sentence from Q0.1, and the blueprint it
|
|
142
|
+
started from if Phase 1 found one.
|
|
143
|
+
2. **The node table**: DOT id, card `id@version`, type, phase, one line each.
|
|
144
|
+
3. **The edges, including the ones deliberately absent**, with a line per absence saying why,
|
|
145
|
+
and the guard on every fork. This is the section that earns the file.
|
|
146
|
+
4. **What is in the folder**: `topology.dot`, `cards/`, `blueprint.yaml`, and
|
|
147
|
+
`ontology/extensions.yaml` when there is one.
|
|
148
|
+
5. **What the validator said**: which validator ran (`darkprint validate`, the
|
|
149
|
+
`/api/validate/bundle` route, or the upload page), the autonomy class and the security
|
|
150
|
+
level it computed, and each warning with the reason it is intended. Never claim the bundle
|
|
151
|
+
validated cleanly without naming which of the three said so.
|
|
152
|
+
6. **What these files leave to the runner**: that ports, `cannot`, `will_not` and
|
|
153
|
+
`risk_markers` are enforced when the bundle is validated and by nothing in the compiled
|
|
154
|
+
pipeline, and that every Attractor attribute a card has no field for (goal gates, timeouts,
|
|
155
|
+
the retry policy above `max_retries`) falls to the runner's own default.
|
|
156
|
+
7. **How to check it again**: drop the folder on `https://www.darkprint.io/upload`.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Re-emitting an existing bundle
|
|
161
|
+
|
|
162
|
+
A published card version is never edited in place.
|
|
163
|
+
|
|
164
|
+
- Content changed ⇒ new version, old file **deleted**, DOT pin updated.
|
|
165
|
+
- The bump has to be at least what the engine infers. Under-bumping is
|
|
166
|
+
`card/version-bump-too-small`, an error. The full table is in `references/card-schema.md`.
|
|
167
|
+
The rows that catch people: a port removed, renamed or retyped, a required input added, a
|
|
168
|
+
`cannot` entry added or a `will_not` entry withdrawn are all **major**; `spec`, `model` and
|
|
169
|
+
a new tool, param key, risk marker or dependency are **minor**; a param's *value*, wording
|
|
170
|
+
and reorders are **patch**.
|
|
171
|
+
- Leaving the superseded file in `cards/` is `bundle/orphan-card` at best and
|
|
172
|
+
`bundle/digest-mismatch` at worst.
|
|
173
|
+
- A card reused from the registry is never re-emitted by you at all. If it needs to change,
|
|
174
|
+
it is a new card with a new id, or the author publishes the change under their own name
|
|
175
|
+
and the DOT pins that.
|
|
176
|
+
- When a card's identity is a parameter (the starter encodes its debugger's attempt count in
|
|
177
|
+
the minor version) keep that convention. Two caps behind one pin would be two contents
|
|
178
|
+
behind one reference.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# cards/<card-id>@<version>.yaml
|
|
2
|
+
#
|
|
3
|
+
# The wire format is snake_case. Every key the validator accepts is listed in
|
|
4
|
+
# references/card-schema.md; anything outside that set is silently ignored, so a typo in
|
|
5
|
+
# `risk_markers` does nothing rather than failing loudly.
|
|
6
|
+
#
|
|
7
|
+
# This is the skeleton for a node instructed by prose (`agent`, `tool`, `validation`,
|
|
8
|
+
# `decision`, `human-gate`, `human-input`). A node that IS a command is `shell-tool` and
|
|
9
|
+
# has its own skeleton in `shell-tool-card.yaml`, because it owes `params.tool_command`.
|
|
10
|
+
#
|
|
11
|
+
# NEVER name a card file `blueprint.yaml` or `extensions.yaml`. Those names are claimed by
|
|
12
|
+
# the manifest and the local vocabulary, and a card called either disappears from the bundle.
|
|
13
|
+
#
|
|
14
|
+
# YAML trap, hit every time: a plain scalar containing ": " is parsed as a nested mapping and
|
|
15
|
+
# the whole card fails with card/parse-error. Quote it, or use a `>-` folded block.
|
|
16
|
+
# description: Where it failed: the line and the text <- BREAKS the card
|
|
17
|
+
# description: "Where it failed: the line and the text" <- fine
|
|
18
|
+
|
|
19
|
+
# ---- identity ----
|
|
20
|
+
id: card-id # ^(?:namespace/)?[a-z0-9]+(-[a-z0-9]+)*$ lowercase, hyphens, no underscores
|
|
21
|
+
name: Human Readable Name
|
|
22
|
+
type: agent # exactly one node-type term. Never an abstract category.
|
|
23
|
+
phase: implementation # optional and repeatable; omit it entirely when the node sits in none of the five
|
|
24
|
+
|
|
25
|
+
# ---- behaviour ----
|
|
26
|
+
action: >-
|
|
27
|
+
One imperative sentence naming the operation and the artefact.
|
|
28
|
+
spec: >-
|
|
29
|
+
150-400 words. This is compiled into the node's `prompt` and handed verbatim to an agent
|
|
30
|
+
that does not see the rest of the graph, so it must be self-sufficient: what arrives, what
|
|
31
|
+
to do in order, what to emit on which port and in what form, and what not to do. If this
|
|
32
|
+
node's work is judged, it must name no criterion, quote no threshold and paraphrase nothing
|
|
33
|
+
the criteria producer wrote. The engine measures the overlap and warns above 0.35.
|
|
34
|
+
model: claude-sonnet-5 # optional. A default the runner can override, not a binding.
|
|
35
|
+
agent: Role Name # optional
|
|
36
|
+
tools: [] # tool terms. web-search / http-fetch / sql / ci infer a risk marker.
|
|
37
|
+
mcp: [] # installed server names. Free text, checked against nothing.
|
|
38
|
+
skill: skills/card-id.md # optional pointer; nothing in the engine reads what it points at
|
|
39
|
+
params:
|
|
40
|
+
# The iteration cap, when this node sits in a cycle. TOP LEVEL only: nested it is not read,
|
|
41
|
+
# and the whole cycle takes -1.5 with no obvious cause. It counts the attempts AFTER the
|
|
42
|
+
# first, so 2 means three runs in all. Say the total in `spec` and make the two agree.
|
|
43
|
+
# `max_iterations` and `maxIterations` are accepted aliases and mean the same number.
|
|
44
|
+
max_retries: 2
|
|
45
|
+
|
|
46
|
+
# ---- interfaces ----
|
|
47
|
+
inputs: # must be PRESENT. Write [] when the node needs nothing.
|
|
48
|
+
- name: brief
|
|
49
|
+
type: plan # a data-type term. Do not reach for `any`.
|
|
50
|
+
description: What is in it and who it came from.
|
|
51
|
+
outputs: # must be PRESENT. [] is how a sink is declared.
|
|
52
|
+
- name: build
|
|
53
|
+
type: code
|
|
54
|
+
description: What is handed over.
|
|
55
|
+
dependencies: # the card ids or DOT node ids of the suppliers, derived from the edges
|
|
56
|
+
- upstream-card-id
|
|
57
|
+
cannot: # data-type term ids ONLY. Anything else is card/unknown-term.
|
|
58
|
+
# ENFORCED: the resolver refuses any incoming edge whose carrier is that type or anything
|
|
59
|
+
# narrower, with bundle/prohibition-violated. On an unpinned edge the carriers are EVERY
|
|
60
|
+
# output of the source card; with `out=` only that port.
|
|
61
|
+
- acceptance-criteria
|
|
62
|
+
will_not: # your own sentences. Nothing checks them, and nothing can.
|
|
63
|
+
# Addressed to whoever reads the card and to the agent instantiated from it. Putting a
|
|
64
|
+
# data-type here is card/prohibition-misfiled, a warning: the card loads and no edge is
|
|
65
|
+
# ever checked against the rule you meant to state.
|
|
66
|
+
- read the checks the work will be run against
|
|
67
|
+
|
|
68
|
+
# ---- evaluation metadata ----
|
|
69
|
+
# Whether a person acts here is not a field. It is `type`: human-gate and human-input are
|
|
70
|
+
# subsumed by human-in-the-loop and every other type is not. Writing `requires_human` here
|
|
71
|
+
# is card/retired-field, a warning, and the key is ignored.
|
|
72
|
+
risk_markers: [] # risk-marker terms. unbounded-loop, unvalidated-external-access and
|
|
73
|
+
# criteria-leak are inferred from the graph whether declared or not.
|
|
74
|
+
notes: >-
|
|
75
|
+
Optional. The place to record the thing a reader would otherwise misread as an oversight:
|
|
76
|
+
the edge that is deliberately absent, the marker deliberately not declared and what it would
|
|
77
|
+
cost if the judgement went the other way.
|
|
78
|
+
|
|
79
|
+
# ---- service fields ----
|
|
80
|
+
version: 1.0.0 # full semver. A published version is never edited in place.
|
|
81
|
+
author: your-handle # optional
|
|
82
|
+
# No ontology_version. There is one vocabulary and every card is read against it. The
|
|
83
|
+
# vocabulary carries no version of its own. Writing the key is card/retired-field, a
|
|
84
|
+
# warning.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# cards/<card-id>@<version>.yaml, for a node that IS a command.
|
|
2
|
+
#
|
|
3
|
+
# `type: shell-tool` is a kind of `tool`, so every rule about tool nodes still holds, and it
|
|
4
|
+
# compiles to the Attractor handler that runs `params.tool_command` and fails the node before
|
|
5
|
+
# doing anything else when the command is missing or blank. A missing command is
|
|
6
|
+
# card/missing-field and a blank one is card/bad-type, both warnings, both meaning the node
|
|
7
|
+
# resolves and then fails on its first execution. A node instructed by prose alone is
|
|
8
|
+
# `type: tool`, and takes the skeleton in `card.yaml`.
|
|
9
|
+
#
|
|
10
|
+
# The same YAML trap as every card: quote any value containing ": ".
|
|
11
|
+
|
|
12
|
+
# ---- identity ----
|
|
13
|
+
id: test-runner
|
|
14
|
+
name: Test Runner
|
|
15
|
+
type: shell-tool
|
|
16
|
+
phase: testing
|
|
17
|
+
|
|
18
|
+
# ---- behaviour ----
|
|
19
|
+
action: >-
|
|
20
|
+
Run the project's test suite against the build and emit the runner's own report.
|
|
21
|
+
spec: >-
|
|
22
|
+
The build arrives on the `build` port as a checked-out tree. Run the command below from
|
|
23
|
+
its root and capture everything it prints. Emit the whole output on the `report` port,
|
|
24
|
+
exit status first, with nothing summarised and nothing removed. Do not modify the tree,
|
|
25
|
+
do not install anything, and do not retry a command that failed.
|
|
26
|
+
tools: [shell] # the permission the command needs, as a tool term
|
|
27
|
+
params:
|
|
28
|
+
# The exact line the runner executes. Fixed in advance, so it is not arbitrary code
|
|
29
|
+
# execution: declare that marker only when the command runs something decided at run
|
|
30
|
+
# time, such as a script the previous node wrote.
|
|
31
|
+
tool_command: npm test
|
|
32
|
+
|
|
33
|
+
# ---- interfaces ----
|
|
34
|
+
inputs:
|
|
35
|
+
- name: build
|
|
36
|
+
type: code
|
|
37
|
+
description: The tree to test, as the builder handed it over.
|
|
38
|
+
outputs:
|
|
39
|
+
- name: report
|
|
40
|
+
type: report
|
|
41
|
+
description: The runner's own output, exit status included.
|
|
42
|
+
dependencies:
|
|
43
|
+
- code-builder
|
|
44
|
+
cannot: []
|
|
45
|
+
will_not:
|
|
46
|
+
- modify the tree under test
|
|
47
|
+
|
|
48
|
+
# ---- evaluation metadata ----
|
|
49
|
+
risk_markers: []
|
|
50
|
+
|
|
51
|
+
# ---- service fields ----
|
|
52
|
+
version: 1.0.0
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// A skeleton, not a form to fill in blindly. Every line here is a decision the interview
|
|
2
|
+
// has already made. Delete what does not apply.
|
|
3
|
+
//
|
|
4
|
+
// Comments are `//` or `/* */`. A `#` comment trips `attractor/hash-comment`.
|
|
5
|
+
// Attributes are COMMA-separated. Node ids match [A-Za-z_][A-Za-z0-9_]* and are never quoted.
|
|
6
|
+
// NEVER write `type=` on a node: that is Attractor's handler override, not DarkPrint's
|
|
7
|
+
// ontology type, which lives in the card.
|
|
8
|
+
|
|
9
|
+
digraph blueprint_name {
|
|
10
|
+
rankdir=LR;
|
|
11
|
+
node [shape=box, style=rounded];
|
|
12
|
+
|
|
13
|
+
/* ---- nodes: the id is the instance, the card is the definition ----
|
|
14
|
+
Always pin `card="<id>@<MAJOR.MINOR.PATCH>"`. The bare `version=` fallback makes the node
|
|
15
|
+
id double as the card id, which only works for a single lowercase word. */
|
|
16
|
+
|
|
17
|
+
plan_step [card="plan-step@1.0.0"];
|
|
18
|
+
build_step [card="build-step@1.0.0"];
|
|
19
|
+
check_step [card="check-step@1.0.0"];
|
|
20
|
+
fix_step [card="fix-step@1.0.0"];
|
|
21
|
+
ship_step [card="ship-step@1.0.0"];
|
|
22
|
+
|
|
23
|
+
/* ---- edges: pin the ports whenever more than one pair would fit ----
|
|
24
|
+
`out=` names a port on the source card, `in=` a port on the target card. Neither is
|
|
25
|
+
reserved by Attractor, so both ride along harmlessly in the compiled pipeline. */
|
|
26
|
+
|
|
27
|
+
plan_step -> check_step [label="acceptance criteria", out="criteria", in="criteria"];
|
|
28
|
+
build_step -> check_step [label="build", out="build", in="build"];
|
|
29
|
+
|
|
30
|
+
/* ---- THE FORK, and the guard on each arm ----
|
|
31
|
+
`check_step` has two exits. A runner picks one in a fixed order: a `condition` that
|
|
32
|
+
holds beats every unguarded edge; then an edge whose `label` matches the label the node
|
|
33
|
+
asked for; then a next id the node suggested; then the higher `weight`; and on a tie,
|
|
34
|
+
the target id that sorts first. Two bare edges out of one node are therefore a branch
|
|
35
|
+
decided by the spelling of the node names. `fix_step` sorts before `ship_step`, so an
|
|
36
|
+
unguarded version of this loop repairs until the cap is spent and never ships.
|
|
37
|
+
|
|
38
|
+
`outcome` is set by the runner from the node's own status on every pass. One key against
|
|
39
|
+
its own negation is the only total split the guard grammar can make: it has `=` and
|
|
40
|
+
`!=`, clauses joined by `&&`, and nothing else. */
|
|
41
|
+
check_step -> fix_step [label="failure evidence", style=dashed, condition="outcome!=success", out="evidence", in="evidence"];
|
|
42
|
+
check_step -> ship_step [label="approved", condition="outcome=success", out="approved", in="release"];
|
|
43
|
+
|
|
44
|
+
// The loop returns to the CHECK, never to the builder: the work already done is preserved
|
|
45
|
+
// and the builder stays isolated from every fact about the failures for the whole run.
|
|
46
|
+
// The cap is a TOP-LEVEL `params.max_retries` on one member, counting the attempts after
|
|
47
|
+
// the first: `max_retries: 2` is three runs in all.
|
|
48
|
+
fix_step -> check_step [label="patch", out="patch", in="build"];
|
|
49
|
+
|
|
50
|
+
/* ---- THE EDGE THAT IS NOT HERE ----
|
|
51
|
+
Nothing runs from `plan_step` to `build_step`. The criteria reach the node that JUDGES the
|
|
52
|
+
work and never the node that PRODUCES it.
|
|
53
|
+
|
|
54
|
+
The topological half of the `criteria-leak` check reads the graph at NODE level: any edge
|
|
55
|
+
at all from the criteria producer into a judged node establishes the marker, whichever
|
|
56
|
+
port that edge carries. So the build brief is handed to `build_step` when the graph is
|
|
57
|
+
instantiated, as a source node with an input port and no incoming edge, rather than
|
|
58
|
+
over an edge from the planner.
|
|
59
|
+
|
|
60
|
+
The other end of the same rule is written in the card: `build-step` lists
|
|
61
|
+
`acceptance-criteria` under `cannot`, so drawing that edge unpinned fails the bundle with
|
|
62
|
+
`bundle/prohibition-violated` rather than loading quietly and scoring 2. Pinning it to
|
|
63
|
+
`out="brief"` would satisfy the prohibition, because the carrier is then that one port,
|
|
64
|
+
and the analyzer would still charge criteria-leak, because its walk reads the graph at
|
|
65
|
+
node level. Two guards, and they answer different questions. */
|
|
66
|
+
}
|