orchestrix-skills 0.9.0 → 0.11.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-skills",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Capability-first AI development skill graph (Anthropic-native): plan → build with a warm-context orchestrator, contract-wired skills, and independent verification.",
5
5
  "author": "Orchestrix",
6
6
  "homepage": "https://orchestrix-mcp.youlidao.ai",
package/README.md CHANGED
@@ -16,19 +16,43 @@ intent
16
16
  └─ orchestrate (root: warm context, wires skills by output→input, enforces gates)
17
17
  ├─ brainstorm ──(needs facts?)─→ research
18
18
  ├─ (existing repo?) ──→ map-codebase (brownfield entry: evidence-based map → registry)
19
- ├─ (has UI?) ──→ design-system (once) → design-ui
19
+ ├─ (has UI?) ──→ design-directions (human picks a rendered direction) → design-system (once) → design-ui
20
20
  ├─ (arch decision?) ──→ design-architecture
21
21
  ├─ draft-story → implement → run-tests → review-code → commit
22
22
  │ ↑ verify ↑ design-review (UI only)
23
23
  │ (objective) ↑ accept (batched)
24
24
  ├─ (verify failing, cause unknown?) ──→ investigate (root cause → rework)
25
25
  ├─ (runnable app?) ──→ smoke-test (drive real flows, evidence captured)
26
+ ├─ (team mode?) ──→ pull-request after commit (PR on GitHub, MR on GitLab; CI red re-enters rework)
26
27
  └─ (accepted + ship it?) ──→ deploy (inline gate, rollback-first)
27
28
  ```
28
29
 
29
30
  Human gates are front-loaded (planning = direction) and at the end (acceptance);
30
31
  the build loop runs lights-out, gated only by objective `verify`.
31
32
 
33
+ ## Team mode
34
+
35
+ Uncomment the `collaboration:` block in `core-config.yaml` and the same loop
36
+ runs for a team. Planning happens once, on a planning branch, and reaches the
37
+ base branch through a PR. Each story is then one run on its own branch
38
+ (`story/<origin>/<slug>`) in its own workspace — a `git worktree` when several
39
+ sessions share a machine, a clone elsewhere — and ends with `pull-request`
40
+ instead of a local commit. Merging stays a reviewer's act in the forge.
41
+
42
+ There is no board file. A story's state is derived from git and the forge each
43
+ time it is needed: a remote branch means claimed, an open PR means in review, a
44
+ merged PR means done. Claiming is one push that the remote rejects if the
45
+ branch already exists, so two people cannot take the same story. The PR body
46
+ carries the run's evidence (acceptance criteria → code → test → verify log),
47
+ because `.orchestrate/` never leaves the machine that produced it.
48
+
49
+ GitHub and GitLab are supported through `adapters/forge/`; the exact CLI
50
+ commands are inlined in the `pull-request` skill and kept identical by test.
51
+ Team mode needs that forge's CLI (`gh` or `glab`) installed and logged in on
52
+ each machine. `install` and `doctor` check for it once the block is active;
53
+ without it a builder run still pushes its branch, then stops and asks a human
54
+ to open the PR — it never pretends the PR exists.
55
+
32
56
  ## Install
33
57
 
34
58
  ```bash
@@ -51,7 +75,7 @@ Re-running `install` refreshes the skills in place. It also writes a stamp at
51
75
  `<skills-dir>/.orchestrix-skills.json`:
52
76
 
53
77
  ```json
54
- { "version": "0.8.0", "ide": "claude", "skills": ["brainstorm", "commit", "…"] }
78
+ { "version": "0.11.0", "ide": "claude", "skills": ["brainstorm", "commit", "…"] }
55
79
  ```
56
80
 
57
81
  Two things read it. The installer prunes skills a previous version placed that
@@ -88,12 +112,21 @@ The root skill is where the interesting engineering lives. Beyond wiring:
88
112
  even if it asked for a deferred one.
89
113
  - **Two rules are hard-wired** because output→input matching structurally cannot
90
114
  reach them: `smoke-test` is the acceptance floor for a runnable app the run
91
- changed, and `design-system` precedes `design-ui`.
115
+ changed, and visual work runs direction → system → screens, with the human
116
+ approving rendered artboards at each gate rather than token files.
117
+ - **Model tiers are declared, not guessed.** Each skill states
118
+ `requires.model: frontier | capable | cheap`; the adapter maps the tier to a
119
+ model, and the resolved model is recorded on every ledger step.
120
+ - **A team adds no shared state.** In team mode a run lives on a branch in
121
+ its own workspace, story state is derived from git and the forge, and an
122
+ epic is accepted on base with `smoke-test` — N green PRs do not prove the
123
+ composition.
92
124
 
93
125
  ## Runtime adapters
94
126
 
95
127
  `skills/` is the runtime-neutral source of truth. `adapters/` describes how a
96
- runtime maps generic capabilities to its tools. During a Codex install the CLI
128
+ runtime maps generic capabilities to its tools, and how a forge (GitHub,
129
+ GitLab) maps the generic pull-request operations to its CLI. During a Codex install the CLI
97
130
  removes Claude-only `allowed-tools`, preserves the contract, and adds Codex
98
131
  runtime guidance. If the target has an unmanaged `AGENTS.md`, its content is left
99
132
  untouched and the guidance is placed at `.codex/orchestrix/AGENTS.md` for manual
@@ -8,6 +8,13 @@
8
8
  "filesystem.write": "Write, Edit",
9
9
  "shell.execute": "Bash",
10
10
  "web.read": "WebSearch, WebFetch",
11
- "agent.spawn": "Task"
11
+ "agent.spawn": "Task",
12
+ "design.canvas": "the bundled design skill (Claude Design canvas) + Artifact publish",
13
+ "forge.pr": "Bash + the forge CLI (gh | glab) named by core-config collaboration.forge; op table in the pull-request skill"
14
+ },
15
+ "models": {
16
+ "frontier": "fable",
17
+ "capable": "opus",
18
+ "cheap": "haiku"
12
19
  }
13
20
  }
@@ -4,8 +4,10 @@
4
4
  - Use the skills installed under `.codex/skills/`; start end-to-end work with `orchestrate`.
5
5
  - Treat each skill's `metadata.contract` as Orchestrix workflow data. Codex skill selection still depends on `name` and `description`.
6
6
  - Resolve logical knowledge and work namespaces through `core-config.yaml`.
7
- - Map capability names in `metadata.requires.capabilities` to the tools available in the current Codex session.
7
+ - Map capability names in `metadata.requires.capabilities` to the tools available in the current Codex session. `design.canvas` is not available: design skills write standalone HTML files, and you open them for the human at visual gates.
8
+ - Map `metadata.requires.model` tiers to models the session can select (`frontier` = the most capable available). When a step cannot switch models, record the session model in its ledger `step` event.
8
9
  - When isolated agents are available, dispatch independent leaf skills concurrently and await them. Otherwise execute leaf skills sequentially in the current context, reading only their declared inputs before each step.
9
10
  - Independently run every objective verification command. Never accept an agent's success report as proof.
10
11
  - Keep runtime evidence at the fixed `.orchestrate/` path described by the `orchestrate` skill.
12
+ - When `core-config.yaml` has a `collaboration:` block, follow the `orchestrate` skill's Collaboration section: one story per branch per workspace (`git worktree add`, then `cd` into it — Codex has no worktree tool), builder runs end with `pull-request`, and forge operations come from that skill's op table.
11
13
  <!-- orchestrix:end -->
@@ -8,6 +8,13 @@
8
8
  "filesystem.write": "runtime patch/edit tools",
9
9
  "shell.execute": "runtime shell tool",
10
10
  "web.read": "runtime web tools when enabled",
11
- "agent.spawn": "optional collaboration tools; otherwise sequential fallback"
11
+ "agent.spawn": "optional collaboration tools; otherwise sequential fallback",
12
+ "design.canvas": "not available; design skills write standalone HTML files",
13
+ "forge.pr": "runtime shell tool + the forge CLI (gh | glab) named by core-config collaboration.forge; op table in the pull-request skill"
14
+ },
15
+ "models": {
16
+ "frontier": "the most capable model the session can select; else the session model, recorded in the ledger",
17
+ "capable": "the session default model",
18
+ "cheap": "the smallest model the session can select; else the session model"
12
19
  }
13
20
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "github",
3
+ "term": "pull request",
4
+ "cli": "gh",
5
+ "ops": {
6
+ "auth": "gh auth status",
7
+ "pr_for_branch": "gh pr list --head {branch} --state all --json number,url,state,isDraft,headRefOid,mergedAt",
8
+ "pr_create_draft": "gh pr create --draft --base {base} --head {branch} --title {title} --body-file {body_file}",
9
+ "pr_view": "gh pr view {pr} --json number,url,state,isDraft,headRefOid,baseRefName",
10
+ "pr_update_body": "gh pr edit {pr} --body-file {body_file}",
11
+ "pr_ready": "gh pr ready {pr}",
12
+ "ci_status": "gh pr checks {pr}",
13
+ "ci_watch": "gh pr checks {pr} --watch --fail-fast"
14
+ },
15
+ "notes": {
16
+ "pr_for_branch": "An empty JSON array means no PR exists for the branch.",
17
+ "ci_status": "Exit code 0 = all checks passed, 8 = pending, 1 = at least one failed (gh 2.63).",
18
+ "protection": "Enforce on the base branch via rulesets: require a pull request, require status checks, require the branch to be up to date before merging."
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "gitlab",
3
+ "term": "merge request",
4
+ "cli": "glab",
5
+ "ops": {
6
+ "auth": "glab auth status",
7
+ "pr_for_branch": "glab mr list --source-branch {branch} --all --output json",
8
+ "pr_create_draft": "glab mr create --draft --yes --source-branch {branch} --target-branch {base} --title {title} --description \"$(cat {body_file})\"",
9
+ "pr_view": "glab mr view {pr} --output json",
10
+ "pr_update_body": "glab mr update {pr} --description \"$(cat {body_file})\"",
11
+ "pr_ready": "glab mr update {pr} --ready",
12
+ "ci_status": "glab ci status --branch {branch} --compact",
13
+ "ci_watch": "glab ci status --branch {branch} --live"
14
+ },
15
+ "notes": {
16
+ "pr_for_branch": "An empty JSON array means no MR exists for the branch. The JSON carries state (opened | merged | closed), sha, and web_url.",
17
+ "ci_status": "Read the pipeline status word from the output (success | failed | running | pending); the exit code alone does not encode it in glab 1.50.",
18
+ "protection": "Enforce on the target branch: protected branch (no direct push), merge requests required, pipelines must succeed, and the merge method setting that requires the source to be up to date (fast-forward or semi-linear)."
19
+ }
20
+ }
package/bin/install.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Copies skills into the runtime's skills dir + scaffolds knowledge/.
4
4
  // No MCP, no license, no network calls. The hosted layer is a separate opt-in.
5
5
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
- import { dirname, join } from "node:path";
6
+ import { delimiter, dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
9
9
  const PKG = join(dirname(fileURLToPath(import.meta.url)), "..");
@@ -45,6 +45,7 @@ What it does:
45
45
  1. Copies the skills into your runtime's skills dir (default: .claude/skills/)
46
46
  2. Scaffolds knowledge/ + core-config.yaml (never overwrites an existing brain)
47
47
  3. Stamps <skills-dir>/.orchestrix-skills.json so upgrades and pruning are exact
48
+ 4. Adds .orchestrate/ to .gitignore (once; ledger and verify logs are per-run state)
48
49
 
49
50
  Hosted orchestration, knowledge hosting, and team features:
50
51
  see https://orchestrix-mcp.youlidao.ai`);
@@ -115,6 +116,18 @@ function installSkills(dir, runtimeName, runtime) {
115
116
  return { count: names.length, pruned };
116
117
  }
117
118
 
119
+ // The ledger and verify logs are per-run, per-workspace state. Committed, they
120
+ // conflict on every PR in team mode, so the ignore line is written once and
121
+ // left alone if the project already has it.
122
+ function ignoreRuntimeDir(dir) {
123
+ const path = join(dir, ".gitignore");
124
+ const current = existsSync(path) ? readFileSync(path, "utf8") : "";
125
+ if (current.split("\n").some((line) => /^\.orchestrate\/?\s*$/.test(line))) return false;
126
+ const separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
127
+ writeFileSync(path, `${current}${separator}.orchestrate/\n`);
128
+ return true;
129
+ }
130
+
118
131
  function installRuntimeGuidance(dir, runtimeName) {
119
132
  if (runtimeName !== "codex") return;
120
133
  const source = join(PKG, "adapters", "codex", "AGENTS.md");
@@ -160,6 +173,37 @@ function nonemptyFile(path) {
160
173
  return isFile(path) && readFileSync(path, "utf8").trim().length > 0;
161
174
  }
162
175
 
176
+ // Team mode is opt-in: an uncommented `collaboration:` block in core-config.
177
+ // Returns the forge name it declares, or null when the block is absent.
178
+ function activeForge(configContent) {
179
+ const block = configContent.match(/^collaboration:\s*(?:#.*)?\n((?:[ \t]+.*\n?)*)/m);
180
+ if (!block) return null;
181
+ return block[1].match(/^[ \t]+forge:\s*([A-Za-z0-9_-]+)/m)?.[1] ?? "";
182
+ }
183
+
184
+ function forgeCli(forge) {
185
+ const path = join(PKG, "adapters", "forge", `${forge}.json`);
186
+ return isFile(path) ? JSON.parse(readFileSync(path, "utf8")).cli : null;
187
+ }
188
+
189
+ function onPath(bin) {
190
+ const exts = process.platform === "win32" ? ["", ".exe", ".cmd"] : [""];
191
+ return (process.env.PATH ?? "").split(delimiter).some((dir) => dir && exts.some((ext) => isFile(join(dir, bin + ext))));
192
+ }
193
+
194
+ // The pull-request skill drives the forge through its CLI (gh | glab). Without
195
+ // it, builder runs stop at "push the branch, open the PR by hand" — better to
196
+ // hear that from install/doctor than mid-run.
197
+ function forgeCheck(configContent) {
198
+ const forge = activeForge(configContent);
199
+ if (forge === null) return null;
200
+ const cli = forgeCli(forge);
201
+ if (!cli) return { ok: false, detail: `collaboration.forge "${forge}" has no adapter (known: github, gitlab)` };
202
+ return onPath(cli)
203
+ ? { ok: true, detail: `${cli} on PATH (collaboration.forge: ${forge})` }
204
+ : { ok: false, detail: `${cli} not on PATH — team mode needs it installed and logged in (collaboration.forge: ${forge})` };
205
+ }
206
+
163
207
  function validSkill(path, runtimeName, expectedName) {
164
208
  if (!nonemptyFile(path)) return false;
165
209
  const content = readFileSync(path, "utf8");
@@ -203,6 +247,14 @@ function install() {
203
247
  console.log("✓ core-config.yaml scaffolded");
204
248
  }
205
249
 
250
+ // 4. Runtime dir — ledger + verify logs never belong in history.
251
+ if (ignoreRuntimeDir(dir)) console.log("✓ .orchestrate/ added to .gitignore");
252
+ else console.log("• .gitignore already ignores .orchestrate/");
253
+
254
+ // 5. Team mode, if the project turned it on: say now whether the forge CLI is there.
255
+ const forge = forgeCheck(nonemptyFile(cfgTarget) ? readFileSync(cfgTarget, "utf8") : "");
256
+ if (forge) console.log(`${forge.ok ? "✓" : "✗"} forge CLI: ${forge.detail}`);
257
+
206
258
  if (ide === "cursor" || ide === "windsurf") {
207
259
  console.log(`\nNote: ${ide} does not auto-load Anthropic skills yet — copied as reference rules.`);
208
260
  }
@@ -234,6 +286,8 @@ function doctor() {
234
286
  ["core config valid", /(^|\n)knowledge:\s*(#.*)?\n/.test(configContent) && /(^|\n)work:\s*(#.*)?\n/.test(configContent), configPath],
235
287
  ["knowledge brain", isDirectory(join(dir, "knowledge")), join(dir, "knowledge")],
236
288
  ];
289
+ const forge = forgeCheck(configContent);
290
+ if (forge) checks.push(["forge CLI (team mode)", forge.ok, forge.detail]);
237
291
  if (ide === "codex") {
238
292
  const reference = join(dir, ".codex", "orchestrix", "AGENTS.md");
239
293
  const rootInstructions = join(dir, "AGENTS.md");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-skills",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Capability-first AI development skill graph — Anthropic-native skills that run in any agent runtime.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,20 @@ work: # work products — skills WRITE these (outputs:), humans review at gates
18
18
  # here for transparency only.
19
19
  runtime: .orchestrate # ledger + verify logs (ephemeral, gitignored)
20
20
 
21
+ # Team mode (opt-in). Uncomment to make orchestrate run each story on its own
22
+ # branch in its own workspace and end builder runs with a PR/MR instead of a
23
+ # local commit. Absent = solo behaviour, unchanged. See the orchestrate skill,
24
+ # "Collaboration".
25
+ #
26
+ # collaboration:
27
+ # forge: github # github | gitlab
28
+ # base: main
29
+ # branch: story/{origin}/{slug}
30
+ # plan_branch: plan/{origin}
31
+ # workspace: worktree # worktree (same machine) | in-place (a clone elsewhere)
32
+ # pr:
33
+ # reviewers: []
34
+
21
35
  # Org-level cascade (shared taste/standards across many products) is intentionally
22
36
  # NOT enabled yet (YAGNI). When needed: add an `extends:` base that project paths
23
37
  # override. The logical namespaces above do not change when cascade is added.
@@ -42,4 +42,5 @@ focus:
42
42
  visible_style: "" # the keyboard focus indicator every interactive element carries
43
43
 
44
44
  provenance: { source: design-system, added: "", approved_by: "" }
45
+ # per token: extracted: <file> | extracted: <file>, snapped <before → after> | added
45
46
  ```
package/skills/README.md CHANGED
@@ -18,6 +18,11 @@ Runtime-neutral capability requirements live under
18
18
  Adapters map those names to runtime tools. Runtime-specific tool names are not
19
19
  part of the orchestration contract.
20
20
 
21
+ `metadata.requires.model` declares the model tier a skill needs —
22
+ `frontier` (judgment: design, review, planning), `capable` (implementation),
23
+ or `cheap` (mechanical). Adapters map tiers to models under `models` in
24
+ `runtime.json`; the orchestrator states the resolved model on every dispatch.
25
+
21
26
  ### The contract (6 fields)
22
27
 
23
28
  | Field | Meaning |
@@ -54,9 +59,9 @@ are different gates; never merge them.
54
59
  intent
55
60
  └─ orchestrate (root: warm context, wires skills by output→input, enforces gates)
56
61
  ├─ brainstorm ──(needs facts?)─→ research
57
- ├─ (has UI?) ──→ design-system (once) → design-ui
62
+ ├─ (has UI?) ──→ design-directions (human picks a rendered direction) → design-system (once) → design-ui
58
63
  ├─ (arch decision?) ──→ design-architecture
59
- └─ draft-story → implement → run-tests → review-code → commit
64
+ └─ draft-story → implement → run-tests → review-code → commit ──(team mode)─→ pull-request
60
65
  ↑ verify ↑ design-review (UI only)
61
66
  (objective) ↑ accept (batched)
62
67
  ```
@@ -71,6 +76,7 @@ Human gates are front-loaded (planning = direction) and at the very end
71
76
  | `orchestrate` | root | inline (delivery) |
72
77
  | `brainstorm` | planning | inline (hard gate) |
73
78
  | `research` | planning (optional) | never |
79
+ | `design-directions` | planning (UI, once) | inline |
74
80
  | `design-system` | planning (UI, once) | inline |
75
81
  | `design-ui` | planning (UI only) | inline |
76
82
  | `design-architecture` | planning (when needed) | inline |
@@ -80,6 +86,7 @@ Human gates are front-loaded (planning = direction) and at the very end
80
86
  | `review-code` | build | deferred |
81
87
  | `design-review` | build (UI only) | deferred |
82
88
  | `commit` | build | deferred |
89
+ | `pull-request` | build (team mode) | inline (first open) |
83
90
 
84
91
  ## Attribution
85
92
 
@@ -6,6 +6,7 @@ allowed-tools: [Read, Write, Grep, Glob]
6
6
  metadata:
7
7
  requires:
8
8
  capabilities: [filesystem.read, filesystem.write]
9
+ model: frontier
9
10
  contract:
10
11
  inputs: [intent, project_context]
11
12
  reads: [taste/*, architecture/*, registry/*]
@@ -75,6 +76,8 @@ origin: <stable short handle of this intent>
75
76
  ## Downstream — flags for the orchestrator:
76
77
 
77
78
  - needs UI design? yes/no (→ design-ui)
79
+ - key screens (UI only): the 2–3 screens that carry the product, one line each
80
+ (→ design-directions renders these first)
78
81
  - needs an architecture decision? yes/no (→ design-architecture)
79
82
  - open question needing facts? yes/no (→ research)
80
83
 
@@ -6,6 +6,7 @@ allowed-tools: [Read, Bash]
6
6
  metadata:
7
7
  requires:
8
8
  capabilities: [filesystem.read, shell.execute]
9
+ model: cheap
9
10
  contract:
10
11
  inputs: [verified_changes, message_intent]
11
12
  reads: []
@@ -6,6 +6,7 @@ allowed-tools: [Read, Bash]
6
6
  metadata:
7
7
  requires:
8
8
  capabilities: [filesystem.read, shell.execute]
9
+ model: capable
9
10
  contract:
10
11
  inputs: [accepted_deliverable, target]
11
12
  reads: [registry/deploy]
@@ -6,6 +6,7 @@ allowed-tools: [Read, Write, Grep, Glob]
6
6
  metadata:
7
7
  requires:
8
8
  capabilities: [filesystem.read, filesystem.write]
9
+ model: frontier
9
10
  contract:
10
11
  inputs: [requirement, system_context]
11
12
  reads: [architecture/*, registry/api, registry/db]
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: design-directions
3
+ description: Use when a product has a UI but no settled visual direction — renders 2–4 genuinely different low-fidelity directions of the key screens so the human picks one they can SEE, before any design system is written.
4
+ license: MIT
5
+ allowed-tools: [Read, Write, Bash, Grep, Glob, Skill]
6
+ metadata:
7
+ requires:
8
+ capabilities: [filesystem.read, filesystem.write, shell.execute, "design.canvas?"]
9
+ model: frontier
10
+ contract:
11
+ inputs: [product_context, key_screens, "references?", "direction_feedback?"]
12
+ reads: [taste/brand, taste/design-system, registry/*]
13
+ outputs: [design_directions]
14
+ authority: "Write design assets under the specs namespace (default docs/specs/design/directions/). No source code, no production. Never publish or share externally — the orchestrator shows the artboards at the gate."
15
+ verify: "2–4 directions, each named by the axis it explores; every direction renders every key screen; every artboard passes the canvas check command (or is a standalone HTML file that opens from disk); directions.md gives every direction a motivation and a tradeoff."
16
+ accept:
17
+ when: "Always — the human picks a direction by looking at rendered screens, never by reading tokens."
18
+ timing: inline
19
+ ---
20
+
21
+ # Design Directions
22
+
23
+ Settle the visual direction with the human, not for them. People react to a
24
+ rendered screen; they do not react to a typeface name or a hex value. So the
25
+ first thing a human sees of a new product's look is 2–4 low-fidelity screens
26
+ they can compare — and the direction they pick is what `design-system` then
27
+ codifies.
28
+
29
+ **Posture:** Senior product designer running a direction review. Breadth over
30
+ polish. Every option gets an honest case; a set where only your favorite is
31
+ argued for is a rigged vote.
32
+
33
+ ## Precondition — skip when the direction is already settled
34
+
35
+ Do not explore what is already decided. Return `skipped` with a one-line
36
+ reason when either holds:
37
+
38
+ - `taste/design-system` is populated (a real system, not the unedited seed).
39
+ - `registry/*` shows an existing UI in the repo. Its look is the direction;
40
+ `design-system` extracts it from source.
41
+
42
+ ## Inputs
43
+
44
+ - `product_context` — the approved spec from `brainstorm` (goal, requirement,
45
+ constraints).
46
+ - `key_screens` — the 2–3 screens that carry the product, from the spec's
47
+ `Downstream` section. If the spec lists none, derive them from the
48
+ requirement and name them at the top of `directions.md`. Never more than 3.
49
+ - `references?` — products, brands, or assets the human named.
50
+ - `direction_feedback?` — present on re-dispatch when the human rejected every
51
+ direction. Read it first; the new set must answer it.
52
+
53
+ ## Process
54
+
55
+ 1. **Read the signal.** `taste/brand`, references, and the product context.
56
+ What is the product for, and for whom? Which tone does the brief imply
57
+ (internal tool → utilitarian; consumer → expressive)?
58
+ 2. **Name 2–4 directions, each on a named axis.** An axis is a real choice:
59
+ density (dense vs airy), type personality (editorial serif vs geometric
60
+ sans vs mono), color stance (one accent on neutral vs tonal), tone (quiet
61
+ vs bold). Two directions that differ only in shade are one direction —
62
+ replace one.
63
+ 3. **Sketch every key screen in every direction, low-fi.** Structure,
64
+ hierarchy, a type pairing, one accent, real layout. Real copy where the
65
+ brief supplies it; a bracketed placeholder like `[price]` where it does
66
+ not. No filler sections, no emoji as icons, no fake device chrome. Low-fi
67
+ means decision fidelity, not deliverable fidelity — enough to choose, not
68
+ enough to ship.
69
+ 4. **Author the artboards.**
70
+ - With `design.canvas`: one artboard per direction × screen, named
71
+ `<Direction>-<Screen>.dc.html`, plus a `canvas.json` that lays each
72
+ direction out as one row. `Main.dc.html` is the leading candidate's
73
+ first screen. Follow the runtime's design skill for the file format and
74
+ run its check command; its output is the verify evidence.
75
+ - Without it: one standalone `<Direction>-<Screen>.html` per artboard,
76
+ self-contained (inline CSS, no external assets except Google Fonts), and
77
+ an `index.html` that links every file under its direction name. Each
78
+ must open from disk.
79
+ 5. **Write `directions.md`.** For each direction: name, the axis it explores,
80
+ the motivation (why it fits this product), and its main tradeoff (what it
81
+ costs). One paragraph each. End with the key screens list.
82
+
83
+ ## Anti-slop
84
+
85
+ The canonical list lives in the `design-system` skill. A direction that lands
86
+ on one of those defaults is allowed only as a stated, justified choice — and
87
+ never as two of the 2–4.
88
+
89
+ ## Output: `specs/design/directions/`
90
+
91
+ ```
92
+ directions.md # name, axis, motivation, tradeoff per direction
93
+ canvas.json # with design.canvas only
94
+ <Direction>-<Screen>.dc.html # with design.canvas
95
+ <Direction>-<Screen>.html + index.html # without it
96
+ ```
97
+
98
+ ## Done
99
+
100
+ Write the files, run the check, then stop (`accept: inline`). The
101
+ orchestrator puts the rendered artboards in front of the human and records
102
+ their pick as `chosen_direction`. Never pick for them. If they reject every
103
+ direction, you are re-dispatched with `direction_feedback` — same skill, new
104
+ set.
@@ -6,8 +6,9 @@ allowed-tools: [Read, Bash, Grep, Glob]
6
6
  metadata:
7
7
  requires:
8
8
  capabilities: [filesystem.read, shell.execute]
9
+ model: frontier
9
10
  contract:
10
- inputs: [built_ui, ui_spec, "screenshots?"]
11
+ inputs: [built_ui, ui_spec, "ui_artboards?", "screenshots?"]
11
12
  reads: [taste/design-system, taste/brand]
12
13
  outputs: [design_review_report]
13
14
  authority: "Read-only on code. Start and stop the app locally to render it, and drive it via browser automation or HTTP. No edits, no commits, no deploy, no external spend."
@@ -42,6 +43,10 @@ clean one.
42
43
  - `built_ui` — the running app (a live URL, or a dev server this skill starts).
43
44
  - `ui_spec` — `specs/<slug>-ui.md` it must satisfy, including its declared
44
45
  **treatment** (`utility` / `product` / `editorial`).
46
+ - `ui_artboards?` — the artboards the human approved at the `design-ui` gate
47
+ (`specs/design/<slug>/`). They are the visual bar for Verdict 3: a built
48
+ screen that departs from its approved artboard in hierarchy, spacing, or
49
+ treatment is a finding, at the same severity as a deviation from the system.
45
50
  - `screenshots?` — captures a prior `smoke-test` already took. Use them instead
46
51
  of relaunching the app for the same screen.
47
52
  - Read `taste/design-system` + `taste/brand` — the bar to calibrate against.