@cohortapp/agent-sdk 2.12.0 → 2.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.
Files changed (127) hide show
  1. package/bin/maestro.mjs +6 -2
  2. package/docs/guides/front-door-session.md +49 -0
  3. package/lib/cli/design.mjs +185 -0
  4. package/lib/cli/design.test.mjs +270 -0
  5. package/lib/cli/global-setup-extras.mjs +44 -0
  6. package/lib/cli/global-setup-extras.test.mjs +95 -0
  7. package/lib/cli/session.mjs +11 -1
  8. package/lib/cli/session.test.mjs +17 -6
  9. package/lib/collective/global-config.mjs +5 -0
  10. package/lib/collective/global-config.test.mjs +5 -0
  11. package/lib/collective/vendor-skills.mjs +305 -0
  12. package/lib/collective/vendor-skills.test.mjs +306 -0
  13. package/lib/design/design-md.mjs +793 -0
  14. package/lib/design/design-md.test.mjs +318 -0
  15. package/lib/design/fixtures/DESIGN.golden.md +238 -0
  16. package/lib/design/fixtures/PRODUCT.golden.md +67 -0
  17. package/lib/design/fixtures/foundation.json +133 -0
  18. package/lib/design/refresh-gate.mjs +154 -0
  19. package/lib/design/refresh-gate.test.mjs +144 -0
  20. package/lib/design/write.mjs +275 -0
  21. package/lib/design/write.test.mjs +241 -0
  22. package/lib/prompts/parallelism.mjs +79 -0
  23. package/lib/prompts/parallelism.test.mjs +177 -0
  24. package/package.json +1 -1
  25. package/plugins/maestro-skills/plugin.json +4 -0
  26. package/plugins/maestro-skills/skills/cohort-design.md +153 -0
  27. package/plugins/maestro-skills/vendor/emilkowalski/LICENSE +21 -0
  28. package/plugins/maestro-skills/vendor/emilkowalski/UPSTREAM.json +70 -0
  29. package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/RECIPES.md +324 -0
  30. package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/SKILL.md +199 -0
  31. package/plugins/maestro-skills/vendor/emilkowalski/skills/animation-vocabulary/SKILL.md +173 -0
  32. package/plugins/maestro-skills/vendor/emilkowalski/skills/apple-design/SKILL.md +282 -0
  33. package/plugins/maestro-skills/vendor/emilkowalski/skills/emil-design-eng/SKILL.md +674 -0
  34. package/plugins/maestro-skills/vendor/emilkowalski/skills/find-animation-opportunities/SKILL.md +132 -0
  35. package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/AUDIT.md +115 -0
  36. package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
  37. package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/SKILL.md +101 -0
  38. package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/PICKER.md +197 -0
  39. package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/SKILL.md +90 -0
  40. package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/SKILL.md +112 -0
  41. package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/STANDARDS.md +187 -0
  42. package/plugins/maestro-skills/vendor/impeccable/LICENSE +191 -0
  43. package/plugins/maestro-skills/vendor/impeccable/NOTICE.md +11 -0
  44. package/plugins/maestro-skills/vendor/impeccable/SKILL.md +86 -0
  45. package/plugins/maestro-skills/vendor/impeccable/UPSTREAM.json +201 -0
  46. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-asset-producer.md +42 -0
  47. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-documenter.md +29 -0
  48. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-finish-reviewer.md +43 -0
  49. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
  50. package/plugins/maestro-skills/vendor/impeccable/reference/adapt.md +312 -0
  51. package/plugins/maestro-skills/vendor/impeccable/reference/adapt.native.md +58 -0
  52. package/plugins/maestro-skills/vendor/impeccable/reference/android.md +46 -0
  53. package/plugins/maestro-skills/vendor/impeccable/reference/animate.md +89 -0
  54. package/plugins/maestro-skills/vendor/impeccable/reference/audit.md +136 -0
  55. package/plugins/maestro-skills/vendor/impeccable/reference/audit.native.md +139 -0
  56. package/plugins/maestro-skills/vendor/impeccable/reference/bolder.md +33 -0
  57. package/plugins/maestro-skills/vendor/impeccable/reference/clarify.md +94 -0
  58. package/plugins/maestro-skills/vendor/impeccable/reference/colorize.md +86 -0
  59. package/plugins/maestro-skills/vendor/impeccable/reference/craft-floor.md +44 -0
  60. package/plugins/maestro-skills/vendor/impeccable/reference/craft.md +5 -0
  61. package/plugins/maestro-skills/vendor/impeccable/reference/critique.md +806 -0
  62. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/asset-producer.md +37 -0
  63. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/documenter.md +24 -0
  64. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/finish-reviewer.md +38 -0
  65. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  66. package/plugins/maestro-skills/vendor/impeccable/reference/delight.md +70 -0
  67. package/plugins/maestro-skills/vendor/impeccable/reference/distill.md +111 -0
  68. package/plugins/maestro-skills/vendor/impeccable/reference/doctor.md +54 -0
  69. package/plugins/maestro-skills/vendor/impeccable/reference/document.md +416 -0
  70. package/plugins/maestro-skills/vendor/impeccable/reference/extract.md +69 -0
  71. package/plugins/maestro-skills/vendor/impeccable/reference/harden.md +336 -0
  72. package/plugins/maestro-skills/vendor/impeccable/reference/hooks.md +111 -0
  73. package/plugins/maestro-skills/vendor/impeccable/reference/init.md +131 -0
  74. package/plugins/maestro-skills/vendor/impeccable/reference/ios.md +51 -0
  75. package/plugins/maestro-skills/vendor/impeccable/reference/layout.md +84 -0
  76. package/plugins/maestro-skills/vendor/impeccable/reference/live-setup.md +104 -0
  77. package/plugins/maestro-skills/vendor/impeccable/reference/live.md +325 -0
  78. package/plugins/maestro-skills/vendor/impeccable/reference/new-work.md +147 -0
  79. package/plugins/maestro-skills/vendor/impeccable/reference/onboard.md +234 -0
  80. package/plugins/maestro-skills/vendor/impeccable/reference/operate.md +61 -0
  81. package/plugins/maestro-skills/vendor/impeccable/reference/optimize.md +258 -0
  82. package/plugins/maestro-skills/vendor/impeccable/reference/overdrive.md +127 -0
  83. package/plugins/maestro-skills/vendor/impeccable/reference/polish.md +105 -0
  84. package/plugins/maestro-skills/vendor/impeccable/reference/quieter.md +99 -0
  85. package/plugins/maestro-skills/vendor/impeccable/reference/routing.md +24 -0
  86. package/plugins/maestro-skills/vendor/impeccable/reference/shape.md +59 -0
  87. package/plugins/maestro-skills/vendor/impeccable/reference/typeset.md +80 -0
  88. package/plugins/maestro-skills/vendor/impeccable/reference/visualize.md +46 -0
  89. package/plugins/maestro-skills/vendor/taste-skill/LICENSE +21 -0
  90. package/plugins/maestro-skills/vendor/taste-skill/UPSTREAM.json +37 -0
  91. package/plugins/maestro-skills/vendor/taste-skill/skills/minimalist-skill/SKILL.md +85 -0
  92. package/plugins/maestro-skills/vendor/taste-skill/skills/redesign-skill/SKILL.md +178 -0
  93. package/plugins/maestro-skills/vendor/taste-skill/skills/soft-skill/SKILL.md +98 -0
  94. package/plugins/maestro-skills/vendor/taste-skill/skills/taste-skill/SKILL.md +1206 -0
  95. package/plugins/maestro-skills/vendor/unlazy/LICENSE +21 -0
  96. package/plugins/maestro-skills/vendor/unlazy/SECURITY.md +72 -0
  97. package/plugins/maestro-skills/vendor/unlazy/SKILL.md +104 -0
  98. package/plugins/maestro-skills/vendor/unlazy/UPSTREAM.json +94 -0
  99. package/plugins/maestro-skills/vendor/unlazy/references/dispatch.md +82 -0
  100. package/plugins/maestro-skills/vendor/unlazy/references/gates.md +149 -0
  101. package/plugins/maestro-skills/vendor/unlazy/references/method.md +49 -0
  102. package/plugins/maestro-skills/vendor/unlazy/references/orchestration.md +107 -0
  103. package/plugins/maestro-skills/vendor/unlazy/references/parallel.md +133 -0
  104. package/plugins/maestro-skills/vendor/unlazy/references/token-economy.md +48 -0
  105. package/plugins/maestro-skills/vendor/unlazy/scripts/dispatch-check.mjs +139 -0
  106. package/plugins/maestro-skills/vendor/unlazy/scripts/gate-check.mjs +960 -0
  107. package/plugins/maestro-skills/vendor/unlazy/scripts/gate-lint.mjs +245 -0
  108. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/check-supervisor.mjs +46 -0
  109. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/dispatch.mjs +293 -0
  110. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/gates.mjs +953 -0
  111. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/process-tree.mjs +161 -0
  112. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/regex-worker.mjs +9 -0
  113. package/plugins/maestro-skills/vendor/unlazy/templates/PLAN.md +116 -0
  114. package/plugins/maestro-skills/vendor/unlazy/templates/gates-leaf.md +51 -0
  115. package/plugins/maestro-skills/vendor/unlazy/templates/gates-node.md +51 -0
  116. package/scripts/ci/check-skill-packs.mjs +388 -0
  117. package/scripts/ci/check-skill-packs.test.mjs +495 -0
  118. package/scripts/ci/check.mjs +3 -0
  119. package/scripts/daemon/agent-daemon-design.test.mjs +238 -0
  120. package/scripts/daemon/agent-daemon.mjs +108 -0
  121. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +61 -2
  122. package/scripts/daemon/cadence-consumer.mjs +46 -22
  123. package/scripts/daemon/prompt-builder.mjs +19 -3
  124. package/scripts/local-triggers/autoupdate.test.mjs +33 -3
  125. package/scripts/vendor/skill-packs.mjs +354 -0
  126. package/scripts/vendor/sync-skill-packs.mjs +242 -0
  127. package/scripts/vendor/sync-skill-packs.test.mjs +103 -0
@@ -0,0 +1,107 @@
1
+ # Orchestrated mode
2
+
3
+ Use orchestrated mode when one context cannot hold the task and its verification at full attention. Keep the driver responsible for planning, dispatch, independent verification, integration, and the root report.
4
+
5
+ ## Declare states and paths
6
+
7
+ Use these leaf states only:
8
+
9
+ - `WAITING`: one or more ids in `Needs` are not yet `VERIFIED`
10
+ - `READY`: dependencies are verified and ownership is available
11
+ - `IN-FLIGHT`: dispatched and not yet independently verified
12
+ - `VERIFIED`: parent re-verification passed and manual gates were reviewed
13
+ - `ABANDONED`: at least one required gate has a recorded handoff; never treat this as full completion
14
+
15
+ Use `OPEN`, `VERIFIED`, or `ABANDONED` for branches. Store leaf ledgers as `gates/leaf-<id>.md` and integration ledgers as `gates/node-<id>.md`. Do not label a branch path as `leaf-*`.
16
+
17
+ ## Driver loop
18
+
19
+ 1. **Plan before fan-out.** Reread the original request and current amendments. Create `.unlazy/<scope>/PLAN.md`, `.unlazy/<scope>/GATES.md`, and one ledger per leaf and branch from the templates. Inventory every independently omittable outcome and acceptance-changing constraint with a stable id, owner, observing gate or manual review, disposition, and revision. Fix interfaces, naming, toolchain, dependencies, and exact ownership before dispatch.
20
+ 2. **Inspect and approve checks.** Run `gate-check --status` on every inherited ledger. Review each `CHECK:`, `EXPECT:`, and `CWD:`, including called scripts. Determine the shell and inherited `PATH`; a new oracle with no exact approval prints its resolved values during a normal run without executing. Use `--approve` only after inspection, and do not treat normal mode as a dry run once approval exists.
21
+ 3. **Claim every concurrent leaf.** Run:
22
+
23
+ ```text
24
+ node <skill-dir>/scripts/gate-check.mjs --scope <scope> --leaf leaf-1.2.1 --claim
25
+ ```
26
+
27
+ A refused claim means the split is not safe for concurrent dispatch. Change the plan or run the work sequentially; never bypass the refusal.
28
+ 4. **Launch each ready wave.** Give each leaf only the shared contract, its exact ownership and dependencies, its own ledger, and the four-pass completion rule. Open a dispatch wave for the independent `READY` leaves, call the host's native nonblocking launch once per leaf, record every host handle, and seal the wave before the first wait or result read. Follow [dispatch.md](dispatch.md); do not leak unrelated leaf histories.
29
+ 5. **Verify each return independently.** Record the native return in its wave, then re-run the returned leaf's runnable gates, including already checked gates:
30
+
31
+ ```text
32
+ node <skill-dir>/scripts/gate-check.mjs --root . --cwd . --reverify .unlazy/<scope>/gates/leaf-1.2.1.md
33
+ ```
34
+
35
+ `--status` alone is not re-verification. If an approved oracle changed, inspect it and approve the new oracle before continuing. Review manual gates directly and try to refute at least one passed gate.
36
+ 6. **Append status and roll forward.** Record the result without rewriting history:
37
+
38
+ ```text
39
+ node <skill-dir>/scripts/gate-check.mjs --scope <scope> --log "leaf-1.2.1 verified"
40
+ ```
41
+
42
+ Mark the leaf `VERIFIED`, release that exact leaf lease, and record the release before promotion:
43
+
44
+ ```text
45
+ node <skill-dir>/scripts/gate-check.mjs --scope <scope> --leaf leaf-1.2.1 --release
46
+ node <skill-dir>/scripts/gate-check.mjs --scope <scope> --log "leaf-1.2.1 lease released"
47
+ ```
48
+
49
+ Only then promote newly unblocked leaves from `WAITING` to `READY` and dispatch them without waiting for unrelated in-flight leaves.
50
+ 7. **Integrate bottom-up.** Work each `node-*.md` ledger only after all named children return. Reverify the children, then run interface, end-to-end, and regression checks.
51
+ 8. **Reconcile, release, and report.** Reread the current request and review every current contract row. Missing/stale ownership or observation, abandonment, deferment, and owner decisions are non-completion. Release the whole scope only after every leaf has settled, every dispatch wave is terminal, branch and root ledgers have been reverified, and final aggregate verification has run. Scope-wide release before that point is reserved for explicit recovery after verifying the recorded owner is gone, never normal promotion. Report only when both the inventory and root ledger are met, then remeasure every reported count.
52
+
53
+ ## Check concurrency
54
+
55
+ Gate checks run sequentially by default (`--jobs 1`). This is the easiest transcript to debug and is the compatibility behavior.
56
+
57
+ Use `--jobs <N>` only when runnable gates are independent and parallel execution reduces wall-clock time:
58
+
59
+ ```text
60
+ node <skill-dir>/scripts/gate-check.mjs --root . --cwd . --reverify --jobs 4 .unlazy/<scope>/gates/leaf-1.1.1.md .unlazy/<scope>/gates/leaf-1.1.2.md
61
+ ```
62
+
63
+ The limit is rolling: start another check when one finishes instead of waiting for a fixed batch. Output and file updates remain deterministic in ledger order. `--jobs` controls command execution, not subagent dispatch and not dependency readiness. Use [dispatch waves](dispatch.md) for native agent concurrency.
64
+
65
+ ## Rolling dispatch
66
+
67
+ Treat dispatch as a loop:
68
+
69
+ ```text
70
+ while an unverified leaf remains:
71
+ collect the independent READY leaves up to the host concurrency limit
72
+ open a dispatch wave for that exact set
73
+ launch every native agent and record every returned host handle
74
+ seal the wave before the first wait
75
+ wait for the next leaf to return
76
+ record that return in its dispatch wave
77
+ reverify that leaf and review its manual evidence
78
+ append status and mark it VERIFIED
79
+ release that exact leaf lease and record the release
80
+ promote each WAITING leaf whose Needs are all VERIFIED
81
+ ```
82
+
83
+ Do not invent a dependency during dispatch. Add it to `PLAN.md`, correct the affected states, and record the change. A user amendment increments the contract revision and must be reconciled before more completion credit. Prefer independent leaves, but do not force independence where an interface must be established first.
84
+
85
+ ## Verification hierarchy
86
+
87
+ 1. **Leaf self-check:** catches ordinary incompleteness but remains self-certification.
88
+ 2. **Parent `--reverify`:** executes each runnable oracle again instead of trusting old or manually written evidence.
89
+ 3. **Branch integration:** catches locally correct children that do not compose.
90
+ 4. **Optional Stop hook:** blocks the driver from ending while its resolved pipeline has unmet ledgers or incomplete dispatch waves. It does not execute checks or validate their meaning.
91
+
92
+ The parent must use the same required toolchain and declared shell. If the environment differs, record and resolve the mismatch instead of accepting old evidence.
93
+
94
+ ## Manual gates
95
+
96
+ Automation cannot prove every user-facing or judgment-heavy outcome. For each manual gate:
97
+
98
+ - cite the exact artifact, location, measurement, or reviewer decision
99
+ - review consequences, not only visual polish
100
+ - obtain independent review for high-risk outcomes when feasible
101
+ - keep the gate unmet if evidence is ambiguous
102
+
103
+ Do not call a leaf `VERIFIED` merely because every runnable gate passed.
104
+
105
+ ## When not to orchestrate
106
+
107
+ Stay solo when one focused context can implement and verify the task without hiding independent deliverables. Orchestration has planning and integration overhead; use it for attention isolation, not ceremony.
@@ -0,0 +1,133 @@
1
+ # Parallel pipelines and leaves
2
+
3
+ Scopes and ownership leases coordinate cooperating unlazy processes. They prevent accidental cross-certification and refuse declared ownership overlap. They do not sandbox shell commands, enforce operating-system permissions, or stop a process that ignores the protocol from writing any file.
4
+
5
+ ## Layout
6
+
7
+ ```text
8
+ .unlazy/
9
+ <scope>/
10
+ PLAN.md
11
+ GATES.md
12
+ gates/
13
+ leaf-*.md
14
+ node-*.md
15
+ status.log
16
+ session
17
+ hook-state.json
18
+ locks/
19
+ ```
20
+
21
+ Keep `.unlazy/` untracked. The legacy single-pipeline layout, `GATES.md` plus `gates/*.md` at the project root, remains available for solo work.
22
+
23
+ ## Scope resolution
24
+
25
+ A scoped checker invocation selects one pipeline in this order:
26
+
27
+ 1. `--scope <id>`
28
+ 2. `UNLAZY_SCOPE`
29
+ 3. the only scope present
30
+ 4. the legacy layout when no scoped pipeline exists
31
+
32
+ The Stop hook can additionally use the current Claude Code `session_id` binding written by `--bind`. A binding associates a session with a scope; it is not authentication.
33
+
34
+ When several scopes exist and none resolves, the checker refuses instead of running every ledger. The Stop hook allows the stop with a diagnostic instead of blocking a session on an unknown pipeline.
35
+
36
+ Use scope ids that match `[A-Za-z0-9][A-Za-z0-9._-]{0,63}` and are not `.` or `..`. Do not use separators, traversal, or absolute paths.
37
+
38
+ ## What a scope isolates
39
+
40
+ A scope limits unlazy's own:
41
+
42
+ - default gate discovery
43
+ - status log target
44
+ - Stop-hook resolution and progress state
45
+ - lease owner label
46
+
47
+ A scope does not limit a `CHECK:` process. Checks inherit ambient operating-system access and can read or write outside the scope. Use separate worktrees or stronger process isolation when commands themselves must be isolated.
48
+
49
+ ## Ownership declarations
50
+
51
+ Declare repository-relative paths before the first gate:
52
+
53
+ ```markdown
54
+ OWNS: src/api/**, tests/api/**
55
+ ```
56
+
57
+ Reject absolute paths and any path containing a `..` traversal segment. Every leaf dispatched concurrently must declare all paths it may modify and claim them before work:
58
+
59
+ ```text
60
+ node <skill-dir>/scripts/gate-check.mjs --scope api --leaf leaf-1.2.1 --claim
61
+ ```
62
+
63
+ Claiming checks all existing leases and writes the new lease while holding one global lease lock. Two simultaneous conflicting claims cannot both succeed. A claim is all-or-nothing.
64
+
65
+ A scope/leaf label is itself exclusive while its lease exists. Repeating the same claim is refused even when the replacement paths are disjoint, and the refused attempt never rewrites the original lease's OWNS path set. Release that exact leaf before claiming it again. This prevents two workers dispatched with the same logical identity from both believing they own one lease.
66
+
67
+ Lock directories contain JSON owner metadata. Unlazy deliberately does not auto-break an apparently stale lock, because deleting a live owner's path can let two successors enter at once. If a process dies while holding a lock, first verify that its recorded process is no longer running and that no unlazy operation could still own the lock, then remove only that specific abandoned lock manually. Never clear the whole lock directory while work is active. Approval-record locks use the same recovery rule.
68
+
69
+ Overlap detection is deliberately conservative. It may reject two globs that a full intersection engine could prove disjoint, especially globs with mid-segment wildcards. It must not clear an uncertain pair as safe. Treat over-conflict as a prompt to use simpler disjoint paths or sequential dispatch.
70
+
71
+ Examples:
72
+
73
+ | First declaration | Second declaration | Result |
74
+ |---|---|---|
75
+ | `src/api/**` | `src/web/**` | disjoint |
76
+ | `src/shared/**` | `src/shared/util.mjs` | conflict |
77
+ | `src/a*.mjs` | `src/ab*.mjs` | conflict because intersection is possible |
78
+ | `**` | `docs/**` | conflict |
79
+
80
+ Leases cover only declared paths and only participants that honor them. They are coordination records, not write isolation.
81
+
82
+ After parent re-verification and manual review, release only that exact leaf and
83
+ record the release before promoting any dependent:
84
+
85
+ ```text
86
+ node <skill-dir>/scripts/gate-check.mjs --scope api --release --leaf leaf-1.2.1
87
+ node <skill-dir>/scripts/gate-check.mjs --scope api --log "leaf-1.2.1 lease released"
88
+ ```
89
+
90
+ Release the whole scope only after every leaf has settled, all dispatch waves
91
+ are terminal, and branch, root, and final aggregate verification have run. The
92
+ scope-wide form is otherwise only for explicit recovery after verifying the
93
+ recorded owner is gone:
94
+
95
+ ```text
96
+ node <skill-dir>/scripts/gate-check.mjs --scope api --release
97
+ ```
98
+
99
+ An unknown `--leaf` is an error. The checker never silently falls back to the first ledger.
100
+
101
+ ## Concurrent ledger updates
102
+
103
+ The checker serializes each gate-file update and commits it atomically. Before applying a completed check, it re-reads the ledger and confirms that the gate id and oracle fields still match what ran. If the command, expectation, working directory, or other bound oracle field changed in flight, the stale result is discarded.
104
+
105
+ Preserve the ledger's original LF or CRLF style. Insert a missing evidence line without changing unrelated content. Keep result output deterministic in gate order even when `--jobs <N>` executes checks concurrently.
106
+
107
+ The status log is append-only:
108
+
109
+ ```text
110
+ node <skill-dir>/scripts/gate-check.mjs --scope api --log "leaf-1.2.1 verified"
111
+ ```
112
+
113
+ Append-only logging reduces lost updates; it does not replace the live state fields in `PLAN.md`.
114
+
115
+ ## Session-keyed hook state
116
+
117
+ The Stop hook keys progress state to the resolved scope and current session. Concurrent hook calls serialize their state update. Completion or disappearance of the ledger clears obsolete state. One session cannot consume another session's six no-progress blocks.
118
+
119
+ The hook may be pinned with installer `--scope` or resolve a session binding written by:
120
+
121
+ ```text
122
+ node <skill-dir>/scripts/gate-check.mjs --scope api --bind <session-id>
123
+ ```
124
+
125
+ Do not treat a stored session id as a secret or identity proof.
126
+
127
+ ## Choose the right isolation level
128
+
129
+ - Use one working tree and several scopes for read-heavy work or leaves with simple disjoint ownership.
130
+ - Use one worktree per pipeline when worktree-local output or generated files would collide. Configure separate cache directories when cache writes can conflict; worktrees do not isolate external caches or services.
131
+ - Use operating-system or container isolation for untrusted commands. Unlazy approval and leases are not a sandbox.
132
+
133
+ Parallelism changes wall-clock time, not the evidence standard. Parent re-verification and branch integration remain required.
@@ -0,0 +1,48 @@
1
+ # Token economy
2
+
3
+ Spend model attention on implementation and judgment. Move repeated, deterministic verification into commands and keep orchestration context narrow.
4
+
5
+ ## Keep enforcement cheap
6
+
7
+ - **Use runnable checks.** External command execution does not itself require model inference. The agent still spends context on the command, returned output, failure interpretation, and evidence review.
8
+ - **Cap evidence.** Store resolved environment facts plus the automatic output fingerprint, never raw successful output or a full build log.
9
+ - **Keep the Stop hook scan-only.** The hook itself does not call a model. A block causes another agent continuation, which does consume model work, so keep the six-block no-progress guard and make each block actionable.
10
+ - **Use sequential checks by default.** Raise `--jobs` only for independent checks when wall-clock savings justify harder failure diagnosis.
11
+
12
+ ## Keep contexts focused
13
+
14
+ - Give a leaf the shared contract and its own ledger, not the driver's transcript or unrelated leaf outputs.
15
+ - Keep `SKILL.md` limited to the core workflow. Load method, gate, orchestration, and parallel references only when the selected mode needs them.
16
+ - Append events to `status.log`. Do not repeatedly regenerate a large plan when one line records the event.
17
+ - Keep failure logs local and summarize only non-sensitive decisive facts when a manual report needs them; automatic success evidence already contains a digest and byte count.
18
+
19
+ ## Mark leaf reasoning needs without inventing host controls
20
+
21
+ `Tier` is planner metadata for execution leaves, not a model name or a routing
22
+ guarantee:
23
+
24
+ - Use `judgment` when the leaf's own artifact needs design, security or
25
+ compatibility reasoning, consequential manual review, or non-mechanical
26
+ verification.
27
+ - Use `mechanical` only when the transformation pattern and acceptance gates are
28
+ already fixed.
29
+
30
+ If the host exposes a documented model or reasoning control, the driver may map
31
+ these tiers through that host-specific control at launch. If no such control is
32
+ available, retain the tier as a briefing and review requirement and do not claim
33
+ that a particular model or reasoning level was selected.
34
+
35
+ Driver and branch duties are not leaf tiers. Contract and architecture work,
36
+ dispatch decisions, parent re-verification, branch integration, and the final
37
+ claim audit remain judgment responsibilities even when every execution leaf is
38
+ mechanical.
39
+
40
+ ## Avoid false economy
41
+
42
+ Do not save time by skipping approval, negative controls, parent re-verification, or integration gates. Those checks exist because a fast false completion costs more than a direct failure.
43
+
44
+ Do not orchestrate a task that one focused session can implement and verify cleanly. Conversely, do not keep an entire build in one context merely to avoid subagent overhead when independent leaves and contracts are clear.
45
+
46
+ ## Measurement claims
47
+
48
+ Earlier unlazy documentation gave exact token and effort ratios from a six-run exploratory comparison. The raw prompts, traces, outputs, and scoring records are not present in this repository, so those numbers are not reproducible here. Do not use them as product guarantees. A protocol for a future reproducible rerun is in [../research/validation-protocol.md](../research/validation-protocol.md).
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ // Records and checks the all-starts-before-wait dispatch contract. Node 16+.
3
+
4
+ import { resolve } from "node:path";
5
+ import { getDispatchWave, updateDispatch } from "./lib/dispatch.mjs";
6
+
7
+ const COMMANDS = new Set(["open", "start", "seal", "return", "abandon", "status"]);
8
+ const args = process.argv.slice(2);
9
+
10
+ function usage() {
11
+ return [
12
+ "Usage:",
13
+ " dispatch-check.mjs open --scope ID --wave ID --leaf ID [--leaf ID ...] [--root PATH]",
14
+ " dispatch-check.mjs start --scope ID --wave ID --leaf ID --handle OPAQUE_ID [--root PATH]",
15
+ " dispatch-check.mjs seal --scope ID --wave ID [--root PATH]",
16
+ " dispatch-check.mjs return --scope ID --wave ID --leaf ID [--root PATH]",
17
+ " dispatch-check.mjs abandon --scope ID --wave ID --reason TEXT [--root PATH]",
18
+ " dispatch-check.mjs status --scope ID --wave ID [--root PATH]",
19
+ ].join("\n");
20
+ }
21
+
22
+ const UNSAFE_TERMINAL = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/;
23
+ const TRUNCATION_MARKER = "...[truncated]";
24
+ function terminalSafe(value, maxBytes = 500) {
25
+ const pieces = [];
26
+ const sizes = [];
27
+ let bytes = 0;
28
+ let truncated = false;
29
+ for (const character of String(value)) {
30
+ let piece = character;
31
+ if (UNSAFE_TERMINAL.test(character)) {
32
+ const code = character.codePointAt(0);
33
+ piece = code <= 0xff
34
+ ? "\\x" + code.toString(16).padStart(2, "0")
35
+ : "\\u" + code.toString(16).padStart(4, "0");
36
+ }
37
+ const size = Buffer.byteLength(piece, "utf8");
38
+ if (bytes + size > maxBytes) { truncated = true; break; }
39
+ pieces.push(piece);
40
+ sizes.push(size);
41
+ bytes += size;
42
+ }
43
+ if (!truncated) return pieces.join("");
44
+ const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
45
+ while (pieces.length && bytes + markerBytes > maxBytes) {
46
+ pieces.pop();
47
+ bytes -= sizes.pop();
48
+ }
49
+ return pieces.join("") + TRUNCATION_MARKER;
50
+ }
51
+
52
+ function die(message, showUsage = false) {
53
+ console.error("unlazy dispatch: " + terminalSafe(message));
54
+ if (showUsage) console.error(usage());
55
+ process.exit(2);
56
+ }
57
+
58
+ if (!args.length || args[0] === "--help" || args[0] === "-h") {
59
+ console.log(usage());
60
+ process.exit(args.length ? 0 : 2);
61
+ }
62
+
63
+ const command = args.shift();
64
+ if (!COMMANDS.has(command)) die("unknown command " + command, true);
65
+
66
+ const options = { root: process.cwd(), scope: null, wave: null, leaves: [], handle: null, reason: null };
67
+ const single = new Set();
68
+ while (args.length) {
69
+ const option = args.shift();
70
+ if (!["--root", "--scope", "--wave", "--leaf", "--handle", "--reason"].includes(option)) die("unknown option " + option);
71
+ if (!args.length || args[0].startsWith("--")) die(option + " requires a value");
72
+ const value = args.shift();
73
+ if (option === "--leaf") options.leaves.push(value);
74
+ else {
75
+ if (single.has(option)) die(option + " may be provided only once");
76
+ single.add(option);
77
+ options[option.slice(2)] = value;
78
+ }
79
+ }
80
+
81
+ if (!options.scope) die("--scope is required");
82
+ if (!options.wave) die("--wave is required");
83
+ options.root = resolve(options.root);
84
+
85
+ if (command === "open") {
86
+ if (!options.leaves.length) die("open requires at least one --leaf");
87
+ if (options.handle !== null || options.reason !== null) die("open does not accept --handle or --reason");
88
+ } else if (command === "start") {
89
+ if (options.leaves.length !== 1) die("start requires exactly one --leaf");
90
+ if (options.handle === null) die("start requires --handle");
91
+ if (options.reason !== null) die("start does not accept --reason");
92
+ } else if (command === "return") {
93
+ if (options.leaves.length !== 1) die("return requires exactly one --leaf");
94
+ if (options.handle !== null || options.reason !== null) die("return does not accept --handle or --reason");
95
+ } else if (command === "abandon") {
96
+ if (options.leaves.length || options.handle !== null) die("abandon does not accept --leaf or --handle");
97
+ if (options.reason === null || !options.reason.trim()) die("abandon requires --reason");
98
+ } else if (options.leaves.length || options.handle !== null || options.reason !== null) {
99
+ die(command + " does not accept --leaf, --handle, or --reason");
100
+ }
101
+
102
+ const summary = (wave, id) => {
103
+ const started = Object.keys(wave.started).length;
104
+ const returned = Object.keys(wave.returned).length;
105
+ if (wave.state === "complete") return "COMPLETE " + id + " (" + returned + "/" + wave.leaves.length + " returned)";
106
+ if (wave.state === "abandoned") return "ABANDONED " + id + " (" + started + "/" + wave.leaves.length +
107
+ " started, " + returned + "/" + wave.leaves.length + " returned): " + terminalSafe(wave.reason);
108
+ return wave.state.toUpperCase() + " " + id + " (" + started + "/" + wave.leaves.length +
109
+ " started, " + returned + "/" + wave.leaves.length + " returned)";
110
+ };
111
+
112
+ try {
113
+ if (command === "status") {
114
+ const wave = getDispatchWave(options.root, options.scope, options.wave);
115
+ console.log(summary(wave, options.wave));
116
+ process.exit(wave.state === "complete" ? 0 : 1);
117
+ }
118
+
119
+ const { wave, logWarning } = await updateDispatch(options.root, {
120
+ action: command,
121
+ scope: options.scope,
122
+ wave: options.wave,
123
+ leaves: options.leaves,
124
+ leaf: options.leaves[0],
125
+ handle: options.handle,
126
+ reason: options.reason,
127
+ });
128
+ if (logWarning) console.error("unlazy dispatch: warning: " + terminalSafe(logWarning));
129
+ const started = Object.keys(wave.started).length;
130
+ const returned = Object.keys(wave.returned).length;
131
+ if (command === "open") console.log("OPEN " + options.wave + " (0/" + wave.leaves.length + " started, 0/" + wave.leaves.length + " returned)");
132
+ else if (command === "start") console.log("STARTED " + options.wave + " " + options.leaves[0] + " (" + started + "/" + wave.leaves.length + " started)");
133
+ else if (command === "seal") console.log("SEALED " + options.wave + " (" + started + "/" + wave.leaves.length + " started)");
134
+ else if (command === "abandon") console.log(summary(wave, options.wave));
135
+ else if (wave.state === "complete") console.log("COMPLETE " + options.wave + " (" + returned + "/" + wave.leaves.length + " returned)");
136
+ else console.log("RETURNED " + options.wave + " " + options.leaves[0] + " (" + returned + "/" + wave.leaves.length + " returned)");
137
+ } catch (error) {
138
+ die(error.message);
139
+ }