@0xgeorgemathew/zcode 0.4.1 → 0.5.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 +81 -15
- package/bin/zcode.js +126 -20
- package/lib/appserver.js +16 -1
- package/lib/bootstrap.js +2 -2
- package/lib/brief.js +69 -0
- package/lib/checks.js +3 -2
- package/lib/commands/doctor.js +3 -0
- package/lib/commands/exec.js +101 -35
- package/lib/commands/mcp-list.js +1 -1
- package/lib/commands/plan.js +301 -0
- package/lib/commands/sessions.js +60 -11
- package/lib/commands/skills.js +39 -0
- package/lib/contract.js +220 -17
- package/lib/executor.js +142 -34
- package/lib/isolation.js +36 -4
- package/lib/models.js +84 -0
- package/lib/plan.js +424 -0
- package/lib/ports.js +45 -0
- package/lib/skills.js +64 -0
- package/lib/store.js +50 -0
- package/lib/taskspec.js +107 -0
- package/lib/ui.js +7 -2
- package/lib/workspace.js +24 -7
- package/package.json +1 -2
- package/skills/README.md +9 -3
- package/skills/claude-code/zcode/SKILL.md +104 -74
- package/skills/codex/zcode/SKILL.md +104 -76
- package/lib/install-skills.js +0 -54
package/lib/taskspec.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Task contract from its sources: a base object (JSON contract / markdown
|
|
3
|
+
// brief) plus CLI-style additions. Wraps normalizeContract with what needs
|
|
4
|
+
// the filesystem: inlining "@file" entries, dropping a --file that is also
|
|
5
|
+
// inlined as context, and validating unit ownership against the session's.
|
|
6
|
+
// Shared by `zcode task` and anything else that builds contracts (plan files).
|
|
7
|
+
//
|
|
8
|
+
// Throws ContractError for every problem, before any engine spawn.
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { normalizeContract, ContractError } = require('./contract.js');
|
|
12
|
+
const { workspaceRoot, normalizeOwns, isOwned, ownsOverlap, OwnershipError } = require('./workspace.js');
|
|
13
|
+
|
|
14
|
+
const FILE_REF = /^@(\S+)$/;
|
|
15
|
+
|
|
16
|
+
const canon = (p) => {
|
|
17
|
+
try {
|
|
18
|
+
return fs.realpathSync(p);
|
|
19
|
+
} catch {
|
|
20
|
+
return p;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const toArray = (v) => (v === undefined || v === null ? [] : Array.isArray(v) ? v : [v]);
|
|
25
|
+
|
|
26
|
+
// Units must stay inside the session's owns and must not overlap each other.
|
|
27
|
+
function validateUnits(contract, cwd) {
|
|
28
|
+
const units = contract.units || [];
|
|
29
|
+
if (!units.length) return;
|
|
30
|
+
let root;
|
|
31
|
+
const norm = (patterns, flag) => {
|
|
32
|
+
root = root || workspaceRoot(cwd);
|
|
33
|
+
try {
|
|
34
|
+
return normalizeOwns(patterns, cwd, root, flag);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof OwnershipError) throw new ContractError(err.message);
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const sessionOwns = norm(contract.owns || [], '--owns');
|
|
41
|
+
const owned = units
|
|
42
|
+
.filter((u) => u.owns && u.owns.length)
|
|
43
|
+
.map((u) => ({ unit: u, patterns: norm(u.owns, `unit "${u.name}" owns`) }));
|
|
44
|
+
for (const { unit, patterns } of owned) {
|
|
45
|
+
const at = patterns.findIndex((p) => !isOwned(p, sessionOwns));
|
|
46
|
+
if (at >= 0) {
|
|
47
|
+
throw new ContractError(
|
|
48
|
+
`unit "${unit.name}" owns "${unit.owns[at]}", which is outside the session's owns (${(contract.owns || []).join(', ') || 'whole workspace'})`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (let i = 0; i < owned.length; i++) {
|
|
53
|
+
for (let j = i + 1; j < owned.length; j++) {
|
|
54
|
+
if (ownsOverlap(owned[i].patterns, owned[j].patterns)) {
|
|
55
|
+
throw new ContractError(
|
|
56
|
+
`units "${owned[i].unit.name}" and "${owned[j].unit.name}" have overlapping owns (${owned[i].unit.owns.join(', ')} / ${owned[j].unit.owns.join(', ')}); units must be disjoint`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// base: parsed contract object; baseDir: where its "@file" entries resolve
|
|
64
|
+
// (the contract file's directory, else cwd). additions: the CLI flags
|
|
65
|
+
// (strings; "@file" resolves against the process cwd). cwd: --cwd.
|
|
66
|
+
function buildContract({ base = {}, baseDir = process.cwd(), objective = '', additions = {}, cwd = process.cwd() }) {
|
|
67
|
+
const inlined = new Set();
|
|
68
|
+
const inline = (list, dir, label) =>
|
|
69
|
+
toArray(list).map((item) => {
|
|
70
|
+
const text = String(item).trim();
|
|
71
|
+
const m = FILE_REF.exec(text);
|
|
72
|
+
if (!m) return text;
|
|
73
|
+
const file = path.resolve(dir, m[1]);
|
|
74
|
+
try {
|
|
75
|
+
const body = fs.readFileSync(file, 'utf8');
|
|
76
|
+
inlined.add(canon(file));
|
|
77
|
+
return body;
|
|
78
|
+
} catch {
|
|
79
|
+
throw new ContractError(`${label} file "${m[1]}" could not be read.`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let b = base;
|
|
84
|
+
if (b && typeof b === 'object' && !Array.isArray(b)) {
|
|
85
|
+
b = { ...b };
|
|
86
|
+
for (const key of ['context', 'decisions']) {
|
|
87
|
+
if (b[key] !== undefined) b[key] = inline(b[key], baseDir, `contract ${key}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const flagDir = process.cwd();
|
|
91
|
+
const contract = normalizeContract(b, {
|
|
92
|
+
...additions,
|
|
93
|
+
objective,
|
|
94
|
+
context: inline(additions.context, flagDir, '--context'),
|
|
95
|
+
decisions: inline(additions.decisions, flagDir, '--decisions'),
|
|
96
|
+
});
|
|
97
|
+
// A file the executor already gets inlined is not also listed to read.
|
|
98
|
+
if (inlined.size) {
|
|
99
|
+
contract.files = contract.files.filter(
|
|
100
|
+
(f) => ![cwd, baseDir, flagDir].some((dir) => inlined.has(canon(path.resolve(dir, f))))
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
validateUnits(contract, cwd);
|
|
104
|
+
return contract;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { buildContract, validateUnits };
|
package/lib/ui.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// ✓ npm test 3.2s · ✗ npm run lint exit 1 1.1s checks
|
|
13
13
|
// │ thinking … reasoning (opt-in)
|
|
14
14
|
|
|
15
|
+
const { questionText } = require('./contract.js');
|
|
16
|
+
|
|
15
17
|
function useColor(stream, env = process.env) {
|
|
16
18
|
return Boolean(stream && stream.isTTY) && !('NO_COLOR' in env) && env.TERM !== 'dumb';
|
|
17
19
|
}
|
|
@@ -157,7 +159,7 @@ function formatResult(r, { color = false } = {}) {
|
|
|
157
159
|
L.push(`changed ${files.length} file${files.length === 1 ? '' : 's'}`);
|
|
158
160
|
for (const f of files.slice(0, 30)) {
|
|
159
161
|
const mark = f.change === 'added' ? 'A' : f.change === 'deleted' ? 'D' : 'M';
|
|
160
|
-
const note = f.concurrentSession ? c.dim(` (session ${f.concurrentSession})`) : !f.owned ? c.red(' outside owned scope') : '';
|
|
162
|
+
const note = f.touched ? c.dim(' (touched)') : f.concurrentSession ? c.dim(` (session ${f.concurrentSession})`) : !f.owned ? c.red(' outside owned scope') : '';
|
|
161
163
|
L.push(` ${mark} ${f.path}${note}`);
|
|
162
164
|
}
|
|
163
165
|
if (files.length > 30) L.push(c.dim(` … ${files.length - 30} more`));
|
|
@@ -181,7 +183,10 @@ function formatResult(r, { color = false } = {}) {
|
|
|
181
183
|
}
|
|
182
184
|
for (const [label, items] of [
|
|
183
185
|
['issues', r.issues],
|
|
184
|
-
['questions', r.questions],
|
|
186
|
+
['questions', (r.questions || []).map(questionText)],
|
|
187
|
+
['decisions', (r.decisions || []).map((d) => `${d.what}${d.why ? ` (${d.why})` : ''}`)],
|
|
188
|
+
['units', (r.units || []).map((u) => `${u.name}: ${u.status || '?'}${u.note ? ` - ${u.note}` : ''}`)],
|
|
189
|
+
['visual', (r.visual || []).map((v) => `${v.met === true ? 'met' : 'UNMET'} ${v.criterion || ''}${v.viewport ? ` @${v.viewport}` : ''}${v.note ? ` - ${v.note}` : ''}`)],
|
|
185
190
|
['unresolved', r.unresolved],
|
|
186
191
|
['not verified', r.notVerified],
|
|
187
192
|
]) {
|
package/lib/workspace.js
CHANGED
|
@@ -42,16 +42,16 @@ function workspaceRoot(cwd) {
|
|
|
42
42
|
|
|
43
43
|
// Ownership patterns arrive relative to --cwd; store them relative to the
|
|
44
44
|
// workspace root, posix-style, so every session compares like with like.
|
|
45
|
-
function normalizeOwns(patterns, cwd, root) {
|
|
45
|
+
function normalizeOwns(patterns, cwd, root, flag = '--owns') {
|
|
46
46
|
const realCwd = fs.realpathSync(cwd);
|
|
47
47
|
return patterns.map((raw) => {
|
|
48
48
|
const pattern = String(raw).trim();
|
|
49
|
-
if (!pattern) throw new OwnershipError(
|
|
49
|
+
if (!pattern) throw new OwnershipError(`${flag} pattern must not be empty`);
|
|
50
50
|
const abs = path.isAbsolute(pattern) ? pattern : path.join(realCwd, pattern);
|
|
51
51
|
const rel = toPosix(path.relative(root, abs));
|
|
52
52
|
if (rel === '' || rel === '.') return '**';
|
|
53
53
|
if (rel.startsWith('../') || rel === '..' || path.isAbsolute(rel)) {
|
|
54
|
-
throw new OwnershipError(
|
|
54
|
+
throw new OwnershipError(`${flag} "${pattern}" is outside the workspace ${root}`);
|
|
55
55
|
}
|
|
56
56
|
return /[/\\]$/.test(pattern) && !hasWildcard(rel) ? `${rel}/**` : rel;
|
|
57
57
|
});
|
|
@@ -150,10 +150,10 @@ function readLeases(dir) {
|
|
|
150
150
|
// lease. Writing first and reading second means two sessions starting at
|
|
151
151
|
// the same instant both see each other; the later (startedAt, id) backs
|
|
152
152
|
// off, so exactly one proceeds.
|
|
153
|
-
function acquireLease({ id, root, owns, allowOverlap = false, env = process.env }) {
|
|
153
|
+
function acquireLease({ id, root, owns, touch = [], allowOverlap = false, env = process.env }) {
|
|
154
154
|
const dir = leaseDir(root, env);
|
|
155
155
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
156
|
-
const mine = { id, pid: process.pid, root, owns, startedAt: Date.now() };
|
|
156
|
+
const mine = { id, pid: process.pid, root, owns, touch, startedAt: Date.now() };
|
|
157
157
|
const file = path.join(dir, `${id}.json`);
|
|
158
158
|
fs.writeFileSync(file, JSON.stringify(mine) + '\n', { mode: 0o600 });
|
|
159
159
|
|
|
@@ -166,6 +166,14 @@ function acquireLease({ id, root, owns, allowOverlap = false, env = process.env
|
|
|
166
166
|
}
|
|
167
167
|
others.push(lease);
|
|
168
168
|
}
|
|
169
|
+
// --touch never reserves anything, but it may not edit what a live
|
|
170
|
+
// session owns (an empty ownership is the whole workspace). Not ordered by
|
|
171
|
+
// start time: the toucher is the one that has to step aside.
|
|
172
|
+
const touchConflicts = touch.length && !allowOverlap ? others.filter((o) => ownsOverlap(touch, o.owns)) : [];
|
|
173
|
+
if (touchConflicts.length) {
|
|
174
|
+
fs.rmSync(file, { force: true });
|
|
175
|
+
return { ok: false, conflicts: touchConflicts, others, touch: true };
|
|
176
|
+
}
|
|
169
177
|
const conflicts = others.filter((o) => ownsOverlap(owns, o.owns));
|
|
170
178
|
const blocking = conflicts.filter(
|
|
171
179
|
(o) => o.startedAt < mine.startedAt || (o.startedAt === mine.startedAt && o.id < id)
|
|
@@ -183,7 +191,14 @@ function liveLeases(root, env = process.env) {
|
|
|
183
191
|
|
|
184
192
|
// --- change tracking ----------------------------------------------------------
|
|
185
193
|
|
|
186
|
-
|
|
194
|
+
// zcode's own per-session directories (<cwd>/.zcode/out/<id>, project skills)
|
|
195
|
+
// are never changes of the task, at any depth. The Playwright MCP's scratch
|
|
196
|
+
// dir (.playwright-mcp: console logs, page snapshots) written by --visual
|
|
197
|
+
// verification is tooling output, not task output.
|
|
198
|
+
const TOOL_DIRS = new Set(['.zcode', '.playwright-mcp']);
|
|
199
|
+
const isZcodePath = (rel) => rel.split('/').some((seg) => TOOL_DIRS.has(seg));
|
|
200
|
+
|
|
201
|
+
const WALK_SKIP = new Set(['.git', 'node_modules', '.zcode', '.playwright-mcp', '.DS_Store']);
|
|
187
202
|
const WALK_LIMIT = 20000;
|
|
188
203
|
const HASH_LIMIT = 8 * 1024 * 1024;
|
|
189
204
|
|
|
@@ -253,6 +268,7 @@ function snapshot(root) {
|
|
|
253
268
|
try {
|
|
254
269
|
const entries = new Map();
|
|
255
270
|
for (const [rel, code] of gitStatus(root)) {
|
|
271
|
+
if (isZcodePath(rel)) continue;
|
|
256
272
|
entries.set(rel, { code, sig: fileSignature(path.join(root, rel)) });
|
|
257
273
|
}
|
|
258
274
|
return { kind: 'git', root, head: gitHead(root), entries };
|
|
@@ -314,7 +330,7 @@ function diffSnapshots(before, after) {
|
|
|
314
330
|
if (after.kind === 'git' && before.head && after.head && before.head !== after.head) {
|
|
315
331
|
const seen = new Set(out.map((f) => f.path));
|
|
316
332
|
for (const f of committedChanges(after.root, before.head, after.head)) {
|
|
317
|
-
if (!seen.has(f.path)) out.push({ ...f, committed: true });
|
|
333
|
+
if (!seen.has(f.path) && !isZcodePath(f.path)) out.push({ ...f, committed: true });
|
|
318
334
|
}
|
|
319
335
|
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
320
336
|
}
|
|
@@ -327,6 +343,7 @@ module.exports = {
|
|
|
327
343
|
normalizeOwns,
|
|
328
344
|
globToRegExp,
|
|
329
345
|
isOwned,
|
|
346
|
+
isZcodePath,
|
|
330
347
|
ownsOverlap,
|
|
331
348
|
acquireLease,
|
|
332
349
|
liveLeases,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xgeorgemathew/zcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Lean GLM-5.3 execution engine for orchestrator agents: task contracts, independent sessions, zcode-run verification, structured handbacks, subagents, MCP, skills. Wraps the ZCode engine. Zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"postinstall": "node lib/install-skills.js",
|
|
25
24
|
"test": "node --test --test-concurrency=1 \"test/unit/*.test.js\"",
|
|
26
25
|
"test:live": "node --test --test-concurrency=1 \"test/live/*.test.js\"",
|
|
27
26
|
"prepublishOnly": "npm test && npm run test:live",
|
package/skills/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Orchestrator skills
|
|
2
2
|
|
|
3
|
-
These skills teach an orchestrating agent
|
|
3
|
+
These skills teach an orchestrating agent to make the design and logic decisions once, delegate the implementation to zcode (one session with units, parallel sessions, or a plan), and read the compact handbacks, spending its own tokens only on decisions and exceptions. Both describe the same CLI; they differ only in runtime notes (tool timeouts for Claude Code, sandbox and network escalation for Codex).
|
|
4
4
|
|
|
5
5
|
| platform | skill in this repo | format |
|
|
6
6
|
|---|---|---|
|
|
@@ -9,9 +9,15 @@ These skills teach an orchestrating agent how to delegate work to zcode, run ses
|
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
-
`npm install -g @0xgeorgemathew/zcode
|
|
12
|
+
After `npm install -g @0xgeorgemathew/zcode`, install both skills with:
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
```sh
|
|
15
|
+
zcode skills install # or: zcode skills install claude | codex
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This copies the Claude Code skill to `~/.claude/skills/zcode` and the Codex skill to `${CODEX_HOME:-~/.codex}/skills/zcode`, replacing only an earlier copy of the zcode skill. Run it again after upgrading zcode to refresh them. `zcode skills` shows what is installed, and `zcode skills uninstall` removes them. The package runs no install scripts, so npm never asks to allow one.
|
|
19
|
+
|
|
20
|
+
To copy them by hand instead, run these from a checkout of this repo, or from the installed package (`$(npm root -g)/@0xgeorgemathew/zcode`), which ships `skills/`.
|
|
15
21
|
|
|
16
22
|
**Claude Code.** Personal skills, available in every project:
|
|
17
23
|
|
|
@@ -1,112 +1,142 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zcode
|
|
3
|
-
description: Delegate scoped implementation work to the zcode CLI (GLM-5.3 executor) and consume its verified handback. Use when you are orchestrating
|
|
3
|
+
description: Delegate scoped implementation work to the zcode CLI (GLM-5.3 executor, vision-capable, has Playwright) and consume its compact verified handback. Use when you are orchestrating: you make the product, design and logic decisions once, then zcode implements, tests, visually verifies and repairs — in one session with subagent units, several parallel sessions, or a dependency plan — while you spend tokens only on decisions and exceptions.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operating zcode as an executor
|
|
7
7
|
|
|
8
|
-
zcode is a CLI execution engine. You
|
|
8
|
+
zcode is a CLI execution engine. You decide; it builds. It implements a scoped task in its own session, runs your checks itself, screenshots and judges UI with its own vision when you ask, repairs failures within a budget, and returns a small structured handback.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**The point is token economy.** Your tokens go to decisions and exceptions. Everything mechanical — writing code, running builds and tests, reading diffs, looking at screenshots, fixing small failures — is zcode's. If you are doing any of that after dispatch, you are paying twice.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Check readiness once per machine: `zcode doctor` (exit 3 → `zcode login`; exit 4 → engine missing). Don't run `--help`: everything you need is here.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- Don't use it for product or design decisions, open-ended exploration, or work you can't describe a finished state for. Decide those yourself first, then delegate the implementation.
|
|
16
|
-
- Size each session so you could review its result in one sitting. Several small sessions beat one huge one, because each returns its own evidence.
|
|
14
|
+
## The workflow
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
1. **Read** the codebase as much as you need to plan. This is the one phase where reading files is expected.
|
|
17
|
+
2. **Decide** everything a user could see or a system depends on (checklists below). Write the decisions down once, as decisions — a file such as `docs/decisions.md` that sessions receive with `--decisions @file`.
|
|
18
|
+
3. **Shape** the work: one session with units, several sessions, or a plan (next section).
|
|
19
|
+
4. **Preflight** (optional, recommended for anything large or ambiguous): `--preflight` makes zcode list every question it would otherwise guess. Answer them all in one edit to the decisions file.
|
|
20
|
+
5. **Dispatch and wait** with `--detach` + `zcode wait`, or `zcode plan run` + `zcode plan wait`.
|
|
21
|
+
6. **Handle exceptions only.** `completed` → move on. Anything else → read only what the brief shows, answer or adjust in one line with `zcode resume`.
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
## Deciding: leave nothing open-ended
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|---|---|---|
|
|
24
|
-
| objective | positional text | always: the outcome, not the steps |
|
|
25
|
-
| acceptance | `--accept "…"` | you have criteria beyond "it works" |
|
|
26
|
-
| checks | `--check "cmd"` | always if possible: shell commands zcode runs itself (build, tests, lint, a grep) |
|
|
27
|
-
| owns | `--owns path/or/glob` | other sessions may touch the same repo |
|
|
28
|
-
| files | `--file path` | specific files to read first |
|
|
29
|
-
| context / design | `--context "…"` or `--context @notes.md` | design decisions, copy, references, constraints |
|
|
30
|
-
| everything | `--task contract.json` | larger handoffs (keys: objective, scope, constraints, design, acceptance, checks, files, references, context, owns, limits, model, effort) |
|
|
25
|
+
The executor implements binding decisions exactly and is told not to invent anything user-visible or behavioural. Gaps either block it (a round trip for you) or get the most conservative default. Close them up front.
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
**Front end:** palette (exact values), type families and scale, spacing scale, radii, iconography/illustration style; every section's layout and hierarchy at each breakpoint you care about; exact copy; component states (hover, focus, active, disabled, loading, empty, error); motion (what moves, property, distance, duration, easing, stagger, trigger, reduced-motion behaviour); accessibility (focus order, traps, labels, contrast floor); stack and libraries; performance budget.
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
- Make checks mechanical and fast. A task with no `--check` can only come back `unverified`.
|
|
36
|
-
- Don't ask zcode to commit, push, or deploy unless you mean it. It won't by default.
|
|
29
|
+
**Back end / logic:** data shapes and storage keys; API routes, payloads, status codes and errors; validation rules (exact regexes, ranges); state machines (steps, transitions, what persists, what resets); auth and permissions; idempotency and retries; what is mocked vs real; side effects that are forbidden.
|
|
37
30
|
|
|
38
|
-
|
|
31
|
+
**Boundaries:** what the executor may choose freely (internal names, file structure, helpers) and what it must ask about.
|
|
39
32
|
|
|
40
|
-
|
|
33
|
+
Write decisions as statements, not wishes: "Reveal: opacity 0→1 and translateY 16px→0, 400 ms, cubic-bezier(.2,.7,.2,1), 60 ms stagger, at 20% visibility; reduced motion: opacity only."
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
zcode task --detach --id <id> --cwd <repo> [flags] "<objective>" # prints the id, returns at once
|
|
44
|
-
zcode wait <id> --timeout-ms 540000 --json --quiet # exit 124 = still running: wait again
|
|
45
|
-
```
|
|
35
|
+
## Shaping the work
|
|
46
36
|
|
|
47
|
-
|
|
37
|
+
Pick the cheapest shape that keeps each session finishable without you.
|
|
48
38
|
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- Pick your own `--id` values (`page-1`, `fix-auth`) so you can retry safely. A start with an existing id is refused (exit 2) instead of running the work twice.
|
|
39
|
+
- **One session with units** when the pieces share context (one page's sections, one module's functions). `--unit "hero: …"` per piece; zcode gives each unit its own subagent and integrates.
|
|
40
|
+
- **Several sessions** when pieces are independent and own disjoint paths. Launch them `--detach` in parallel; each may have its own units.
|
|
41
|
+
- **A plan** when there are dependencies (scaffold → parallel features → integration). `zcode plan run plan.json` sequences and parallelises for you; you wait once.
|
|
53
42
|
|
|
54
|
-
|
|
43
|
+
Parallel safety is automatic: each session gets `ZCODE_PORT`, `ZCODE_OUT`, `ZCODE_TMP` and `ZCODE_SESSION_ID`. Tell the first (scaffold) session to make dev-server ports, build output dirs and test artifacts honour them. Target repos should ignore `.zcode/`.
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
- Sessions in the same repo must declare disjoint `--owns`. An overlapping start is refused with exit 7, and the error names the conflicting session. Fix the split, wait, or give the session its own `git worktree` and point `--cwd` at it.
|
|
58
|
-
- After the work, each handback lists changed files. Anything outside the session's ownership appears in `scopeViolations` and makes the result `partial`.
|
|
59
|
-
- You integrate the results. zcode doesn't coordinate sessions with each other.
|
|
45
|
+
## The handoff
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
| field | flag | give it when |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| objective | positional text | always: the outcome, not the steps |
|
|
50
|
+
| decisions | `--decision "…"`, `--decisions @file` | always for anything user-visible or behavioural |
|
|
51
|
+
| checks | `--check "cmd"` | always: build, typecheck, tests, lint, greps — zcode runs them itself |
|
|
52
|
+
| visual | `--visual "criterion"`, `--viewport WxH`, `--serve "cmd"` | any UI work: zcode serves on $ZCODE_PORT, screenshots, judges and repairs |
|
|
53
|
+
| units | `--unit "name: objective [owns a,b]"` | one session should fan out to subagents |
|
|
54
|
+
| owns | `--owns path/glob` | always when other sessions may run in the same repo |
|
|
55
|
+
| touch | `--touch path` | a small edit to a shared file no running session owns |
|
|
56
|
+
| files | `--file path` | files to read first (don't also pass them as `--context @`) |
|
|
57
|
+
| context | `--context "…"` / `--context @file` | background that isn't a decision |
|
|
58
|
+
| acceptance | `--accept "…"` | criteria beyond checks and visuals |
|
|
59
|
+
| everything | `--task contract.json` | keys: objective, scope, constraints, decisions, design, acceptance, checks, visual, viewports, units, files, references, context, owns, touch, limits, model, effort |
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
Model and effort: `--model glm-5.3` for real work (`flash` for trivial edits); `--effort low|high|max` (default `max`). Other values are refused before anything starts.
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|---|---|---|---|
|
|
67
|
-
| `completed` | 0 | zcode's checks passed, executor reports done, scope respected | review on your own schedule; integrate |
|
|
68
|
-
| `unverified` | 9 | executor says done, but there were no checks (or no usable handback) | verify yourself, or resume with a check added to the message |
|
|
69
|
-
| `partial` | 8 | work remains, a criterion is unmet, or files changed outside scope | read `issues`/`unresolved`/`scopeViolations`; resume or re-scope |
|
|
70
|
-
| `blocked` | 10 | needs you: see `questions` | decide, then `zcode resume <id> "<answer>"` |
|
|
71
|
-
| `failed` | 8 / 5 / 124 | `error.kind`: `checks_failed` (still failing after repairs), turn/engine error, or `timeout` | read `checks[].output` / `error`; resume with guidance or re-scope |
|
|
72
|
-
| `cancelled` | 130 | stopped | resume if wanted |
|
|
63
|
+
## Running
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
```sh
|
|
66
|
+
zcode task --detach --id <id> --cwd <repo> [flags] "<objective>" # prints the id, returns at once
|
|
67
|
+
zcode wait <id…> --timeout-ms 540000 --json --quiet # 124 = still running (one line each): wait again
|
|
68
|
+
zcode wait <id…> --any --timeout-ms 540000 --json --quiet # return on the first that finishes
|
|
69
|
+
zcode resume <id> "<answer or follow-up>" --detach # same session, context intact
|
|
70
|
+
zcode result <id> [--full] # brief again, or the full record
|
|
71
|
+
zcode cancel <id>
|
|
72
|
+
```
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
Plans:
|
|
77
75
|
|
|
78
76
|
```sh
|
|
79
|
-
zcode
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
zcode plan check plan.json
|
|
78
|
+
zcode plan run plan.json --detach [--preflight]
|
|
79
|
+
zcode plan wait <name> --timeout-ms 540000 --json # one summary line per session; details only for non-completed
|
|
80
|
+
zcode plan resume <name> ["<message>"] # resume failed/partial, relaunch skipped
|
|
81
|
+
zcode plan resume <name> --session <id> "<answer>" # answer one blocked session
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
`plan.json`: `{"cwd"?, "decisions"?, "context"?, "files"?, "model"?, "effort"?, "sessions": [{"id", "after"?: [ids], ...contract keys}]}`. Top-level decisions/context/files go to every session; `@` entries are files relative to the plan. The plan name is the file basename (or `--id`). Sessions that can run at the same time must own disjoint paths; `plan check` enforces it. When an upstream session ends non-completed, its dependants are `skipped` until you `plan resume`.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
Your Bash tool times out at 10 minutes; always wait in bounded slices. Run the wait with `run_in_background` if you have other planning to do.
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
## Reading the brief
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
90
|
+
`--json` prints one compact line: `status`, `summary`, `files`, failing check tails, `questions`, `issues`, `unresolved`, `scopeViolations`, `touched`, `decisions`, `visual`, `units`, and `result` (path to the full record).
|
|
91
|
+
|
|
92
|
+
| status | exit | do |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `completed` | 0 | nothing. Skim `decisions` (the executor's own in-scope choices); veto with `resume` only if one is wrong |
|
|
95
|
+
| `blocked` | 10 | answer `questions` (they come with options and a recommendation) via `zcode resume <id> "<answer>"`, and add the answer to your decisions file |
|
|
96
|
+
| `partial` | 8 | read `issues` / `unresolved` / unmet `visual` / `scopeViolations`; resume with one-line guidance or re-scope |
|
|
97
|
+
| `failed` | 8/5/124 | read the failing check `tail` or `error`; resume with guidance |
|
|
98
|
+
| `unverified` | 9 | you forgot checks: resume with a check added |
|
|
99
|
+
| `cancelled` | 130 | resume if wanted |
|
|
100
|
+
|
|
101
|
+
## Hard rules after dispatch
|
|
102
|
+
|
|
103
|
+
- Don't open files a `completed` session changed. Don't re-run checks it ran. Don't look at its screenshots.
|
|
104
|
+
- Don't poll `status`; use `wait`.
|
|
105
|
+
- Don't fix code yourself; `resume` the session with one line.
|
|
106
|
+
- Need to know something about the result? `zcode result <id> --full` first; read code only as a last resort.
|
|
107
|
+
- Don't ask zcode to commit, push, publish or deploy unless the user asked; when they did, make it its own final session gated on everything else being `completed`.
|
|
108
|
+
|
|
109
|
+
## Example: a marketing one-pager with an application form
|
|
110
|
+
|
|
111
|
+
You read the empty repo, decide the design system, copy, motion, form logic and demo constraints, and write `docs/decisions.md`. Then `plan.json`:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"decisions": ["@docs/decisions.md"],
|
|
116
|
+
"model": "glm-5.3", "effort": "high",
|
|
117
|
+
"sessions": [
|
|
118
|
+
{"id": "scaffold", "owns": ["package.json", "package-lock.json", "index.html", "*.config.*", "tsconfig*.json", "src/app/**", "src/styles/**", "src/components/**", "e2e/smoke.spec.ts", ".gitignore"],
|
|
119
|
+
"objective": "Vite + React + TS scaffold, tokens, shared shell, stubs for src/landing and src/apply; dev/preview/e2e honour ZCODE_PORT and ZCODE_OUT",
|
|
120
|
+
"checks": ["npm run build", "npm test", "npm run e2e"]},
|
|
121
|
+
{"id": "landing", "after": ["scaffold"], "owns": ["src/landing/**", "e2e/landing.spec.ts"],
|
|
122
|
+
"objective": "All landing sections per the decisions",
|
|
123
|
+
"units": ["hero: …", "highlights: …", "products: …", "how: …", "charges: …", "faqs: …", "about-cta: …"],
|
|
124
|
+
"checks": ["npm run build", "npx playwright test e2e/landing.spec.ts"],
|
|
125
|
+
"visual": ["hero headline fits on two lines at 390px", "product cards align on one row at 1440px", "no text overlaps any circle motif"]},
|
|
126
|
+
{"id": "apply", "after": ["scaffold"], "owns": ["src/apply/**", "e2e/apply.spec.ts"],
|
|
127
|
+
"objective": "Application dialog: demo OTP, four steps, local draft, thank-you",
|
|
128
|
+
"checks": ["npm run build", "npm test -- apply", "npx playwright test e2e/apply.spec.ts"],
|
|
129
|
+
"visual": ["OTP code is visible on screen after entering a number", "step indicator shows the current step at 390px"]},
|
|
130
|
+
{"id": "integration", "after": ["landing", "apply"], "owns": ["src/App.tsx", "e2e/**"],
|
|
131
|
+
"objective": "Wire everything, full-page pass, fix integration issues",
|
|
132
|
+
"checks": ["npm run build", "npm test", "npm run e2e"],
|
|
133
|
+
"visual": ["full page at 390 and 1440 matches the decisions; motion respects reduced motion"]}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
106
136
|
```
|
|
107
137
|
|
|
108
|
-
|
|
138
|
+
`zcode plan check plan.json` → `zcode plan run plan.json --preflight` (read-only `<id>.pre` sessions; prints only the questions) → answer them once in the decisions file → `zcode plan run plan.json --detach` → `zcode plan wait plan --timeout-ms 540000 --json`. Your total involvement: the decisions, one answer pass, one summary.
|
|
109
139
|
|
|
110
140
|
## Exit codes
|
|
111
141
|
|
|
112
|
-
0 completed · 2 usage (bad flag, contract, or duplicate id) · 3 no key · 4 no engine · 5 failed · 6 `agent` had no subagent · 7 workspace conflict / session busy · 8 partial or checks failing · 9 unverified · 10 blocked · 124 timeout (for `wait`: still running) · 130 cancelled.
|
|
142
|
+
0 completed · 2 usage (bad flag, contract, plan, effort, or duplicate id) · 3 no key · 4 no engine · 5 failed · 6 `agent` had no subagent · 7 workspace conflict / session busy · 8 partial or checks failing · 9 unverified · 10 blocked · 124 timeout (for `wait`: still running) · 130 cancelled.
|