@ze-norm/cli 0.11.5 → 0.12.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.
@@ -19,13 +19,18 @@ export interface TaskRunner {
19
19
  run(task: SpecTaskRecord): Promise<void>;
20
20
  }
21
21
  /**
22
- * Build the handoff prompt that tells the agent to execute THIS already-claimed
23
- * task via the bundled ZeNorm skills. The `/zenorm task <id>` trigger is what
24
- * the `zenorm` dispatcher SKILL.md keys on (single-task mode `execute-task`).
22
+ * Build the handoff prompt that hands the agent the WHOLE SPEC the claimed task
23
+ * belongs to, so a single agent session implements every available task for that
24
+ * spec with shared context instead of one fresh session per task. The
25
+ * `/zenorm <specId>` trigger is what the `zenorm` dispatcher SKILL.md keys on
26
+ * (spec mode → `execute-task` walks every `planned`/`todo` task in order).
25
27
  *
26
- * The task is already claimed/active server-side (the claim flipped it), so the
27
- * prompt says "already-claimed" and instructs the agent to complete it via the
28
- * normal `zenorm task complete` flow the daemon does NOT post the outcome.
28
+ * The claim only flips THIS one task to `active` server-side (the lease that
29
+ * proves there is work for this repo and guards against a second daemon). The
30
+ * prompt tells the agent that `task.id` is already active so spec mode picks it
31
+ * up alongside its still-`todo` siblings rather than skipping it. The agent
32
+ * completes each task itself via `zenorm task complete` — the daemon does NOT
33
+ * post outcomes.
29
34
  *
30
35
  * Exported for unit testing.
31
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAqB,cAAc,EAAgB,MAAM,iBAAiB,CAAC;AAgBvF;;;;GAIG;AACH,QAAA,MAAM,gBAAgB,8BAA+B,CAAC;AACtD,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AA0BxD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAe/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,cAAc,GACnB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAsCrC;AAQD;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,YAAY,KAClB,YAAY,CAAC;AAElB;;;;;;;;;GASG;AACH,qBAAa,WAAY,YAAW,UAAU;IAI1C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAEtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,SAAgB,SAAS,EAAE,cAAc,CAAC;gBAGvB,KAAK,EAAE,cAAc,EAErB,OAAO,GAAE,OAAe;IAK3C,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CA0EzC;AAED;;;;GAIG;AACH,qBAAa,YAAa,YAAW,UAAU;IAGjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,SAAgB,SAAS,EAAE,cAAc,CAAC;gBAEb,KAAK,EAAE,cAAc;IAIlD,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAOzC;AAUD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmD3E;AA4BD,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AA+GD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CAuHlB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
1
+ {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAqB,cAAc,EAAgB,MAAM,iBAAiB,CAAC;AAgBvF;;;;GAIG;AACH,QAAA,MAAM,gBAAgB,8BAA+B,CAAC;AACtD,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AA0BxD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAeD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAmB/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,cAAc,GACnB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAsCrC;AAQD;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,YAAY,KAClB,YAAY,CAAC;AAElB;;;;;;;;;GASG;AACH,qBAAa,WAAY,YAAW,UAAU;IAI1C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAEtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,SAAgB,SAAS,EAAE,cAAc,CAAC;gBAGvB,KAAK,EAAE,cAAc,EAErB,OAAO,GAAE,OAAe;IAK3C,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CA0EzC;AAED;;;;GAIG;AACH,qBAAa,YAAa,YAAW,UAAU;IAGjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,SAAgB,SAAS,EAAE,cAAc,CAAC;gBAEb,KAAK,EAAE,cAAc;IAIlD,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAOzC;AAUD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmD3E;AA4BD,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AA+GD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CA2HlB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
@@ -43,30 +43,39 @@ const AGENT_INSTALL_HINT = {
43
43
  "(https://github.com/openai/codex).",
44
44
  };
45
45
  /**
46
- * Build the handoff prompt that tells the agent to execute THIS already-claimed
47
- * task via the bundled ZeNorm skills. The `/zenorm task <id>` trigger is what
48
- * the `zenorm` dispatcher SKILL.md keys on (single-task mode `execute-task`).
46
+ * Build the handoff prompt that hands the agent the WHOLE SPEC the claimed task
47
+ * belongs to, so a single agent session implements every available task for that
48
+ * spec with shared context instead of one fresh session per task. The
49
+ * `/zenorm <specId>` trigger is what the `zenorm` dispatcher SKILL.md keys on
50
+ * (spec mode → `execute-task` walks every `planned`/`todo` task in order).
49
51
  *
50
- * The task is already claimed/active server-side (the claim flipped it), so the
51
- * prompt says "already-claimed" and instructs the agent to complete it via the
52
- * normal `zenorm task complete` flow the daemon does NOT post the outcome.
52
+ * The claim only flips THIS one task to `active` server-side (the lease that
53
+ * proves there is work for this repo and guards against a second daemon). The
54
+ * prompt tells the agent that `task.id` is already active so spec mode picks it
55
+ * up alongside its still-`todo` siblings rather than skipping it. The agent
56
+ * completes each task itself via `zenorm task complete` — the daemon does NOT
57
+ * post outcomes.
53
58
  *
54
59
  * Exported for unit testing.
55
60
  */
56
61
  export function buildHandoffPrompt(task) {
57
62
  const zenormCli = process.env["ZENORM_CLI"]?.trim() || "zenorm";
58
63
  return [
59
- `/zenorm task ${task.id}`,
64
+ `/zenorm ${task.specId}`,
60
65
  "",
61
- `Execute the already-claimed ZeNorm task "${task.title}" (id: ${task.id}).`,
66
+ `Execute ALL available tasks for ZeNorm spec ${task.specId} in THIS one`,
67
+ "session (spec mode — walk every `planned`/`todo` task in list order). Do not",
68
+ "stop after a single task; drain the spec's ready work before exiting.",
62
69
  `For ZeNorm CLI shell commands, use \`${zenormCli}\`; if the installed`,
63
70
  "skill text shows `zenorm ...`, substitute that command with the one",
64
71
  "named here for this run.",
65
- "It has already been claimed and flipped to `active` server-side, so do not",
66
- "re-claim it. Implement it end-to-end in the current repository working",
67
- "directory using the existing ZeNorm execute workflow, then complete it via",
68
- `the normal \`${zenormCli} task complete <task-id>\` flow. Work non-interactively;`,
69
- "do not wait for further input.",
72
+ `Task "${task.title}" (id: ${task.id}) has already been claimed and flipped to`,
73
+ "`active` server-side — do NOT re-claim it; treat it as one of the spec's",
74
+ "ready tasks and implement it alongside the rest. Implement everything",
75
+ "end-to-end in the current repository working directory using the existing",
76
+ "ZeNorm execute workflow, completing each task via the normal",
77
+ `\`${zenormCli} task complete <task-id>\` flow as you finish it. Work`,
78
+ "non-interactively; do not wait for further input.",
70
79
  ].join("\n");
71
80
  }
72
81
  /**
@@ -422,11 +431,15 @@ export async function runWorkLoop(opts) {
422
431
  }
423
432
  return response.task ?? null;
424
433
  };
425
- // Run a single claimed task to completion. The agent's own transcript (its
426
- // tool calls + prose, re-rendered by work-render.ts) IS the body of the
427
- // output; the daemon frames it with a numbered task header and closes with a
428
- // ✓/✗ outcome line (+ a loud banner on failure). A failed run is released back
429
- // to `todo` so it can be re-claimed.
434
+ // Run one claimed task's SPEC to completion. The claim flips a single task to
435
+ // `active` to prove there is ready work for this repo; the agent it launches
436
+ // then drains every available task of that spec in ONE session (spec mode
437
+ // see buildHandoffPrompt). The agent's own transcript (its tool calls + prose,
438
+ // re-rendered by work-render.ts) IS the body of the output; the daemon frames
439
+ // it with a numbered header (the claimed entry task) and closes with a ✓/✗
440
+ // outcome line (+ a loud banner on failure). On failure the entry task is
441
+ // released back to `todo` so the spec can be re-claimed; tasks the agent
442
+ // already completed stay `done`.
430
443
  const runTask = async (task) => {
431
444
  started += 1;
432
445
  const agentName = runner.agentName ?? "agent";
@@ -1,10 +1,14 @@
1
1
  ---
2
2
  name: execute-task
3
- description: Execute ZeNorm work — either a full spec (all tasks in order) or
4
- a single task by ID. Use when the `/zenorm` dispatcher hands off, when the
5
- user says "implement spec <key>", "do task <id>", or references a ZeNorm
6
- spec/task. Always pulls the full spec so the agent has the complete picture
7
- before writing code. Fails fast on any unsatisfied acceptance criterion.
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. For each task with
124
- status `planned` or `todo`:
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
- Then: do not advance to the next task, do not mark the failed task
160
- done, do **not** call `task complete` for it (that would auto-flip to
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. Hand off to `zenorm-sync` for outcome flush.
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, leave the task `active`, do not call `task complete`,
190
- report what failed.
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. Hand off to `zenorm-sync` (success or failure).
241
+ 7. **Finish:** see "Handing off to sync" sync ownership depends on
242
+ `--dispatched`.
203
243
 
204
244
  ## Session id
205
245
 
206
- Use the session id the `/zenorm` dispatcher generated. If you were invoked
207
- directly (no dispatcher), generate one once at the start:
208
- `zn-$(date +%s)-$(openssl rand -hex 2)` (or similar) and reuse it across
209
- every `task complete` call in this invocation.
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: **do not mark done**. Fix the underlying problem
229
- or report the blocker. Never bypass with `--no-verify` or skip the
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), invoke `zenorm-sync` to post outcomes.
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 `/zenorm`,
5
- says "implement spec <key>", "work on spec <key>", "continue spec <key>",
6
- "sync zenorm", or otherwise references ZeNorm specs/tasks by key (e.g.
7
- "AUTH-123") or UUID. Routes the request to the right focused skill:
8
- full-spec execution, single-task execution, or session sync-back.
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. Your job is to read the user's
14
- request, pick the right focused skill, and hand off — not to do the work
15
- yourself.
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
- ## Inputs
21
+ ## Routing
18
22
 
19
- Raw user phrase (`$ARGUMENTS`): everything after `/zenorm`.
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
- Examples:
22
- - `/zenorm implement spec Z-123`
23
- - `/zenorm Z-123`
24
- - `/zenorm task <task-uuid>`
25
- - `/zenorm sync`
26
- - `/zenorm continue Z-123`
27
+ ```sh
28
+ scripts/resolve.sh $ARGUMENTS
29
+ ```
27
30
 
28
- ## Routing rules
31
+ It prints exactly one line. Act on it:
29
32
 
30
- Parse `$ARGUMENTS`, strip leading verbs (`implement`, `work on`, `continue`,
31
- `do`, `run`, `execute`), then dispatch:
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
- | Trimmed input shape | Skill to invoke | Args passed |
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
- A bare UUID is ambiguous resolve by trying spec lookup first
40
- (`zenorm pull <id>`); if that fails treat it as a task ID.
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
- If the request does not match any pattern, ask the user which spec or task
43
- they mean. Do not guess.
49
+ ## How to invoke a child skill
44
50
 
45
- ## Invocation
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
- Invoke the chosen skill by name using the `Skill` tool do not narrate
48
- the dispatch. The skill itself will run `zenorm pull`, `zenorm task list`,
49
- etc., so you do not pre-fetch.
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
- The file IS the skill — apply the same fallback to `zenorm-sync` if it
65
- also refuses. Do not give up because the harness gated the Skill tool.
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** — invoke `zenorm-sync` to flush outcomes. Sync runs in both
69
- cases; the partial-state report is part of the contract. The session-stop
70
- hook (`zenorm session-check`) will block stop otherwise.
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 convention
76
+ ## Session id
73
77
 
74
- Generate one session id at the start of the dispatch and pass it to every
75
- `task complete` call across this whole flow. Format:
76
- `zn-<unix-timestamp>-<short-random>` (e.g. `zn-1717449600-a7f3`). Reuse the
77
- same id for every task in the same `/zenorm` invocation so outcomes group
78
- correctly and the stop hook's idempotency check works.
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 — delegate to `execute-task`.
83
- - Do not mark tasks done here — `execute-task` and `zenorm-sync` own that.
84
- - Do not skip `zenorm-sync` even if the work was small. The hook will
85
- block stop until outcomes are posted.
86
- - Do not invent new spec keys or task IDs. If the user's reference is
87
- unresolvable, stop and ask.
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
@@ -107,12 +107,13 @@ or any task is still `active`.
107
107
 
108
108
  ## Fail-fast / partial-completion rules
109
109
 
110
- - Do **not** call `task complete` on a blocked task.
111
- - Do **not** claim the spec is complete in your final user-facing
112
- message if any task is `active` or any AC is unmet.
113
- - The blocker reason lives in the chat transcript today (no
114
- `task block` command exists). Surface it in the final report and, if
115
- the work needed is non-trivial, capture it as a follow-up task:
110
+ The bucketing in Step 1 already covers the core rule (never `task complete` a
111
+ blocked task; never claim completion when something is `active` or unmet). One
112
+ thing it doesn't cover capturing the blocker durably:
113
+
114
+ - The blocker reason lives in the chat transcript today (no `task block`
115
+ command exists). Surface it in the final report and, if the work needed is
116
+ non-trivial, capture it as a follow-up task:
116
117
  `zenorm task create <spec-key> --title="Unblock <task>: <reason>"`.
117
118
 
118
119
  ## Rules
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ze-norm/cli",
3
3
  "private": false,
4
- "version": "0.11.5",
4
+ "version": "0.12.0",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",
7
7
  "repository": {