orchestrix-skills 0.10.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +31 -2
- package/adapters/claude/runtime.json +2 -1
- package/adapters/codex/AGENTS.md +1 -0
- package/adapters/codex/runtime.json +2 -1
- package/adapters/forge/github.json +20 -0
- package/adapters/forge/gitlab.json +20 -0
- package/bin/install.js +55 -1
- package/package.json +1 -1
- package/project-scaffold/core-config.yaml +14 -0
- package/skills/README.md +2 -1
- package/skills/draft-story/SKILL.md +11 -1
- package/skills/orchestrate/SKILL.md +132 -4
- package/skills/pull-request/SKILL.md +157 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orchestrix-skills",
|
|
3
|
-
"version": "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
|
@@ -23,12 +23,36 @@ intent
|
|
|
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.
|
|
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
|
|
@@ -93,11 +117,16 @@ The root skill is where the interesting engineering lives. Beyond wiring:
|
|
|
93
117
|
- **Model tiers are declared, not guessed.** Each skill states
|
|
94
118
|
`requires.model: frontier | capable | cheap`; the adapter maps the tier to a
|
|
95
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.
|
|
96
124
|
|
|
97
125
|
## Runtime adapters
|
|
98
126
|
|
|
99
127
|
`skills/` is the runtime-neutral source of truth. `adapters/` describes how a
|
|
100
|
-
runtime maps generic capabilities to its tools
|
|
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
|
|
101
130
|
removes Claude-only `allowed-tools`, preserves the contract, and adds Codex
|
|
102
131
|
runtime guidance. If the target has an unmanaged `AGENTS.md`, its content is left
|
|
103
132
|
untouched and the guidance is placed at `.codex/orchestrix/AGENTS.md` for manual
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"shell.execute": "Bash",
|
|
10
10
|
"web.read": "WebSearch, WebFetch",
|
|
11
11
|
"agent.spawn": "Task",
|
|
12
|
-
"design.canvas": "the bundled design skill (Claude Design canvas) + Artifact publish"
|
|
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"
|
|
13
14
|
},
|
|
14
15
|
"models": {
|
|
15
16
|
"frontier": "fable",
|
package/adapters/codex/AGENTS.md
CHANGED
|
@@ -9,4 +9,5 @@
|
|
|
9
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.
|
|
10
10
|
- Independently run every objective verification command. Never accept an agent's success report as proof.
|
|
11
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.
|
|
12
13
|
<!-- orchestrix:end -->
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"shell.execute": "runtime shell tool",
|
|
10
10
|
"web.read": "runtime web tools when enabled",
|
|
11
11
|
"agent.spawn": "optional collaboration tools; otherwise sequential fallback",
|
|
12
|
-
"design.canvas": "not available; design skills write standalone HTML files"
|
|
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"
|
|
13
14
|
},
|
|
14
15
|
"models": {
|
|
15
16
|
"frontier": "the most capable model the session can select; else the session model, recorded in the ledger",
|
|
@@ -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
|
@@ -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.
|
package/skills/README.md
CHANGED
|
@@ -61,7 +61,7 @@ intent
|
|
|
61
61
|
├─ brainstorm ──(needs facts?)─→ research
|
|
62
62
|
├─ (has UI?) ──→ design-directions (human picks a rendered direction) → design-system (once) → design-ui
|
|
63
63
|
├─ (arch decision?) ──→ design-architecture
|
|
64
|
-
└─ draft-story → implement → run-tests → review-code → commit
|
|
64
|
+
└─ draft-story → implement → run-tests → review-code → commit ──(team mode)─→ pull-request
|
|
65
65
|
↑ verify ↑ design-review (UI only)
|
|
66
66
|
(objective) ↑ accept (batched)
|
|
67
67
|
```
|
|
@@ -86,6 +86,7 @@ Human gates are front-loaded (planning = direction) and at the very end
|
|
|
86
86
|
| `review-code` | build | deferred |
|
|
87
87
|
| `design-review` | build (UI only) | deferred |
|
|
88
88
|
| `commit` | build | deferred |
|
|
89
|
+
| `pull-request` | build (team mode) | inline (first open) |
|
|
89
90
|
|
|
90
91
|
## Attribution
|
|
91
92
|
|
|
@@ -12,7 +12,7 @@ metadata:
|
|
|
12
12
|
reads: [taste/coding-standards, registry/api, registry/db, "front-end-spec?"]
|
|
13
13
|
outputs: [stories/<slug>.md]
|
|
14
14
|
authority: "Write one flat story file in the stories namespace (physical path from core-config.yaml; default docs/stories/). No folders. No source code. No production. No spend."
|
|
15
|
-
verify: "Every requirement maps to at least one acceptance criterion; constraints are copied verbatim; no placeholders (no TBD/TODO/'handle edge cases')."
|
|
15
|
+
verify: "Every requirement maps to at least one acceptance criterion; constraints are copied verbatim; no placeholders (no TBD/TODO/'handle edge cases'); depends_on names only stories of the same origin whose Interfaces produce what this one consumes; touches equals the File Map."
|
|
16
16
|
accept:
|
|
17
17
|
when: "Always — this output sets the direction the whole build rests on."
|
|
18
18
|
timing: inline
|
|
@@ -36,6 +36,8 @@ these sections (with frontmatter) and nothing more.
|
|
|
36
36
|
```markdown
|
|
37
37
|
---
|
|
38
38
|
origin: <stable short handle of the requirement this story came from>
|
|
39
|
+
depends_on: [] # slugs of same-origin stories whose Produces this story Consumes
|
|
40
|
+
touches: [] # every path in File Map (Create + Modify), as written there
|
|
39
41
|
---
|
|
40
42
|
|
|
41
43
|
# <Story title>
|
|
@@ -85,6 +87,12 @@ What this story deliberately does NOT do.
|
|
|
85
87
|
- **Flat, no hierarchy.** One file per story in the stories dir. Grouping is
|
|
86
88
|
the `origin` field (a query the AI runs), never a folder. Order comes from
|
|
87
89
|
Interfaces (dependencies), never from a number.
|
|
90
|
+
- **Dependencies and footprint are explicit.** `depends_on` is derived from
|
|
91
|
+
Interfaces: a story that Consumes a signature another same-origin story
|
|
92
|
+
Produces depends on it — nothing else qualifies. `touches` repeats the
|
|
93
|
+
File Map so a team run can detect two stories that would edit the same
|
|
94
|
+
file without opening either. Both are empty lists when there is nothing
|
|
95
|
+
to list, never omitted.
|
|
88
96
|
- **No placeholders.** "Add validation", "handle errors", "TBD" are failures.
|
|
89
97
|
State the actual condition.
|
|
90
98
|
- **Acceptance criteria are observable.** "Works well" is not an AC. "Returns
|
|
@@ -102,6 +110,8 @@ What this story deliberately does NOT do.
|
|
|
102
110
|
2. Are all hard constraints copied verbatim, not paraphrased?
|
|
103
111
|
3. Any placeholder text? Replace with the real condition.
|
|
104
112
|
4. Do the names in Interfaces match across sections?
|
|
113
|
+
5. Does `depends_on` name exactly the same-origin stories whose Produces
|
|
114
|
+
this story Consumes, and does `touches` equal the File Map?
|
|
105
115
|
|
|
106
116
|
## Done
|
|
107
117
|
|
|
@@ -4,9 +4,9 @@ description: Use when a goal must be delivered end-to-end by composing skills, w
|
|
|
4
4
|
license: MIT
|
|
5
5
|
allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Task, Skill, Artifact]
|
|
6
6
|
metadata:
|
|
7
|
-
version:
|
|
7
|
+
version: 8
|
|
8
8
|
requires:
|
|
9
|
-
capabilities: [filesystem.read, filesystem.write, shell.execute, "agent.spawn?", "design.canvas?"]
|
|
9
|
+
capabilities: [filesystem.read, filesystem.write, shell.execute, "agent.spawn?", "design.canvas?", "forge.pr?"]
|
|
10
10
|
model: frontier
|
|
11
11
|
contract:
|
|
12
12
|
inputs: [intent, "constraints?"]
|
|
@@ -146,6 +146,121 @@ CANNOT reach — wire these by rule, not by match:
|
|
|
146
146
|
gate, hand the human's pick to `design-system` as `chosen_direction`,
|
|
147
147
|
then `design-ui`.
|
|
148
148
|
A direction the human has not seen rendered is not a direction.
|
|
149
|
+
3. **An epic is accepted on base, not story by story.** In collaboration
|
|
150
|
+
mode (below), when every story of an origin is `done`, offer the
|
|
151
|
+
integration run: on base, `smoke-test` with flows from the spec's
|
|
152
|
+
acceptance criteria, final acceptance against the original intent, then
|
|
153
|
+
`deploy` if asked. N green PRs prove N stories; they do not prove the
|
|
154
|
+
composition. No output→input match reaches this step.
|
|
155
|
+
|
|
156
|
+
## Collaboration (team mode, opt-in)
|
|
157
|
+
|
|
158
|
+
Enabled only when `core-config.yaml` has a `collaboration:` block. Without it,
|
|
159
|
+
nothing in this section applies and the run behaves exactly as described
|
|
160
|
+
above. With it, the same loop runs; what changes is where a run lives (a
|
|
161
|
+
branch in its own workspace), how a run ends (a PR/MR, not a local commit),
|
|
162
|
+
and one preflight.
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
collaboration:
|
|
166
|
+
forge: github # github | gitlab — op table in the pull-request skill (and adapters/forge/<forge>.json)
|
|
167
|
+
base: main
|
|
168
|
+
branch: story/{origin}/{slug} # one story, one branch, one run
|
|
169
|
+
plan_branch: plan/{origin} # planning runs (specs, stories, knowledge)
|
|
170
|
+
workspace: worktree # worktree (same machine) | in-place (a clone elsewhere)
|
|
171
|
+
pr:
|
|
172
|
+
reviewers: []
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Principle: no new shared state.** Which stories are open, claimed, in
|
|
176
|
+
review, or done is derived from git and the forge every time it is needed,
|
|
177
|
+
never written to a board file that would need its own merge strategy.
|
|
178
|
+
|
|
179
|
+
| Story state | Derived from |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `open` | no remote branch for the story (`git ls-remote --heads origin <branch>` is empty) and no merged PR/MR |
|
|
182
|
+
| `claimed` | remote branch exists, no PR/MR (`pr_for_branch` is empty) |
|
|
183
|
+
| `in_review` | an open PR/MR for the branch |
|
|
184
|
+
| `done` | the forge reports a merged PR/MR for the branch (squash merges leave no ancestry, so `git branch --merged` cannot be the source) |
|
|
185
|
+
| `blocked` | any story in `depends_on` is not `done` |
|
|
186
|
+
| `stale` | `claimed`, and the branch's last commit is older than 7 days |
|
|
187
|
+
| `unknown` | the forge could not be queried — never treated as `open` |
|
|
188
|
+
|
|
189
|
+
### Preflight (deterministic, after binding intent)
|
|
190
|
+
|
|
191
|
+
1. `git fetch origin`, then bring the base checkout up to date with
|
|
192
|
+
`git merge --ff-only origin/<base>`. If that fails, stop: the checkout
|
|
193
|
+
has diverged from base and the brain it would read is stale.
|
|
194
|
+
2. Run the forge `auth` op (`gh auth status` | `glab auth status`; the full
|
|
195
|
+
op table is in the `pull-request` skill). Not authenticated → stop;
|
|
196
|
+
logging in is the human's act on this machine.
|
|
197
|
+
3. `git worktree prune`, then remove worktrees whose story is `done`.
|
|
198
|
+
4. `.orchestrate/` must be ignored (`git check-ignore -q .orchestrate`). If
|
|
199
|
+
not, add the line to `.gitignore` before anything else — a committed
|
|
200
|
+
ledger conflicts on every PR.
|
|
201
|
+
|
|
202
|
+
### Planning runs end in a planning PR
|
|
203
|
+
|
|
204
|
+
A run that wires any skill writing to the specs, stories, or knowledge
|
|
205
|
+
namespaces (`brainstorm`, `research`, `design-*`, `draft-story`,
|
|
206
|
+
`map-codebase`) runs on `plan_branch` and ends with `commit` →
|
|
207
|
+
`pull-request`. The backlog exists for the team only once that PR is merged
|
|
208
|
+
to base: a story that is not on base cannot be claimed. Do not build in the
|
|
209
|
+
same run that planned; the builder run starts from base.
|
|
210
|
+
|
|
211
|
+
### Builder runs: pick → workspace → claim → build → pull-request
|
|
212
|
+
|
|
213
|
+
1. **Pick.** Compute the board for the origin. The intent names a story, or
|
|
214
|
+
you propose the first `open`, not `blocked` story in dependency order.
|
|
215
|
+
Two `open` stories whose `touches` globs overlap are serialized: name the
|
|
216
|
+
conflict and pick only one. The human confirms the pick at the front gate
|
|
217
|
+
you already hold. Picking is a human act; there is no assign skill.
|
|
218
|
+
2. **Workspace.** Create the branch without checking it out, then give it a
|
|
219
|
+
working tree. Order matters: `git worktree add` refuses a branch that is
|
|
220
|
+
already checked out anywhere, so the branch must not be switched to in
|
|
221
|
+
the base checkout first.
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
git branch <branch> origin/<base>
|
|
225
|
+
git worktree add ../<repo>--<slug> <branch> # workspace: worktree
|
|
226
|
+
git switch <branch> # workspace: in-place
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
From here on, the workspace directory is the working directory for every
|
|
230
|
+
dispatch and every namespace resolution. One story per workspace is
|
|
231
|
+
enforced by git itself, not by this skill.
|
|
232
|
+
3. **Claim — atomic on the remote.** Inside the workspace, make one empty
|
|
233
|
+
commit (`git commit --allow-empty -m "chore(story): claim <slug>"`) so
|
|
234
|
+
the claim has a SHA and an author, then push it so that the push fails
|
|
235
|
+
if the branch already exists on the remote:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
git push -u --force-with-lease=refs/heads/<branch>: origin <branch>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The empty expectation after the colon means "the ref must not exist". A
|
|
242
|
+
rejected push (`stale info`) means someone else claimed it: remove the
|
|
243
|
+
workspace (`git worktree remove`, `git branch -D`), recompute the board,
|
|
244
|
+
pick again. Only after the push succeeds, write `run_start` followed by
|
|
245
|
+
`workspace` in the workspace's own `.orchestrate/ledger.jsonl`.
|
|
246
|
+
4. **Build — unchanged.** The loop above, gated by `verify`. Namespace paths
|
|
247
|
+
resolve inside the workspace.
|
|
248
|
+
5. **Tail.** `commit` → `pull-request`. Its `ci_status` is a verify result:
|
|
249
|
+
`red` re-enters the rework loop with the failing job's log as
|
|
250
|
+
`qa_feedback` (investigate first when the cause is not understood; the
|
|
251
|
+
3-attempt cap applies); `pending` at the ceiling is a gate, not a pass.
|
|
252
|
+
6. **Deliver** when the PR/MR is open and `ci_status` is `green`. Write
|
|
253
|
+
`run_end` with the `pr` URL. Merging is the reviewer's act in the forge;
|
|
254
|
+
this run does not wait for it.
|
|
255
|
+
|
|
256
|
+
### Parallel hazards on one machine
|
|
257
|
+
|
|
258
|
+
Two workspaces sharing one local database, port, or container project
|
|
259
|
+
corrupt each other's `smoke-test` and `design-review`. Unless `registry/app`
|
|
260
|
+
declares the app parallel-safe (per-workspace database name, port from
|
|
261
|
+
`$PORT` or `0`, distinct compose project), run those two skills under a lock
|
|
262
|
+
file in the git common dir (`git rev-parse --git-common-dir`) so only one
|
|
263
|
+
workspace drives an app at a time.
|
|
149
264
|
|
|
150
265
|
## Accept gate
|
|
151
266
|
|
|
@@ -233,11 +348,12 @@ Events and when to write them:
|
|
|
233
348
|
|
|
234
349
|
| Event | When | Shape |
|
|
235
350
|
| ----- | ---- | ----- |
|
|
236
|
-
| `run_start` | right after binding intent | `{"e":"run_start","run":"r-<yyyymmdd>-<slug>","intent":"...","ts":"..."}` |
|
|
351
|
+
| `run_start` | right after binding intent (in collaboration mode: as the first line of the workspace's ledger) | `{"e":"run_start","run":"r-<yyyymmdd>-<slug>","intent":"...","actor":"<git user.email>","ts":"..."}` |
|
|
352
|
+
| `workspace` | collaboration mode only, right after `run_start` | `{"e":"workspace","run":"...","branch":"story/<origin>/<slug>","path":"<absolute workspace path>","base":"<origin/base sha at claim>","ts":"..."}` |
|
|
237
353
|
| `plan` | after wiring the graph, and EVERY time the graph changes | `{"e":"plan","run":"...","steps":[{"n":1,"skill":"research","title":"..."}, …]}` — full current plan; latest `plan` line wins; steps may be added, never removed |
|
|
238
354
|
| `step` | immediately BEFORE each dispatch, and again after its verify | `{"e":"step","run":"...","n":3,"skill":"implement","status":"dispatched\|done\|failed\|skipped","attempt":1,"model":"<resolved model, or session>","evidence":"<file or one-line result>","ts":"..."}` — rework = same `n`, next `attempt`; a step a replan made obsolete gets `skipped` with the reason in `evidence` (plan lines are never removed, so this is how an obsolete step closes) |
|
|
239
355
|
| `gate` | when stopping at a human gate | `{"e":"gate","run":"...","kind":"inline_accept","question":"...","shows":"<artboard URL or path, visual gates only>","ts":"..."}` |
|
|
240
|
-
| `run_end` | at delivery or abandonment | `{"e":"run_end","run":"...","result":"delivered\|paused\|abandoned","ts":"..."}` |
|
|
356
|
+
| `run_end` | at delivery or abandonment | `{"e":"run_end","run":"...","result":"delivered\|paused\|abandoned","pr":"<PR/MR URL, collaboration mode only>","ts":"..."}` |
|
|
241
357
|
|
|
242
358
|
A step recorded `done` is done — do not re-dispatch it. `evidence` on a `done`
|
|
243
359
|
step is required and should be the step's verify log path
|
|
@@ -261,6 +377,11 @@ session, or a wake-up — do NOT continue from what you remember. Replay:
|
|
|
261
377
|
Fail → re-dispatch as the next attempt.
|
|
262
378
|
4. Continue the loop from the first open step. If the run was stopped at a
|
|
263
379
|
`gate`, re-ask that gate's question — never assume it was answered.
|
|
380
|
+
5. In collaboration mode, the `workspace` line says which branch and
|
|
381
|
+
directory the run lives in. Resume from inside that workspace; the base
|
|
382
|
+
checkout's ledger never holds a builder run. A `workspace` line whose
|
|
383
|
+
path no longer exists means the worktree was removed: the run is
|
|
384
|
+
`abandoned`, write `run_end` and say so.
|
|
264
385
|
|
|
265
386
|
## Context discipline (stay lean)
|
|
266
387
|
|
|
@@ -312,3 +433,10 @@ implementer missed; mechanical skills are `cheap`.
|
|
|
312
433
|
- Running a `frontier` step on a cheaper model without recording it in the
|
|
313
434
|
ledger
|
|
314
435
|
- Marking the run complete without every step's `verify` evidence
|
|
436
|
+
- Collaboration mode: building on base, or in the same run that planned
|
|
437
|
+
- Collaboration mode: claiming with a plain push (two claims both succeed),
|
|
438
|
+
or switching to the story branch before `git worktree add` (which then
|
|
439
|
+
refuses it)
|
|
440
|
+
- Collaboration mode: treating `ci_status: pending` or `unknown` as green, or
|
|
441
|
+
delivering a builder run with no `pr` on `run_end`
|
|
442
|
+
- Collaboration mode: two workspaces driving one local app with no lock
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pull-request
|
|
3
|
+
description: Use when a verified story branch (or a planning branch) must be published for team review as a pull request (GitHub) or merge request (GitLab), and its CI result pulled back into the run. Never merges. Not for solo runs with no collaboration block.
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: [Read, Bash]
|
|
6
|
+
metadata:
|
|
7
|
+
requires:
|
|
8
|
+
capabilities: [filesystem.read, shell.execute, forge.pr]
|
|
9
|
+
model: cheap
|
|
10
|
+
contract:
|
|
11
|
+
inputs: [branch, story, verification_report, "ac_traceability?", "review_report?", "smoke_report?", "design_review_report?", "pr?"]
|
|
12
|
+
reads: [core-config]
|
|
13
|
+
outputs: [pr_url, ci_status]
|
|
14
|
+
authority: "Rebase the current branch onto base, push it, and open or update ONE pull/merge request against base. Never merge, never push to base, never force-push over commits you did not make (--force-with-lease only), never change forge settings."
|
|
15
|
+
verify: "The forge returns a PR/MR for the branch whose head SHA equals local HEAD; ci_status is green, red, or pending — never unknown; the body lists every acceptance criterion with its evidence."
|
|
16
|
+
accept:
|
|
17
|
+
when: "The first open of a PR/MR — the human approves title, body, and reviewers once. Later updates to the same PR/MR need no gate."
|
|
18
|
+
timing: inline
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Pull Request
|
|
22
|
+
|
|
23
|
+
Publish a verified branch for team review. A pull request (GitHub) and a merge
|
|
24
|
+
request (GitLab) are the same object: a request to merge one branch into
|
|
25
|
+
another with review and CI attached. This skill speaks to whichever forge
|
|
26
|
+
`core-config.yaml` names and treats the two identically.
|
|
27
|
+
|
|
28
|
+
**Core principle:** The PR body is the run's evidence, made portable. Verify
|
|
29
|
+
logs live in `.orchestrate/`, which never leaves the machine. A reviewer on
|
|
30
|
+
another machine sees only what this skill puts in the body — so the body is
|
|
31
|
+
assembled from the run's files, never from memory.
|
|
32
|
+
|
|
33
|
+
## Preconditions — verify each now
|
|
34
|
+
|
|
35
|
+
1. `core-config.yaml` has a `collaboration:` block. Absent → this skill does
|
|
36
|
+
not apply; stop and say so.
|
|
37
|
+
2. The forge CLI is authenticated: run the `auth` op. Not authenticated →
|
|
38
|
+
stop; authentication is the human's act on this machine.
|
|
39
|
+
3. The current branch matches `collaboration.branch` (story) or
|
|
40
|
+
`collaboration.plan_branch` (planning), and is not `collaboration.base`.
|
|
41
|
+
4. `verification_report` is a fresh green run — not a remembered one.
|
|
42
|
+
|
|
43
|
+
## Forge operations
|
|
44
|
+
|
|
45
|
+
`collaboration.forge` selects the column. Substitute `{base}`, `{branch}`,
|
|
46
|
+
`{pr}`, `{title}`, `{body_file}`. The same table ships as
|
|
47
|
+
`adapters/forge/<forge>.json` for tools that read it as data; the two are
|
|
48
|
+
kept identical by test.
|
|
49
|
+
|
|
50
|
+
| Op | GitHub (`gh`) | GitLab (`glab`) |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `auth` | `gh auth status` | `glab auth status` |
|
|
53
|
+
| `pr_for_branch` | `gh pr list --head {branch} --state all --json number,url,state,isDraft,headRefOid,mergedAt` | `glab mr list --source-branch {branch} --all --output json` |
|
|
54
|
+
| `pr_create_draft` | `gh pr create --draft --base {base} --head {branch} --title {title} --body-file {body_file}` | `glab mr create --draft --yes --source-branch {branch} --target-branch {base} --title {title} --description "$(cat {body_file})"` |
|
|
55
|
+
| `pr_view` | `gh pr view {pr} --json number,url,state,isDraft,headRefOid,baseRefName` | `glab mr view {pr} --output json` |
|
|
56
|
+
| `pr_update_body` | `gh pr edit {pr} --body-file {body_file}` | `glab mr update {pr} --description "$(cat {body_file})"` |
|
|
57
|
+
| `pr_ready` | `gh pr ready {pr}` | `glab mr update {pr} --ready` |
|
|
58
|
+
| `ci_status` | `gh pr checks {pr}` | `glab ci status --branch {branch} --compact` |
|
|
59
|
+
| `ci_watch` | `gh pr checks {pr} --watch --fail-fast` | `glab ci status --branch {branch} --live` |
|
|
60
|
+
|
|
61
|
+
Reading results: an empty JSON array from `pr_for_branch` means no PR/MR
|
|
62
|
+
exists. `gh pr checks` exits 0 when every check passed, 8 while checks are
|
|
63
|
+
pending, 1 when one failed. `glab ci status` encodes the pipeline state in
|
|
64
|
+
its output words (`success`, `failed`, `running`, `pending`), not in its
|
|
65
|
+
exit code — read the text.
|
|
66
|
+
|
|
67
|
+
An unknown forge name, or a CLI missing from PATH → push the branch, print
|
|
68
|
+
the URL a human would use to open the PR/MR, and return `ci_status:
|
|
69
|
+
unknown`. That fails this skill's verify on purpose: the run stops at a gate
|
|
70
|
+
instead of pretending.
|
|
71
|
+
|
|
72
|
+
## Process
|
|
73
|
+
|
|
74
|
+
1. **Sync with base.** `git fetch origin`, then rebase onto
|
|
75
|
+
`origin/<base>`. A conflict outside the knowledge namespace → stop and
|
|
76
|
+
hand the conflict to the human; do not resolve source conflicts. A
|
|
77
|
+
conflict inside the knowledge namespace is resolved by **keyed union**:
|
|
78
|
+
keep every entry from both sides, matching on the entry's key (`id` for
|
|
79
|
+
taste rows, `path` for registry endpoints, the ADR id for decisions),
|
|
80
|
+
prefer the base side when the same key differs, and list what you merged
|
|
81
|
+
in the body under Brain changes.
|
|
82
|
+
2. **Re-verify on the rebased tree.** Run the project's test command fresh
|
|
83
|
+
and capture it to `.orchestrate/verify/pull-request-tests.log`. Red →
|
|
84
|
+
return `ci_status: red` with the failing output as the reason; do not
|
|
85
|
+
push a red branch.
|
|
86
|
+
3. **Push** with `git push --force-with-lease origin <branch>`.
|
|
87
|
+
`--force-with-lease` protects commits you did not make; plain `--force`
|
|
88
|
+
is forbidden.
|
|
89
|
+
4. **Find or create.** `pr_for_branch`. None → assemble the body (below),
|
|
90
|
+
write it to `.orchestrate/pr-body.md`, then stop for the inline gate:
|
|
91
|
+
show title, body, and reviewers; create as draft only on sign-off.
|
|
92
|
+
Exists → `pr_update_body` with the re-assembled body; no gate.
|
|
93
|
+
5. **Wait for CI, bounded.** `ci_watch` with a ceiling of about 15 minutes.
|
|
94
|
+
Green → `pr_ready`, return `ci_status: green`. Red → return
|
|
95
|
+
`ci_status: red` with the failing job name and its log tail as the
|
|
96
|
+
reason. Still pending at the ceiling → return `ci_status: pending`.
|
|
97
|
+
6. **Verify.** `pr_view`: head SHA must equal `git rev-parse HEAD`. Mismatch
|
|
98
|
+
means the push did not land or someone else pushed; report it as a
|
|
99
|
+
failure, never as done.
|
|
100
|
+
|
|
101
|
+
## The body
|
|
102
|
+
|
|
103
|
+
Every section is filled from a file the run produced. A missing input leaves
|
|
104
|
+
its section reading `not run this run`, never blank and never invented.
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
# <story title>
|
|
108
|
+
|
|
109
|
+
`<branch>` → `<base>` · story: `<stories path>` · origin: `<origin>`
|
|
110
|
+
|
|
111
|
+
## Acceptance criteria → evidence
|
|
112
|
+
| AC | Code | Test | Verify log |
|
|
113
|
+
| --- | --- | --- | --- |
|
|
114
|
+
| AC1 <text> | src/x.ts:42-58 | tests/x.test.ts 'rejects empty email' | step-4-attempt-1.log · exit 0 |
|
|
115
|
+
|
|
116
|
+
## Verification
|
|
117
|
+
- run-tests: <passed>/<failed> · `<command>`
|
|
118
|
+
- smoke-test: <passed> passed · <failed> failed · <untested> untested (<reasons>)
|
|
119
|
+
- review-code: <verdict>; findings routed back: <n>
|
|
120
|
+
- design-review: <verdict> · coverage <full|partial> (<untested checks>)
|
|
121
|
+
|
|
122
|
+
## Brain changes — review these; a bad lesson contaminates every later run
|
|
123
|
+
- taste/<file>: <+n rows | none>
|
|
124
|
+
- registry/<file>: <+n entries | none>
|
|
125
|
+
- architecture/decisions: <ids added | none>
|
|
126
|
+
|
|
127
|
+
## Run
|
|
128
|
+
actor <email> · run <id> · attempts per step: <n:k, …> · models: <skill=model, …>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Title: the story title, prefixed with the conventional-commit type of the
|
|
132
|
+
dominant change (`feat:`, `fix:`, …). Reviewers: `collaboration.pr.reviewers`.
|
|
133
|
+
|
|
134
|
+
## Output
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
pr_url: https://…
|
|
138
|
+
ci_status: green # green | red | pending | unknown
|
|
139
|
+
reason: "" # required when red or unknown: failing job + log tail, or what is missing
|
|
140
|
+
head: <sha>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Red flags — stop
|
|
144
|
+
|
|
145
|
+
- Pushing a branch whose fresh test run is red
|
|
146
|
+
- `git push --force` without `--force-with-lease`
|
|
147
|
+
- Resolving a source-code conflict yourself during the rebase
|
|
148
|
+
- A body section written from memory instead of from the run's files
|
|
149
|
+
- Creating the PR/MR before the inline gate on first open
|
|
150
|
+
- Reporting `green` from the CI tool's summary without reading which checks ran
|
|
151
|
+
- Merging, or marking ready while CI is red or pending
|
|
152
|
+
|
|
153
|
+
## Done
|
|
154
|
+
|
|
155
|
+
PR/MR exists, head SHA matches local HEAD, `ci_status` is known. Return
|
|
156
|
+
`pr_url` and `ci_status`; the orchestrator records `pr_url` on `run_end` and
|
|
157
|
+
routes a `red` result into the rework loop.
|