@rallycry/conveyor-skills 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 +61 -0
- package/bin/cli.mjs +179 -0
- package/package.json +24 -0
- package/skills/conveyor-local-loop/SKILL.md +157 -0
- package/skills/conveyor-plan/SKILL.md +98 -0
- package/skills/conveyor-plan/references/plan-format.md +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @rallycry/conveyor-skills
|
|
2
|
+
|
|
3
|
+
Shared [Claude Code skills](https://code.claude.com/docs) for projects managed
|
|
4
|
+
with [Conveyor](https://github.com/rallycry/conveyor). Installing this package
|
|
5
|
+
and linking it once gives every Claude Code session in your repo the Conveyor
|
|
6
|
+
workflow skills (`/conveyor-plan`, `/conveyor-local-loop`, …), and updates
|
|
7
|
+
arrive through normal dependency bumps — no git submodules, no manual syncing.
|
|
8
|
+
|
|
9
|
+
## Install (once per repo)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add -d @rallycry/conveyor-skills # or: pnpm add -D / npm i -D
|
|
13
|
+
bunx conveyor-skills link # or: pnpm exec / npx conveyor-skills link
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then make it stick:
|
|
17
|
+
|
|
18
|
+
1. **Add the link step to your root `package.json` `prepare` script** so every
|
|
19
|
+
install refreshes the links (new skills in a version bump appear
|
|
20
|
+
automatically; `prepare` runs at the end of every `bun install` — nobody
|
|
21
|
+
ever runs it by hand):
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
{ "scripts": { "prepare": "conveyor-skills link" } }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you already have a `prepare` script, chain it: `"husky && conveyor-skills link"`.
|
|
28
|
+
|
|
29
|
+
2. **Commit the symlinks** it created in `.claude/skills/`. They point into
|
|
30
|
+
`node_modules`, so on a fresh clone they are briefly broken — Claude Code
|
|
31
|
+
silently ignores broken symlinks — and come alive as soon as the first
|
|
32
|
+
install finishes. Committing them means CI agents and teammates get the
|
|
33
|
+
skills with zero extra setup.
|
|
34
|
+
|
|
35
|
+
## How updates flow
|
|
36
|
+
|
|
37
|
+
Skill content is read live from `node_modules`, so any dependency bump
|
|
38
|
+
(`bun update @rallycry/conveyor-skills`, Renovate/Dependabot, a `^` range
|
|
39
|
+
refresh) updates the skills with no further action. Re-linking is only needed
|
|
40
|
+
when a version adds or removes a skill — which the `prepare` hook already
|
|
41
|
+
covers.
|
|
42
|
+
|
|
43
|
+
## What `link` does (and won't do)
|
|
44
|
+
|
|
45
|
+
`conveyor-skills link` writes one relative symlink per skill into
|
|
46
|
+
`<repo>/.claude/skills/`, pointing at the stable
|
|
47
|
+
`node_modules/@rallycry/conveyor-skills/skills/<name>` path. It is idempotent
|
|
48
|
+
and safe:
|
|
49
|
+
|
|
50
|
+
- It only ever replaces or prunes symlinks it owns (target contains
|
|
51
|
+
`conveyor-skills/skills/`).
|
|
52
|
+
- A real directory or a foreign symlink with the same name is left alone with
|
|
53
|
+
a warning — your local skills always win.
|
|
54
|
+
- `conveyor-skills link --check` verifies the links without changing anything
|
|
55
|
+
(exit 1 when out of date) — useful in CI.
|
|
56
|
+
|
|
57
|
+
## Versioning
|
|
58
|
+
|
|
59
|
+
Versions are cut automatically from git tags on the Conveyor repo's `main`
|
|
60
|
+
branch (`conveyor-skills-v<semver>`); every shipped-source change publishes a
|
|
61
|
+
patch release. Consume with a `^` range and bump like any other dependency.
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// conveyor-skills — link this package's Claude Code skills into a consumer
|
|
3
|
+
// repo's .claude/skills/ as relative symlinks. Claude Code follows symlinks
|
|
4
|
+
// there and silently skips broken ones, so the links are safe to commit: they
|
|
5
|
+
// are dead on a fresh clone and come alive after the first install.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// conveyor-skills link (default) create/refresh links, prune stale ones
|
|
9
|
+
// conveyor-skills link --check verify links; exit 1 if missing/stale
|
|
10
|
+
//
|
|
11
|
+
// Only links owned by this package (target path contains conveyor-skills/skills/)
|
|
12
|
+
// are ever replaced or pruned. Real directories and foreign symlinks are left
|
|
13
|
+
// alone with a warning.
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
existsSync,
|
|
17
|
+
lstatSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
readdirSync,
|
|
20
|
+
readlinkSync,
|
|
21
|
+
realpathSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
symlinkSync,
|
|
24
|
+
} from "node:fs";
|
|
25
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
import process from "node:process";
|
|
28
|
+
|
|
29
|
+
const OWNED_MARKER = `conveyor-skills${sep}skills${sep}`;
|
|
30
|
+
|
|
31
|
+
function fail(msg) {
|
|
32
|
+
process.stderr.write(`conveyor-skills: ${msg}\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function warn(msg) {
|
|
37
|
+
process.stderr.write(`conveyor-skills: warning: ${msg}\n`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Nearest ancestor of cwd that looks like a repo root (.git first, then package.json). */
|
|
41
|
+
function findRepoRoot(startDir) {
|
|
42
|
+
for (const marker of [".git", "package.json"]) {
|
|
43
|
+
let dir = startDir;
|
|
44
|
+
for (;;) {
|
|
45
|
+
if (existsSync(join(dir, marker))) return dir;
|
|
46
|
+
const parent = dirname(dir);
|
|
47
|
+
if (parent === dir) break;
|
|
48
|
+
dir = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isSymlink(path) {
|
|
55
|
+
try {
|
|
56
|
+
return lstatSync(path).isSymbolicLink();
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ownedBy(linkPath) {
|
|
63
|
+
try {
|
|
64
|
+
return readlinkSync(linkPath).split("/").join(sep).includes(OWNED_MARKER);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const args = process.argv.slice(2);
|
|
71
|
+
const checkOnly = args.includes("--check");
|
|
72
|
+
const command = args.find((a) => !a.startsWith("-")) ?? "link";
|
|
73
|
+
if (command !== "link")
|
|
74
|
+
fail(`unknown command '${command}' — only 'link' (with optional --check) exists`);
|
|
75
|
+
|
|
76
|
+
// This package's own skills/ dir, resolved through any install symlink (bun
|
|
77
|
+
// may install the package as a symlink into its store).
|
|
78
|
+
const packageDir = realpathSync(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
79
|
+
const sourceSkillsDir = join(packageDir, "skills");
|
|
80
|
+
if (!existsSync(sourceSkillsDir))
|
|
81
|
+
fail(`no skills directory at ${sourceSkillsDir} (broken install?)`);
|
|
82
|
+
|
|
83
|
+
const repoRoot = findRepoRoot(process.cwd());
|
|
84
|
+
if (!repoRoot)
|
|
85
|
+
fail("could not find a repo root (.git or package.json) above the current directory");
|
|
86
|
+
|
|
87
|
+
const linkDir = join(repoRoot, ".claude", "skills");
|
|
88
|
+
|
|
89
|
+
// Prefer the stable node_modules path as the link target so committed links
|
|
90
|
+
// never embed package-store hashes; fall back to the real package location
|
|
91
|
+
// (the monorepo dogfood case resolves to the same node_modules path).
|
|
92
|
+
const stableSkillsDir = join(repoRoot, "node_modules", "@rallycry", "conveyor-skills", "skills");
|
|
93
|
+
const targetSkillsDir = existsSync(stableSkillsDir) ? stableSkillsDir : sourceSkillsDir;
|
|
94
|
+
|
|
95
|
+
const skillNames = readdirSync(sourceSkillsDir, { withFileTypes: true })
|
|
96
|
+
.filter((entry) => entry.isDirectory())
|
|
97
|
+
.map((entry) => entry.name)
|
|
98
|
+
.sort();
|
|
99
|
+
if (skillNames.length === 0) fail(`no skills found in ${sourceSkillsDir}`);
|
|
100
|
+
|
|
101
|
+
let problems = 0;
|
|
102
|
+
let linked = 0;
|
|
103
|
+
let pruned = 0;
|
|
104
|
+
|
|
105
|
+
if (!checkOnly) {
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(linkDir, { recursive: true });
|
|
108
|
+
} catch (err) {
|
|
109
|
+
fail(`cannot create ${linkDir}: ${err?.message ?? err}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const name of skillNames) {
|
|
114
|
+
const linkPath = join(linkDir, name);
|
|
115
|
+
const desiredTarget = relative(linkDir, join(targetSkillsDir, name));
|
|
116
|
+
|
|
117
|
+
const linkExists = isSymlink(linkPath);
|
|
118
|
+
const upToDate = linkExists && readlinkSync(linkPath) === desiredTarget;
|
|
119
|
+
|
|
120
|
+
if (upToDate) continue;
|
|
121
|
+
|
|
122
|
+
if (checkOnly) {
|
|
123
|
+
warn(`link for '${name}' is ${linkExists ? "stale" : "missing"}`);
|
|
124
|
+
problems += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (existsSync(linkPath) && !linkExists) {
|
|
129
|
+
warn(`${linkPath} exists and is not a symlink — leaving it alone`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (linkExists && !ownedBy(linkPath)) {
|
|
133
|
+
warn(
|
|
134
|
+
`${linkPath} is a symlink owned by something else (${readlinkSync(linkPath)}) — leaving it alone`,
|
|
135
|
+
);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
rmSync(linkPath, { force: true });
|
|
141
|
+
symlinkSync(desiredTarget, linkPath);
|
|
142
|
+
linked += 1;
|
|
143
|
+
process.stdout.write(`linked .claude/skills/${name} -> ${desiredTarget}\n`);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
// Symlink-hostile filesystems (e.g. Windows without developer mode) get a
|
|
146
|
+
// warning, not a broken install.
|
|
147
|
+
warn(`could not link '${name}': ${err?.message ?? err}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Prune owned links whose skill no longer ships in the package.
|
|
152
|
+
if (existsSync(linkDir)) {
|
|
153
|
+
for (const entry of readdirSync(linkDir)) {
|
|
154
|
+
const linkPath = join(linkDir, entry);
|
|
155
|
+
if (skillNames.includes(entry) || !isSymlink(linkPath) || !ownedBy(linkPath)) continue;
|
|
156
|
+
if (checkOnly) {
|
|
157
|
+
warn(`link for removed skill '${entry}' still present`);
|
|
158
|
+
problems += 1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
rmSync(linkPath);
|
|
163
|
+
pruned += 1;
|
|
164
|
+
process.stdout.write(`pruned stale link .claude/skills/${entry}\n`);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
warn(`could not prune '${entry}': ${err?.message ?? err}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (checkOnly) {
|
|
172
|
+
if (problems > 0) fail(`${problems} link(s) out of date — run 'conveyor-skills link'`);
|
|
173
|
+
process.stdout.write(`conveyor-skills: all ${skillNames.length} links up to date\n`);
|
|
174
|
+
} else {
|
|
175
|
+
const relLinkDir = relative(repoRoot, linkDir) || linkDir;
|
|
176
|
+
process.stdout.write(
|
|
177
|
+
`conveyor-skills: ${skillNames.length} skill(s) in ${relLinkDir} (${linked} updated, ${pruned} pruned)\n`,
|
|
178
|
+
);
|
|
179
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rallycry/conveyor-skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared Claude Code skills for Conveyor consumer repos, linked into .claude/skills via the conveyor-skills CLI",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"conveyor",
|
|
9
|
+
"skills"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"bin": {
|
|
13
|
+
"conveyor-skills": "bin/cli.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"skills",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conveyor-local-loop
|
|
3
|
+
description: Run this machine as a serial local claudespace — pick the session owner's highest-priority Open Conveyor card, claim it, execute its plan to the PR finish line, repeat. One invocation = one iteration; run continuously with "/loop /conveyor-local-loop" (no interval) and it self-paces (~1-2 min between cards while the queue has work, ~25 min idle polls when empty). Use when the user says "/conveyor-local-loop", "start the local loop", "work my open cards locally", or wants planned cards executed with full local CPU/RAM instead of spawning claudespaces.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Conveyor Local Loop
|
|
7
|
+
|
|
8
|
+
Turn a queue of researched Open cards (usually authored via `/conveyor-plan`)
|
|
9
|
+
into review-ready PRs using this machine, one card at a time. Behave exactly as
|
|
10
|
+
a claudespace agent would: the card IS the spec, task chat is the log, and the
|
|
11
|
+
host repo's CLAUDE.md governs gates, verification, and PR mechanics. This skill
|
|
12
|
+
adds only selection, claiming, cadence, and local-machine hygiene.
|
|
13
|
+
|
|
14
|
+
## Ground rules
|
|
15
|
+
|
|
16
|
+
- **Conveyor is the state store.** The loop session spans days and gets
|
|
17
|
+
compacted; at each iteration re-derive state from MCP reads, never from
|
|
18
|
+
conversation memory.
|
|
19
|
+
- **All Conveyor tools fully-qualified** (`mcp__conveyor__get_task`; bare names
|
|
20
|
+
fail).
|
|
21
|
+
- **WIP = 1.** At most one card claimed by this loop at a time.
|
|
22
|
+
- **Never `start_task`** — that boots a cloud pod and duplicates the work. Only
|
|
23
|
+
exception: the opt-in offload valve below.
|
|
24
|
+
- **Never approve or merge your own PRs.** Finish line = card in ReviewPR with
|
|
25
|
+
green CI; the user takes it from there.
|
|
26
|
+
- **Only cards created by the session owner** (match against
|
|
27
|
+
`mcp__conveyor__get_connection_context`), status Open, unassigned. Teammates'
|
|
28
|
+
cards and pod-claimed cards are off limits.
|
|
29
|
+
- **Pack cards: parents never; children only while the pack is parked.** A
|
|
30
|
+
card with children is the coordinator itself — never claimable. A card with
|
|
31
|
+
a `parentTaskId` IS claimable, but only while the parent's status is neither
|
|
32
|
+
InProgress nor ReviewPR: the pack watchdog and the child-event notifier both
|
|
33
|
+
ignore parked parents, but an actively-orchestrating parent reads a headless
|
|
34
|
+
InProgress child as a dead agent environment and "recovers" it onto a cloud
|
|
35
|
+
pod (observed 2026-07-28 — duplicate implementation). Working a pack locally
|
|
36
|
+
means: leave the parent parked (never Build/start it), work the children
|
|
37
|
+
serially in dependency order, and the user merges each child PR into the
|
|
38
|
+
pack branch to unblock the next.
|
|
39
|
+
- **Clean tree before any branch switch — hard rule.** Dirty `git status` at
|
|
40
|
+
iteration start → touch nothing, report, and idle: the cwd is probably the
|
|
41
|
+
user's interactive checkout. Recommend a dedicated checkout once
|
|
42
|
+
(`git worktree add ../<repo>-loop dev`) and running the loop session there.
|
|
43
|
+
- **Push early.** There is no pod WIP-autosync locally; committed-and-pushed is
|
|
44
|
+
the only durable state. Push the branch (`-u origin`) as soon as it exists.
|
|
45
|
+
|
|
46
|
+
## Iteration order
|
|
47
|
+
|
|
48
|
+
Each invocation does the FIRST of these that produces work, then paces:
|
|
49
|
+
|
|
50
|
+
1. **Recover** — a card with my `[local-loop] claimed` chat marker still
|
|
51
|
+
InProgress without a PR? Resume it. The branch may already exist locally or
|
|
52
|
+
on origin — check both before re-implementing anything.
|
|
53
|
+
2. **Babysit** — my loop-opened PRs (ReviewPR cards): red CI → fix now;
|
|
54
|
+
request-changes or unanswered review comments → address now; green and
|
|
55
|
+
quiet → leave alone.
|
|
56
|
+
3. **Claim** the next card (below).
|
|
57
|
+
4. **Idle** — nothing claimable: pace long.
|
|
58
|
+
|
|
59
|
+
## Claiming
|
|
60
|
+
|
|
61
|
+
1. `mcp__conveyor__list_tasks` with `status: "Open"` — results are already
|
|
62
|
+
priority-then-newest ordered; board priority IS the intelligence, don't
|
|
63
|
+
invent your own ranking. Walk top-down, `mcp__conveyor__get_task` each until
|
|
64
|
+
one passes: created by me, no assignee or active session, an executable
|
|
65
|
+
plan, all `mcp__conveyor__get_dependencies` blockers Complete, no
|
|
66
|
+
`[local-loop] parked:` chat marker without a later human reply (a reply
|
|
67
|
+
un-parks), no child tasks of its own, and — if it has a `parentTaskId` —
|
|
68
|
+
the parent's status is neither InProgress nor ReviewPR (see ground rules).
|
|
69
|
+
Skip `followParentStatus` mirror children. A blocker counts as met only
|
|
70
|
+
when merged-or-beyond (ReviewDev/ReviewLive/Complete) or Cancelled — a
|
|
71
|
+
blocker sitting in ReviewPR is NOT met until its PR merges.
|
|
72
|
+
2. Claim: re-confirm via `get_task` it is still Open, then
|
|
73
|
+
`mcp__conveyor__update_task` → `status: "InProgress"`, then
|
|
74
|
+
`mcp__conveyor__post_to_chat`: `[local-loop] claimed — working locally on
|
|
75
|
+
<hostname>`. Status changed under you → someone else took it; next
|
|
76
|
+
candidate.
|
|
77
|
+
3. Plan missing or failing the context-free-reader bar → don't wing it: post
|
|
78
|
+
what's missing to chat, leave the card Open, skip it.
|
|
79
|
+
|
|
80
|
+
## Execute and finish
|
|
81
|
+
|
|
82
|
+
1. `mcp__conveyor__get_task` (full plan) + `mcp__conveyor__read_task_chat`
|
|
83
|
+
(addenda, user answers). The card must stand alone — if you find yourself
|
|
84
|
+
relying on loop-session memory, stop and re-read the card instead.
|
|
85
|
+
2. Branch from the card's base, never blindly dev: `base` = the card's
|
|
86
|
+
`baseBranch` (a pack child's base is the PARENT's feature branch). If the
|
|
87
|
+
card already has a `githubBranch` with commits on origin, resume THAT
|
|
88
|
+
branch — a prior pod may have landed real work; audit it before
|
|
89
|
+
re-implementing anything. Else `git fetch origin <base> && git checkout -B
|
|
90
|
+
<feat|fix|chore>/<slug> origin/<base>`. Reinstall deps if the lockfile
|
|
91
|
+
changed.
|
|
92
|
+
3. Work the plan. Post chat updates at real milestones only (claim, blocking
|
|
93
|
+
discovery, gates green, PR) — not play-by-play.
|
|
94
|
+
4. Verify per the host repo's CLAUDE.md policy (scoped gates). UI-visible
|
|
95
|
+
change → capture screenshot/recording evidence with the repo's tooling and
|
|
96
|
+
attach via `mcp__conveyor__upload_attachment` before opening the PR.
|
|
97
|
+
5. Refresh against the card's base (`git fetch origin <base> && git merge
|
|
98
|
+
origin/<base> --no-edit && git push`), then
|
|
99
|
+
`mcp__conveyor__create_pull_request` with `head:` your branch and `base:`
|
|
100
|
+
the card's base branch. Pack child: re-check the parent's status first —
|
|
101
|
+
if it went InProgress/ReviewPR, park instead (post `[local-loop] parked:
|
|
102
|
+
pack coordinator active — yielding`, leave the branch pushed) and let the
|
|
103
|
+
coordinator take over. The card moves to ReviewPR. Post a chat summary:
|
|
104
|
+
what shipped, how verified, what to look at.
|
|
105
|
+
6. Confirm CI actually started (read-only `gh pr checks`); do NOT wait on it —
|
|
106
|
+
later iterations babysit.
|
|
107
|
+
|
|
108
|
+
**Parked protocol** — after 2 genuinely different failed approaches, or on a
|
|
109
|
+
decision only the user can make: post `[local-loop] parked: <reason + the
|
|
110
|
+
specific question>`, set status back to `"Open"`, restore the tree
|
|
111
|
+
(`git checkout dev`), move on. The user's next chat reply is the un-park
|
|
112
|
+
signal.
|
|
113
|
+
|
|
114
|
+
## Pacing (dynamic /loop only)
|
|
115
|
+
|
|
116
|
+
Under `/loop` with no interval, end EVERY iteration with exactly one
|
|
117
|
+
`ScheduleWakeup` (prompt = the original /loop input verbatim):
|
|
118
|
+
|
|
119
|
+
| State | Delay | Reason should say |
|
|
120
|
+
|-------|-------|-------------------|
|
|
121
|
+
| A background gate/agent is in flight — its completion notification is the real wake | 1200–1800s fallback | "fallback while <gate> runs — its notification wakes me sooner" |
|
|
122
|
+
| ANY actionable work exists: claimable cards, a PR still to open, red/pending CI, review comments | 60–90s | queue depth / which item is next |
|
|
123
|
+
| Queue enumerated as empty THIS iteration, all loop PRs green and quiet | 1200–1800s | queue empty, idle poll |
|
|
124
|
+
| Loop-fatal: MCP dead after 2 tries, dirty tree, broken repo | notify the user (PushNotification if available), then 1800s — or `stop: true` if continuing is unsafe | what is wrong |
|
|
125
|
+
|
|
126
|
+
The long idle tier is EARNED, never defaulted: it requires having enumerated
|
|
127
|
+
the queue this very iteration and found zero actionable work. When unsure
|
|
128
|
+
which tier applies, take the short one — a wasted 60s wake costs less than a
|
|
129
|
+
30-minute stall on live work.
|
|
130
|
+
|
|
131
|
+
Invoked bare (no /loop)? Run one iteration, report, and suggest
|
|
132
|
+
`/loop /conveyor-local-loop` — don't self-schedule.
|
|
133
|
+
|
|
134
|
+
## Offload valve (opt-in)
|
|
135
|
+
|
|
136
|
+
Only with an explicit `offload=N` argument: when 4+ claimable cards queue up,
|
|
137
|
+
`mcp__conveyor__start_task` up to N of the smallest into claudespaces and say
|
|
138
|
+
so in the iteration summary. Without the argument, never — just report backlog
|
|
139
|
+
depth each iteration so the user can offload manually.
|
|
140
|
+
|
|
141
|
+
## What this is not
|
|
142
|
+
|
|
143
|
+
- Not a reviewer: never `approve_task`, `approve_and_merge_pr`, or
|
|
144
|
+
`request_changes` on anything.
|
|
145
|
+
- Not a pod: no sandbox, no WIP snapshots, and the dev DB + dev-server ports
|
|
146
|
+
are shared with the user's interactive sessions — no destructive
|
|
147
|
+
experiments, never reset the dev DB, reuse a running dev stack rather than
|
|
148
|
+
fighting over ports.
|
|
149
|
+
- Not a parallel executor: one card at a time is the point (the full machine
|
|
150
|
+
per gate). Backlogged? That is what claudespaces — or the offload valve —
|
|
151
|
+
are for.
|
|
152
|
+
|
|
153
|
+
## Improve This Skill
|
|
154
|
+
|
|
155
|
+
If this skill was insufficient or slowed the work down, file it with
|
|
156
|
+
`mcp__conveyor__create_suggestion` on the Conveyor project: the issue,
|
|
157
|
+
evidence, and proposed fix.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conveyor-plan
|
|
3
|
+
description: Turn a raw feature or bug idea into a research-backed, immediately-buildable Conveyor card for a claudespace agent to execute later. Use when the user says "/conveyor-plan <idea>", "plan this as a conveyor card", "write this up for a claudespace agent", or wants deep planning that ends in a handoff card rather than local implementation. Runs plan-mode-quality research (codebase + prior Conveyor cards + prod logs), asks clarifying questions only when scope is genuinely ambiguous, then creates the card and moves it to Open so identification auto-fills story points, tags, icon, and agent.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Conveyor Plan → Handoff Card
|
|
7
|
+
|
|
8
|
+
Produce ONE artifact: a Conveyor card whose plan a fresh claudespace agent can
|
|
9
|
+
execute with zero session context. Everything the executor needs must be ON the
|
|
10
|
+
card — it never sees this conversation.
|
|
11
|
+
|
|
12
|
+
The division of labor is fixed: **you research and recommend; Conveyor's
|
|
13
|
+
identification decides.** Moving the card to Open fires identification
|
|
14
|
+
automatically (story points, icon, agent, and tags if you set none). Never set
|
|
15
|
+
icon or story points yourself, and never `start_task` unless the user asks.
|
|
16
|
+
|
|
17
|
+
## Phase 0 — Resolve context
|
|
18
|
+
|
|
19
|
+
1. `mcp__conveyor__get_connection_context` (all Conveyor tools fully-qualified;
|
|
20
|
+
bare names fail). No default project? `mcp__conveyor__list_projects` and
|
|
21
|
+
match `githubRepoOwner/Name` to the cwd's `git remote`. Ambiguous → ask.
|
|
22
|
+
2. Copy project IDs exactly — a mistyped `projectId` surfaces as
|
|
23
|
+
"Insufficient permissions", not "not found". Verify the ID before
|
|
24
|
+
concluding you lack access.
|
|
25
|
+
|
|
26
|
+
## Phase 1 — Research (parallelize)
|
|
27
|
+
|
|
28
|
+
Scale to the idea's size; a one-file tweak needs minutes, not a survey.
|
|
29
|
+
|
|
30
|
+
- **Codebase**: use a code-graph or architecture skill if the repo provides
|
|
31
|
+
one for architecture/flow questions; `rg` for exact strings. Fan out
|
|
32
|
+
Explore subagents for broad sweeps.
|
|
33
|
+
- **Prior art**: `mcp__conveyor__search_tasks` on 2-3 keyword variants
|
|
34
|
+
(`typeFilters` to include incidents/suggestions when relevant), then
|
|
35
|
+
`mcp__conveyor__get_task` on the closest hits. You're looking for duplicates
|
|
36
|
+
(stop and surface), related shipped work (reuse its patterns), and
|
|
37
|
+
cancelled attempts (learn why before re-proposing).
|
|
38
|
+
- **Prod signals** (only when the feature touches live behavior):
|
|
39
|
+
`mcp__conveyor__query_gcp_logs` / `mcp__conveyor__query_grafana_logs` for
|
|
40
|
+
error rates, actual usage, current behavior.
|
|
41
|
+
- **Blast radius**: enumerate callers/consumers of every surface the plan
|
|
42
|
+
touches (shared packages, DB schema, published packages, webhooks, other
|
|
43
|
+
cards in flight on the same files). Unintended impact goes in the plan's
|
|
44
|
+
Notes, not in your head.
|
|
45
|
+
|
|
46
|
+
## Phase 2 — Clarify (only if needed)
|
|
47
|
+
|
|
48
|
+
Ask the user only decisions that change the plan's shape — scope cuts, UX
|
|
49
|
+
choices, irreversible tradeoffs. Batch them in one round; never drip. Facts
|
|
50
|
+
the repo can answer are yours to find, not theirs.
|
|
51
|
+
|
|
52
|
+
## Phase 3 — Draft the plan
|
|
53
|
+
|
|
54
|
+
Use the plan format in [references/plan-format.md](references/plan-format.md):
|
|
55
|
+
Objective / Approach / Implementation Steps / Testing / Notes.
|
|
56
|
+
|
|
57
|
+
Actionability bar — every step must survive a context-free reader:
|
|
58
|
+
|
|
59
|
+
- Name exact repo-relative files and symbols, with the pattern to follow
|
|
60
|
+
("mirror `apps/api/src/services/task/methods/mutations.ts`").
|
|
61
|
+
- Testing = runnable commands + observable acceptance criteria.
|
|
62
|
+
- Notes = risks, blast radius, dependencies, and decisions already made (so
|
|
63
|
+
the executor doesn't relitigate them).
|
|
64
|
+
- No "as discussed", no links to this chat, no TODOs the executor must
|
|
65
|
+
research from scratch.
|
|
66
|
+
|
|
67
|
+
Size it: 1 SP default, 2 multi-file, 3 complex patterns/design, 5 hard. Larger
|
|
68
|
+
→ propose a pack (parent + `mcp__conveyor__create_subtask` children with
|
|
69
|
+
`mcp__conveyor__add_dependency` edges) instead of one mega-card.
|
|
70
|
+
|
|
71
|
+
Show the user title + description + plan + SP/tag recommendation before
|
|
72
|
+
touching Conveyor, unless they asked you to just ship it.
|
|
73
|
+
|
|
74
|
+
## Phase 4 — Create and hand off (order matters)
|
|
75
|
+
|
|
76
|
+
Identification fires the moment the card lands beyond Planning and reads task
|
|
77
|
+
chat for your recommendation — so chat BEFORE the status flip:
|
|
78
|
+
|
|
79
|
+
1. `mcp__conveyor__create_task` — `status: "Planning"`, concise imperative
|
|
80
|
+
title, 2-4 sentence description (the board-card summary), full plan.
|
|
81
|
+
Optionally `tags`: only names from `mcp__conveyor__list_tags` that clearly
|
|
82
|
+
fit (unknown names are rejected; pre-set tags make identification skip tag
|
|
83
|
+
assignment — when unsure, omit and let it choose).
|
|
84
|
+
2. `mcp__conveyor__post_to_chat` — one message: recommended SP + one-line
|
|
85
|
+
rationale, tag suggestion, any executor warnings.
|
|
86
|
+
3. `mcp__conveyor__update_task` — `status: "Open"` (+ `risk` when the plan
|
|
87
|
+
touches critical surface). This triggers identification.
|
|
88
|
+
4. Verify with `mcp__conveyor__get_task`: status Open, `agentId` set. SP and
|
|
89
|
+
tags are NOT in the `get_task` response — confirm on the board if needed.
|
|
90
|
+
5. Report the card URL (`<project url>/cards/<slug>`), what identification
|
|
91
|
+
filled, and that it's ready for `start_task`. Park-in-Planning instead if
|
|
92
|
+
the user wants to review first; offer (don't run) `start_task`.
|
|
93
|
+
|
|
94
|
+
## Improve This Skill
|
|
95
|
+
|
|
96
|
+
If this skill was insufficient or slowed the work down, file it with
|
|
97
|
+
`mcp__conveyor__create_suggestion` on the Conveyor project: the issue,
|
|
98
|
+
evidence, and proposed fix.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Conveyor Plan Format & Sizing
|
|
2
|
+
|
|
3
|
+
## Plan Format
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
## Objective
|
|
7
|
+
One sentence: what this task accomplishes and why.
|
|
8
|
+
|
|
9
|
+
## Approach
|
|
10
|
+
High-level strategy with relevant files/patterns/APIs.
|
|
11
|
+
|
|
12
|
+
## Implementation Steps
|
|
13
|
+
1. Concrete step.
|
|
14
|
+
2. Concrete step.
|
|
15
|
+
|
|
16
|
+
## Testing
|
|
17
|
+
- Commands and manual checks.
|
|
18
|
+
- Edge cases.
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
- Dependencies, risks, blockers, or files likely touched.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Sizing
|
|
25
|
+
|
|
26
|
+
Default **1 SP**. Use **2 SP** for multi-file work, **3 SP** for complex
|
|
27
|
+
patterns/design choices, **5 SP** only for hard work. Split anything larger.
|
|
28
|
+
|
|
29
|
+
## Packs (parent + child tasks)
|
|
30
|
+
|
|
31
|
+
A **pack** is Conveyor's bundle shape: a parent card with child cards.
|
|
32
|
+
`create_subtask(parentTaskId, ...)` is the **only** way to parent a card —
|
|
33
|
+
`create_task`/`update_task` have no parent field. Each child is a full card
|
|
34
|
+
(own chat, plan, story points). Split into a pack only when the work is
|
|
35
|
+
genuinely multiple independently buildable pieces (8-SP-tier); otherwise keep
|
|
36
|
+
one card.
|
|
37
|
+
|
|
38
|
+
- **Orchestration packs** (future work): `start_task` on the parent boots a
|
|
39
|
+
*pack runner* that starts each ready child's own build/PR, honoring
|
|
40
|
+
`add_dependency` edges (independent children run in parallel). Give each
|
|
41
|
+
child a detailed plan with a **Testing / Verification** section and a
|
|
42
|
+
`storyPointValue`. The parent's `featureBranch` toggle (default on) makes
|
|
43
|
+
children branch off and PR back into the parent's branch instead of dev.
|
|
44
|
+
- **Mirror packs** (already-done work shipping in ONE PR on the parent's
|
|
45
|
+
branch): create children with **`followParentStatus: true`** — title and a
|
|
46
|
+
plain-language description only. A follower mirrors its parent's status
|
|
47
|
+
automatically through the whole pipeline, identification sizes it, and it
|
|
48
|
+
never posts its own Slack card. Evidence rolls up to the **parent**. Never
|
|
49
|
+
`start_task` a mirror pack's parent, and leave children's PR fields empty —
|
|
50
|
+
the parent owns the PR.
|
|
51
|
+
|
|
52
|
+
## Status Flow
|
|
53
|
+
|
|
54
|
+
`Planning -> Open -> InProgress -> ReviewPR -> ReviewDev -> ReviewLive -> Complete`
|
|
55
|
+
|
|
56
|
+
`Cancelled` is terminal. A task must be `Open` before `start_task`.
|