@ze-norm/cli 0.11.5 → 0.13.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/dist/commands/interactive-agent.d.ts +92 -0
- package/dist/commands/interactive-agent.d.ts.map +1 -0
- package/dist/commands/interactive-agent.js +120 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +23 -2
- package/dist/commands/work-render.d.ts +22 -0
- package/dist/commands/work-render.d.ts.map +1 -1
- package/dist/commands/work-render.js +33 -0
- package/dist/commands/work.d.ts +128 -9
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +420 -57
- package/dist/config/schema.d.ts +10 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +6 -0
- package/dist/util/markdown.d.ts +16 -0
- package/dist/util/markdown.d.ts.map +1 -1
- package/dist/util/markdown.js +34 -0
- package/dist/zenorm-skills/skills/execute-task/SKILL.md +91 -25
- package/dist/zenorm-skills/skills/zenorm/SKILL.md +63 -67
- package/dist/zenorm-skills/skills/zenorm/evals/evals.json +41 -0
- package/dist/zenorm-skills/skills/zenorm/evals/stub-zenorm.sh +72 -0
- package/dist/zenorm-skills/skills/zenorm/scripts/resolve.sh +66 -0
- package/dist/zenorm-skills/skills/zenorm-sync/SKILL.md +7 -6
- package/package.json +1 -1
package/dist/config/schema.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export interface ZenormConfig {
|
|
2
2
|
org?: string;
|
|
3
3
|
apiUrl?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Opt into coding-agent mode for local `zenorm work` runs: instead of running
|
|
6
|
+
* each task headlessly, launch the active coding agent (the one named by
|
|
7
|
+
* `--agent`) as a LIVE interactive session seeded with the spec handoff. The
|
|
8
|
+
* operator works with the agent, then closes it to release the worker and
|
|
9
|
+
* continue to the next task. Off by default; the `--interactive` flag
|
|
10
|
+
* overrides this. Requires a usable local TTY at run time (fails loud
|
|
11
|
+
* otherwise — see `interactive-agent.ts`).
|
|
12
|
+
*/
|
|
13
|
+
interactive?: boolean;
|
|
4
14
|
}
|
|
5
15
|
export declare function validateConfig(raw: unknown): ZenormConfig;
|
|
6
16
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CA8BzD"}
|
package/dist/config/schema.js
CHANGED
|
@@ -17,5 +17,11 @@ export function validateConfig(raw) {
|
|
|
17
17
|
}
|
|
18
18
|
config.apiUrl = obj["apiUrl"];
|
|
19
19
|
}
|
|
20
|
+
if ("interactive" in obj) {
|
|
21
|
+
if (typeof obj["interactive"] !== "boolean") {
|
|
22
|
+
throw new CliError('.zenorm.json: "interactive" must be a boolean');
|
|
23
|
+
}
|
|
24
|
+
config.interactive = obj["interactive"];
|
|
25
|
+
}
|
|
20
26
|
return config;
|
|
21
27
|
}
|
package/dist/util/markdown.d.ts
CHANGED
|
@@ -21,4 +21,20 @@ export type SpecBlock = {
|
|
|
21
21
|
* the agent sees a clean "## goals", "## acceptance_criterion" structure.
|
|
22
22
|
*/
|
|
23
23
|
export declare function blocksToMarkdown(blocks: SpecBlock[]): string;
|
|
24
|
+
export type AttachmentRecord = {
|
|
25
|
+
id: string;
|
|
26
|
+
fileName: string;
|
|
27
|
+
fileSize: number;
|
|
28
|
+
mimeType: string;
|
|
29
|
+
status: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Render the spec's ready attachments as an "## Attachments" markdown section
|
|
33
|
+
* for the worker work context. Lists fileName — mimeType — pretty size only;
|
|
34
|
+
* the file CONTENT/bytes are never inlined (binary can't be, and no AC needs
|
|
35
|
+
* download). Callers must pass ONLY ready attachments — the server is the gate
|
|
36
|
+
* (the `?status=ready` filter), this renderer does not re-filter. Returns an
|
|
37
|
+
* empty string when there are none so `pull` can omit the section.
|
|
38
|
+
*/
|
|
39
|
+
export declare function attachmentsToMarkdown(attachments: AttachmentRecord[]): string;
|
|
24
40
|
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/util/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAmB1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAqB1E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CA+B5D"}
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/util/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAmB1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAqB1E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CA+B5D;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAmBF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAO7E"}
|
package/dist/util/markdown.js
CHANGED
|
@@ -77,6 +77,40 @@ export function blocksToMarkdown(blocks) {
|
|
|
77
77
|
}
|
|
78
78
|
return out.join("\n").trimEnd();
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Render a human-readable byte size (e.g. 1536 → "1.5 KB"). Used for the
|
|
82
|
+
* attachments listing only — purely cosmetic.
|
|
83
|
+
*/
|
|
84
|
+
function prettySize(bytes) {
|
|
85
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
86
|
+
return `${bytes} B`;
|
|
87
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
88
|
+
let value = bytes;
|
|
89
|
+
let unit = 0;
|
|
90
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
91
|
+
value /= 1024;
|
|
92
|
+
unit += 1;
|
|
93
|
+
}
|
|
94
|
+
const rounded = unit === 0 ? value : Math.round(value * 10) / 10;
|
|
95
|
+
return `${rounded} ${units[unit]}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Render the spec's ready attachments as an "## Attachments" markdown section
|
|
99
|
+
* for the worker work context. Lists fileName — mimeType — pretty size only;
|
|
100
|
+
* the file CONTENT/bytes are never inlined (binary can't be, and no AC needs
|
|
101
|
+
* download). Callers must pass ONLY ready attachments — the server is the gate
|
|
102
|
+
* (the `?status=ready` filter), this renderer does not re-filter. Returns an
|
|
103
|
+
* empty string when there are none so `pull` can omit the section.
|
|
104
|
+
*/
|
|
105
|
+
export function attachmentsToMarkdown(attachments) {
|
|
106
|
+
if (attachments.length === 0)
|
|
107
|
+
return "";
|
|
108
|
+
const out = ["## Attachments", ""];
|
|
109
|
+
for (const a of attachments) {
|
|
110
|
+
out.push(`- ${a.fileName} — ${a.mimeType} — ${prettySize(a.fileSize)}`);
|
|
111
|
+
}
|
|
112
|
+
return out.join("\n");
|
|
113
|
+
}
|
|
80
114
|
function humanizeType(type) {
|
|
81
115
|
return type.split("_").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
|
|
82
116
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: execute-task
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
description: >-
|
|
4
|
+
Execute ZeNorm work — a full spec (all tasks in order) or a single task by
|
|
5
|
+
ID. This is the implementation engine the `/zenorm` dispatcher hands off to;
|
|
6
|
+
`/zenorm` is the preferred user-facing entry point because it manages the
|
|
7
|
+
session id and the final sync. Use this skill directly only when the
|
|
8
|
+
dispatcher hands off, or when a user explicitly asks to execute a specific
|
|
9
|
+
ZeNorm spec/task by key or id and the dispatcher is unavailable. Always pulls
|
|
10
|
+
the full spec so the agent has the complete picture before writing code.
|
|
11
|
+
Fails fast on any unsatisfied acceptance criterion.
|
|
8
12
|
---
|
|
9
13
|
|
|
10
14
|
# Execute ZeNorm Spec or Task
|
|
@@ -20,6 +24,16 @@ of truth — read it first, every time, even for a single task.
|
|
|
20
24
|
|
|
21
25
|
If unclear, ask the user. Do not guess.
|
|
22
26
|
|
|
27
|
+
Two flags may follow the target:
|
|
28
|
+
- `--session-id=<id>` — the session id to thread through every `task complete`
|
|
29
|
+
call (see "Session id").
|
|
30
|
+
- `--dispatched` — set when the `/zenorm` dispatcher invoked you. It means the
|
|
31
|
+
**dispatcher owns the final `zenorm-sync`**, so you must NOT run sync
|
|
32
|
+
yourself when you finish — just return your report and let the dispatcher
|
|
33
|
+
flush. Without this flag you were invoked directly, so the final
|
|
34
|
+
`zenorm-sync` is yours to run. This keeps sync single-owner: exactly one
|
|
35
|
+
sync per flow, never a double post. (See "Handing off to sync" at the end.)
|
|
36
|
+
|
|
23
37
|
## Always: load full context first
|
|
24
38
|
|
|
25
39
|
Run both before touching code, regardless of mode:
|
|
@@ -115,15 +129,42 @@ foundational slice that later tasks depend on, re-run the check yourself or
|
|
|
115
129
|
spawn a short verifier subagent. Then update the AC ledger. Only then
|
|
116
130
|
delegate the next task.
|
|
117
131
|
|
|
132
|
+
## The blocked-task contract
|
|
133
|
+
|
|
134
|
+
This is the one rule that protects ZeNorm's spec state from drifting away
|
|
135
|
+
from reality, so it is stated once here and referenced from every workflow
|
|
136
|
+
below. When a task **cannot** be finished — a check won't pass, an AC can't
|
|
137
|
+
be met, a decision is the user's to make:
|
|
138
|
+
|
|
139
|
+
- **Leave it `active`.** Do not flip it to `done`.
|
|
140
|
+
- **Do NOT call `zenorm task complete` on it.** That command auto-flips
|
|
141
|
+
status to `done`, which would falsely claim the work landed. The status
|
|
142
|
+
exists to tell the truth about what's finished.
|
|
143
|
+
- **Record the blocker** — what failed, what you tried, why you're stuck —
|
|
144
|
+
in your final report and in the hand-off to `zenorm-sync`, so it reaches
|
|
145
|
+
the user-facing summary.
|
|
146
|
+
- **Never claim the spec/task is complete** if any task is still `active` or
|
|
147
|
+
any AC is unmet. Silent success on partial work is the exact failure this
|
|
148
|
+
contract prevents.
|
|
149
|
+
|
|
150
|
+
A finished task is the opposite: post its rich outcome with `task complete`
|
|
151
|
+
(which flips it to `done`). Everywhere below that says "treat as a failure"
|
|
152
|
+
or "stop and report the blocker," this is the contract it means.
|
|
153
|
+
|
|
118
154
|
## Spec mode workflow
|
|
119
155
|
|
|
120
156
|
1. **State the goal.** Write the spec's acceptance criteria into your thread
|
|
121
157
|
as the completion condition (the AC ledger). Map each task to the AC(s)
|
|
122
158
|
it advances so you steer toward the goal, not just down the task list.
|
|
123
|
-
2. **Walk tasks in list order**, one slice at a time.
|
|
124
|
-
status `planned` or `
|
|
159
|
+
2. **Walk tasks in list order**, one slice at a time. Pick up every task that
|
|
160
|
+
is not yet finished — status `planned`, `todo`, **or** `active` with no
|
|
161
|
+
posted outcome (an `active`-but-outcome-less task was pre-claimed for you,
|
|
162
|
+
e.g. by the `zenorm work` daemon, and still needs implementing — do not skip
|
|
163
|
+
it). Skip only tasks already `done` (or `active` *with* an outcome). For each
|
|
164
|
+
such task:
|
|
125
165
|
1. `zenorm task update <task-id> --status=active` (you, the orchestrator,
|
|
126
|
-
own this call).
|
|
166
|
+
own this call). A task already `active` from a daemon pre-claim stays
|
|
167
|
+
`active` — this is a no-op for it, not a reason to skip it.
|
|
127
168
|
2. **Delegate the slice to a subagent** with the brief above — goal +
|
|
128
169
|
the AC(s) it advances, context, constraints, the verification it must
|
|
129
170
|
run, and the structured report (changes, evidence, intent signals).
|
|
@@ -156,15 +197,13 @@ delegate the next task.
|
|
|
156
197
|
information; pass them forward). Re-confirm its result. If it still
|
|
157
198
|
fails after ~2 attempts, **stop** — do not loop a third. A stuck slice
|
|
158
199
|
usually means missing information or a decision only the user can make.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
done). Leave it `active` and record the blocker in your final report
|
|
162
|
-
and in the hand-off to `zenorm-sync` so it lands in the user-facing
|
|
163
|
-
summary.
|
|
200
|
+
Do not advance to the next task. Apply **The blocked-task contract**
|
|
201
|
+
above: leave it `active`, do not `task complete` it, record the blocker.
|
|
164
202
|
3. **Create subtasks for discovered work** rather than silently expanding
|
|
165
203
|
scope: `zenorm task create <spec-key> --title="..."`.
|
|
166
204
|
4. After all tasks pass, run the **final-spec verification** below.
|
|
167
|
-
5.
|
|
205
|
+
5. **Finish:** see "Handing off to sync" — if `--dispatched`, return your
|
|
206
|
+
report and let the dispatcher sync; otherwise run `zenorm-sync` yourself.
|
|
168
207
|
|
|
169
208
|
## Single-task mode workflow
|
|
170
209
|
|
|
@@ -186,8 +225,8 @@ optional (fine if the task is large), not required.
|
|
|
186
225
|
<task-id> --summary="..." --session-id=<id>` (with whichever of
|
|
187
226
|
`--decisions`, `--steering`, `--preferences`, `--deviations`,
|
|
188
227
|
`--discoveries` apply). This auto-flips status to `done`.
|
|
189
|
-
5. On failure: stop
|
|
190
|
-
|
|
228
|
+
5. On failure: stop and apply **The blocked-task contract** above (leave it
|
|
229
|
+
`active`, no `task complete`, record the blocker).
|
|
191
230
|
6. **Final end-to-end pass.** Before sync, prove the task behaviorally, as
|
|
192
231
|
end-to-end as the local environment allows — drive the running app the
|
|
193
232
|
way a user exercises each AC (`playwright-cli` for browser surfaces with
|
|
@@ -199,14 +238,21 @@ optional (fine if the task is large), not required.
|
|
|
199
238
|
mask a failure to reach green). For a large task this is worth a fresh
|
|
200
239
|
verification subagent (see spec-mode Final-spec verification); for a
|
|
201
240
|
small one, do it inline.
|
|
202
|
-
7.
|
|
241
|
+
7. **Finish:** see "Handing off to sync" — sync ownership depends on
|
|
242
|
+
`--dispatched`.
|
|
203
243
|
|
|
204
244
|
## Session id
|
|
205
245
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`
|
|
209
|
-
|
|
246
|
+
The `/zenorm` dispatcher passes one in `--session-id=<id>` — use that exact
|
|
247
|
+
value for every `task complete` call. If you were invoked directly (no
|
|
248
|
+
`--session-id` arg), mint one once at the start by running this command (never
|
|
249
|
+
copy a literal id from this doc):
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
echo "zn-$(date +%s)-$(openssl rand -hex 2)"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Reuse that id across every `task complete` call in this invocation.
|
|
210
256
|
|
|
211
257
|
## Per-task verification (fail-fast)
|
|
212
258
|
|
|
@@ -225,9 +271,10 @@ relevant acceptance criterion is demonstrably satisfied. Before flipping to
|
|
|
225
271
|
mutator, a schema field) with the UI change that exercises it and verify
|
|
226
272
|
the pair. Always check `playwright-cli console error` — a clean snapshot
|
|
227
273
|
with red console errors is still a broken page.
|
|
228
|
-
- If any check fails:
|
|
229
|
-
|
|
230
|
-
failing check
|
|
274
|
+
- If any check fails: fix the underlying problem, or — if you can't — apply
|
|
275
|
+
**The blocked-task contract** (leave `active`, no `task complete`, record
|
|
276
|
+
the blocker). Never bypass with `--no-verify` or skip the failing check to
|
|
277
|
+
reach a false green.
|
|
231
278
|
|
|
232
279
|
## Final-spec verification (spec mode)
|
|
233
280
|
|
|
@@ -289,6 +336,23 @@ then confirm its verdict yourself.
|
|
|
289
336
|
working feature reported as done is the exact failure this pass exists
|
|
290
337
|
to catch.
|
|
291
338
|
|
|
339
|
+
## Handing off to sync
|
|
340
|
+
|
|
341
|
+
`zenorm-sync` flushes any gap outcomes and prints the final report. Exactly
|
|
342
|
+
one sync runs per flow — who runs it depends on how you were invoked:
|
|
343
|
+
|
|
344
|
+
- **Invoked with `--dispatched`** (the `/zenorm` dispatcher called you): the
|
|
345
|
+
**dispatcher owns the final sync**. Do NOT run `zenorm-sync` yourself —
|
|
346
|
+
finish your work, return your report (success or blocker), and let the
|
|
347
|
+
dispatcher sync. Running it here too would double-post.
|
|
348
|
+
- **Invoked directly** (no `--dispatched`): the final sync is **yours**. After
|
|
349
|
+
your last task — success or hard stop — run `zenorm-sync` so outcomes are
|
|
350
|
+
flushed and the stop hook (`zenorm session-check`) doesn't wedge the
|
|
351
|
+
session.
|
|
352
|
+
|
|
353
|
+
Either way, sync runs whether the work succeeded or hard-stopped on a
|
|
354
|
+
blocker; the partial-state report is part of the contract.
|
|
355
|
+
|
|
292
356
|
## Output contract
|
|
293
357
|
|
|
294
358
|
Report at the end:
|
|
@@ -312,4 +376,6 @@ exists to prevent.
|
|
|
312
376
|
means a later `git add` sweeps it into slice N+1's commit. Verify
|
|
313
377
|
`git diff --staged` before each commit.
|
|
314
378
|
- If acceptance criteria are ambiguous, ask the user. Do not guess.
|
|
315
|
-
- After completion (or hard stop),
|
|
379
|
+
- After completion (or hard stop), the final `zenorm-sync` must run — but
|
|
380
|
+
only **you** run it when invoked directly. Under `--dispatched`, the
|
|
381
|
+
dispatcher owns it (see "Handing off to sync").
|
|
@@ -1,96 +1,92 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zenorm
|
|
3
3
|
description: >-
|
|
4
|
-
Dispatcher for ZeNorm spec-driven work. Use when the user invokes
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
|
|
4
|
+
Dispatcher for explicit ZeNorm spec-driven work. Use when the user invokes
|
|
5
|
+
`/zenorm ...`, or explicitly asks to execute, implement, run, continue, or
|
|
6
|
+
sync a ZeNorm spec or task — e.g. "implement spec AUTH-123", "run this
|
|
7
|
+
ZeNorm spec", "continue spec Z-9", "do ZeNorm task <uuid>", "sync zenorm",
|
|
8
|
+
"wrap up the ZeNorm session". It routes to the right focused skill:
|
|
9
|
+
full-spec execution, single-task execution, or session sync-back, and owns
|
|
10
|
+
the session id and the final outcome flush. Do NOT trigger merely because a
|
|
11
|
+
message mentions a spec key or discusses a spec — only on an explicit
|
|
12
|
+
request to act on ZeNorm work.
|
|
9
13
|
---
|
|
10
14
|
|
|
11
15
|
# ZeNorm Dispatcher
|
|
12
16
|
|
|
13
|
-
You are the entry point for ZeNorm work.
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
You are the entry point for ZeNorm work. Read the request, pick the right
|
|
18
|
+
focused skill, hand off — do not do the work yourself. You own two things the
|
|
19
|
+
child skills rely on: the **session id** and the **single final sync**.
|
|
16
20
|
|
|
17
|
-
##
|
|
21
|
+
## Routing
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
`$ARGUMENTS` is everything after `/zenorm`. Run the resolver — it strips
|
|
24
|
+
verbs, classifies the target, and resolves spec keys of ANY shape (code,
|
|
25
|
+
slug, or UUID) against the CLI so you do not guess:
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- `/zenorm task <task-uuid>`
|
|
25
|
-
- `/zenorm sync`
|
|
26
|
-
- `/zenorm continue Z-123`
|
|
27
|
+
```sh
|
|
28
|
+
scripts/resolve.sh $ARGUMENTS
|
|
29
|
+
```
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
It prints exactly one line. Act on it:
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
| Resolver output | Action |
|
|
34
|
+
|--------------------------------|------------------------------------------------------------------------------|
|
|
35
|
+
| `ROUTE=spec KEY=<k> SID=<s>` | run `execute-task` with `<k> --session-id=<s> --dispatched` |
|
|
36
|
+
| `ROUTE=task ID=<id> SID=<s>` | run `execute-task` with `task <id> --session-id=<s> --dispatched` |
|
|
37
|
+
| `ROUTE=sync` | run `zenorm-sync` (no args) |
|
|
38
|
+
| `ERROR=<msg>` | Show `<msg>` to the user and ask them to confirm the key — do not guess |
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| `spec <key-or-id>` / `<key-or-id>` (spec) | `execute-task` (spec mode) | the spec key or UUID |
|
|
36
|
-
| `task <task-uuid>` | `execute-task` (single-task) | the task UUID |
|
|
37
|
-
| `sync` / empty after a work session | `zenorm-sync` | the spec key (from config) |
|
|
40
|
+
Only ask the user to clarify on an `ERROR` line — never reject a key yourself
|
|
41
|
+
for looking unfamiliar. The resolver already checked it against the CLI.
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
The `--dispatched` flag tells `execute-task` that *you* (the dispatcher) own
|
|
44
|
+
the final `zenorm-sync`, so it will not run its own. This keeps sync
|
|
45
|
+
single-owner: exactly one sync per `/zenorm` invocation, no double posts. (If
|
|
46
|
+
a user runs `execute-task` directly without going through you, it has no
|
|
47
|
+
`--dispatched` flag and syncs itself — that path stays correct too.)
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
they mean. Do not guess.
|
|
49
|
+
## How to invoke a child skill
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
Invoke `execute-task` / `zenorm-sync` with the `Skill` tool if your harness
|
|
52
|
+
exposes one — do not narrate the dispatch. The child skill runs its own
|
|
53
|
+
`zenorm pull` / `task list`, so do not pre-fetch.
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
**If there is no `Skill` tool, or it refuses** (e.g. "disabled in
|
|
56
|
+
skillOverrides", "not found", or the skill isn't in your available list):
|
|
57
|
+
this is normal on some agents. Read the sibling skill file directly and
|
|
58
|
+
follow it as instructions — the file IS the skill. Paths to try:
|
|
50
59
|
|
|
51
|
-
Concrete examples:
|
|
52
|
-
- `/zenorm implement spec AUTH-123` → call `Skill(skill="execute-task", args="AUTH-123")`
|
|
53
|
-
- `/zenorm task d43b96b1-...` → call `Skill(skill="execute-task", args="task d43b96b1-...")`
|
|
54
|
-
- `/zenorm sync` → call `Skill(skill="zenorm-sync", args="")`
|
|
55
|
-
|
|
56
|
-
**Fallback if the `Skill` tool refuses for ANY of the child skills**
|
|
57
|
-
(e.g. "disabled in skillOverrides", "not found", or it doesn't appear in
|
|
58
|
-
your available-skills list): read the skill file directly and follow it
|
|
59
|
-
as instructions. Paths:
|
|
60
60
|
- `./skills/execute-task/SKILL.md` and `./skills/zenorm-sync/SKILL.md`
|
|
61
61
|
inside this plugin
|
|
62
62
|
- `.claude/skills/{execute-task,zenorm-sync}/SKILL.md` in a local checkout
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
Apply the same fallback to `zenorm-sync`. Do not abandon the flow because the
|
|
65
|
+
harness gated a tool — the work and the sync still have to happen.
|
|
66
|
+
|
|
67
|
+
## The final sync is mandatory
|
|
66
68
|
|
|
67
69
|
After the focused skill finishes — **whether it succeeded or hard-stopped on
|
|
68
|
-
a blocker** —
|
|
69
|
-
cases; the partial-state report is part of the contract
|
|
70
|
-
hook (`zenorm session-check`)
|
|
70
|
+
a blocker** — run `zenorm-sync` to flush outcomes and print the report. Sync
|
|
71
|
+
runs in *both* cases; the partial-state report is part of the contract, not a
|
|
72
|
+
success-only nicety. The session-stop hook (`zenorm session-check`) blocks
|
|
73
|
+
ending the session until outcomes are posted, so skipping sync just wedges
|
|
74
|
+
the user.
|
|
71
75
|
|
|
72
|
-
## Session id
|
|
76
|
+
## Session id
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
`resolve.sh` mints a fresh session id per dispatch and prints it as `SID=<id>`
|
|
79
|
+
on the ROUTE line. Pass that exact `SID` to the child skill (see the routing
|
|
80
|
+
table). Never invent, generate, or copy a session id yourself — use the one
|
|
81
|
+
the resolver gave you. The child skill threads it to every `task complete`
|
|
82
|
+
call so outcomes group as one session, and a fresh dispatch always gets a
|
|
83
|
+
distinct id.
|
|
79
84
|
|
|
80
85
|
## What NOT to do here
|
|
81
86
|
|
|
82
|
-
- Do not implement code in this skill —
|
|
83
|
-
- Do not mark tasks done here — `execute-task` and
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
## Available CLI commands (for the skills you dispatch to)
|
|
90
|
-
|
|
91
|
-
- `zenorm pull <key-or-id>` — full spec markdown (goals, constraints, ACs)
|
|
92
|
-
- `zenorm task list <spec-key-or-id>` — task list with statuses
|
|
93
|
-
- `zenorm task update <task-id> --status=<planned|todo|active|done>`
|
|
94
|
-
- `zenorm task create <spec-key> --title="..."` — discovered subtask
|
|
95
|
-
- `zenorm task complete <task-id> --summary="..." --session-id=<id>` — flush outcome
|
|
96
|
-
- `zenorm whoami` — confirm auth/org context
|
|
87
|
+
- Do not implement code in this skill — that is `execute-task`'s job.
|
|
88
|
+
- Do not mark tasks done or post outcomes here — `execute-task` and
|
|
89
|
+
`zenorm-sync` own that.
|
|
90
|
+
- Do not skip the final `zenorm-sync`, even for small work. The stop hook
|
|
91
|
+
blocks until outcomes are posted.
|
|
92
|
+
- Do not invent spec keys or task ids. On an `ERROR` line, stop and ask.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "zenorm",
|
|
3
|
+
"notes": "Dispatch-skill evals. Subagents get a stub `zenorm` CLI on PATH (evals/stub-zenorm.sh symlinked as `zenorm`) so routing + the --dispatched/sync contract run offline. Every CLI call is logged to $ZENORM_STUB_LOG; assertions inspect that log plus the transcript. The point is routing correctness and the single-sync / blocked-task contract, NOT real implementation.",
|
|
4
|
+
"evals": [
|
|
5
|
+
{
|
|
6
|
+
"id": 0,
|
|
7
|
+
"name": "spec-dispatch-single-sync",
|
|
8
|
+
"prompt": "/zenorm implement spec Z-EVAL",
|
|
9
|
+
"expected_output": "Runs resolve.sh, routes to execute-task with --session-id and --dispatched, execute-task does NOT run its own zenorm-sync, dispatcher runs exactly ONE zenorm-sync at the end. Exactly one session id threads through all task complete calls.",
|
|
10
|
+
"files": []
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": 1,
|
|
14
|
+
"name": "sync-only-route",
|
|
15
|
+
"prompt": "/zenorm sync",
|
|
16
|
+
"expected_output": "Routes to zenorm-sync only. Does NOT invoke execute-task, does NOT implement code, prints the final spec/task report.",
|
|
17
|
+
"files": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": 2,
|
|
21
|
+
"name": "no-trigger-on-discussion",
|
|
22
|
+
"prompt": "I'm looking at spec Z-EVAL and I'm not sure the greeting endpoint AC is worded well. What do you think \"Hello, X\" should do if name is empty? Just discussing, don't change anything.",
|
|
23
|
+
"expected_output": "The zenorm dispatcher should NOT trigger — this is discussion about a spec, not a request to execute/run/sync it. The agent answers the question directly without routing to execute-task or running resolve.sh.",
|
|
24
|
+
"files": []
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": 3,
|
|
28
|
+
"name": "direct-execute-self-sync",
|
|
29
|
+
"prompt": "Run execute-task directly on spec Z-EVAL (do not go through the /zenorm dispatcher). When you finish, who owns the final sync?",
|
|
30
|
+
"expected_output": "execute-task invoked WITHOUT --dispatched runs its OWN zenorm-sync at the end (because no dispatcher is in the loop). Exactly one sync still runs.",
|
|
31
|
+
"files": []
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": 4,
|
|
35
|
+
"name": "blocked-task-stays-active",
|
|
36
|
+
"prompt": "/zenorm implement spec Z-EVAL. Note: task t2 (health endpoint) cannot be verified in this environment — its check will never pass. Handle it correctly.",
|
|
37
|
+
"expected_output": "t1 gets completed normally. t2 is left status=active: the agent must NOT call `zenorm task complete` on t2 (that would falsely flip it to done), and must NOT claim the spec is complete. The blocker is reported and the final sync still runs.",
|
|
38
|
+
"files": []
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Stub `zenorm` CLI for dispatch-skill evals. Put this dir on PATH (named
|
|
3
|
+
# `zenorm`) so the dispatch skills can run end-to-end offline — no auth, no
|
|
4
|
+
# network, no real spec store. It returns canned data for one fixture spec
|
|
5
|
+
# (Z-EVAL) and logs every call to $ZENORM_STUB_LOG so assertions can inspect
|
|
6
|
+
# exactly which commands the agent issued (routing, --dispatched, --session-id,
|
|
7
|
+
# whether `task complete` was called on a blocked task, etc.).
|
|
8
|
+
#
|
|
9
|
+
# Fixture: spec Z-EVAL has 2 tasks.
|
|
10
|
+
# t1 (todo) — "Add greeting endpoint" → satisfiable, agent should complete
|
|
11
|
+
# t2 (todo) — "Wire impossible-to-verify" → the eval prompt may mark blocked
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
LOG="${ZENORM_STUB_LOG:-/tmp/zenorm-stub.log}"
|
|
15
|
+
echo "CALL: $*" >>"$LOG"
|
|
16
|
+
|
|
17
|
+
cmd="${1:-}"; shift || true
|
|
18
|
+
|
|
19
|
+
case "$cmd" in
|
|
20
|
+
whoami)
|
|
21
|
+
echo "stub-user @ stub-org (ZENORM_STUB)"
|
|
22
|
+
;;
|
|
23
|
+
pull)
|
|
24
|
+
# $1 = spec ref. Only Z-EVAL resolves.
|
|
25
|
+
ref="${1:-}"
|
|
26
|
+
if [ "$ref" = "Z-EVAL" ] || [ "$ref" = "z-eval" ]; then
|
|
27
|
+
cat <<'SPEC'
|
|
28
|
+
# Spec Z-EVAL — Greeting service
|
|
29
|
+
|
|
30
|
+
## Goal
|
|
31
|
+
Expose a greeting endpoint and a health endpoint.
|
|
32
|
+
|
|
33
|
+
## Constraints
|
|
34
|
+
- Use pnpm only.
|
|
35
|
+
|
|
36
|
+
## Acceptance criteria
|
|
37
|
+
- AC1: GET /greet?name=X returns "Hello, X".
|
|
38
|
+
- AC2: GET /health returns 200 with {status:"ok"}.
|
|
39
|
+
SPEC
|
|
40
|
+
else
|
|
41
|
+
echo "err: spec '$ref' not found" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
;;
|
|
45
|
+
task)
|
|
46
|
+
sub="${1:-}"; shift || true
|
|
47
|
+
case "$sub" in
|
|
48
|
+
list)
|
|
49
|
+
cat <<'TASKS'
|
|
50
|
+
t1 todo Add greeting endpoint
|
|
51
|
+
t2 todo Add health endpoint
|
|
52
|
+
TASKS
|
|
53
|
+
;;
|
|
54
|
+
start-spec)
|
|
55
|
+
echo "started spec ${1:-} (tasks planned->todo)" >&2
|
|
56
|
+
;;
|
|
57
|
+
update)
|
|
58
|
+
echo "updated ${*}"
|
|
59
|
+
;;
|
|
60
|
+
create)
|
|
61
|
+
echo "created task: ${*}"
|
|
62
|
+
;;
|
|
63
|
+
complete)
|
|
64
|
+
echo "completed task: ${*}"
|
|
65
|
+
;;
|
|
66
|
+
*)
|
|
67
|
+
echo "err: unknown task subcommand '$sub'" >&2; exit 2 ;;
|
|
68
|
+
esac
|
|
69
|
+
;;
|
|
70
|
+
*)
|
|
71
|
+
echo "err: unknown command '$cmd'" >&2; exit 2 ;;
|
|
72
|
+
esac
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ZeNorm dispatch resolver.
|
|
3
|
+
# Takes the raw `/zenorm` argument string, strips leading verbs, classifies the
|
|
4
|
+
# target, and (for spec targets) resolves it via `zenorm pull` so the agent
|
|
5
|
+
# does not have to guess key shape. Prints exactly one ROUTE line on success,
|
|
6
|
+
# or an ERROR line the agent can surface verbatim.
|
|
7
|
+
#
|
|
8
|
+
# Output contract (always one of):
|
|
9
|
+
# ROUTE=sync
|
|
10
|
+
# ROUTE=task ID=<token> SID=<session-id>
|
|
11
|
+
# ROUTE=spec KEY=<token> SID=<session-id>
|
|
12
|
+
# ERROR=<message>
|
|
13
|
+
# SID is a fresh session id minted per dispatch; the agent passes it to every
|
|
14
|
+
# `task complete` call so outcomes group as one session in the dashboard.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
raw="${*:-}"
|
|
18
|
+
|
|
19
|
+
# One fresh session id per dispatch. Generated here so the agent never invents
|
|
20
|
+
# or copies a literal — it reads SID off the ROUTE line below. Unique per
|
|
21
|
+
# invocation: two separate runs get distinct ids and group as separate sessions
|
|
22
|
+
# in the dashboard.
|
|
23
|
+
SID="zn-$(date +%s)-$(openssl rand -hex 2)"
|
|
24
|
+
|
|
25
|
+
# Strip leading verbs and an optional `spec` keyword, collapse whitespace.
|
|
26
|
+
trimmed="$(printf '%s' "$raw" \
|
|
27
|
+
| sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' \
|
|
28
|
+
| sed -E 's/^(implement|work on|continue|do|run|execute)[[:space:]]+//I' \
|
|
29
|
+
| sed -E 's/^spec[[:space:]]+//I')"
|
|
30
|
+
|
|
31
|
+
# Empty -> sync.
|
|
32
|
+
if [ -z "$trimmed" ] || printf '%s' "$trimmed" | grep -qiE '^sync$'; then
|
|
33
|
+
echo "ROUTE=sync"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Explicit task target.
|
|
38
|
+
if printf '%s' "$trimmed" | grep -qiE '^task[[:space:]]+'; then
|
|
39
|
+
id="$(printf '%s' "$trimmed" | sed -E 's/^task[[:space:]]+//I' | awk '{print $1}')"
|
|
40
|
+
echo "ROUTE=task ID=$id SID=$SID"
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Otherwise: first token is a candidate spec key of ANY shape. Resolve it.
|
|
45
|
+
token="$(printf '%s' "$trimmed" | awk '{print $1}')"
|
|
46
|
+
|
|
47
|
+
if zenorm pull "$token" >/dev/null 2>&1; then
|
|
48
|
+
# Deterministic, non-agent step: starting execution of a spec moves all of
|
|
49
|
+
# its `planned` tasks into `todo`. Idempotent — safe to re-run. Failure here
|
|
50
|
+
# must not block routing, so log to stderr and keep the one-line contract.
|
|
51
|
+
zenorm task start-spec "$token" 1>&2 || \
|
|
52
|
+
echo "warn: 'task start-spec $token' failed; tasks not advanced to todo" 1>&2
|
|
53
|
+
echo "ROUTE=spec KEY=$token SID=$SID"
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Spec pull failed. If it looks like a UUID, retry as a task id.
|
|
58
|
+
if printf '%s' "$token" | grep -qiE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; then
|
|
59
|
+
echo "ROUTE=task ID=$token SID=$SID"
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Genuinely unresolvable. Surface the real CLI error.
|
|
64
|
+
err="$(zenorm pull "$token" 2>&1 || true)"
|
|
65
|
+
echo "ERROR=spec '$token' did not resolve: $err"
|
|
66
|
+
exit 1
|