@cohortapp/agent-sdk 2.12.0 → 2.14.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/bin/maestro.mjs +6 -2
- package/docs/guides/front-door-session.md +86 -0
- package/lib/cli/design.mjs +185 -0
- package/lib/cli/design.test.mjs +270 -0
- package/lib/cli/global-setup-extras.mjs +44 -0
- package/lib/cli/global-setup-extras.test.mjs +95 -0
- package/lib/cli/session.mjs +11 -1
- package/lib/cli/session.test.mjs +17 -6
- package/lib/collective/global-config.mjs +5 -0
- package/lib/collective/global-config.test.mjs +5 -0
- package/lib/collective/vendor-skills.mjs +305 -0
- package/lib/collective/vendor-skills.test.mjs +306 -0
- package/lib/design/design-md.mjs +793 -0
- package/lib/design/design-md.test.mjs +318 -0
- package/lib/design/fixtures/DESIGN.golden.md +238 -0
- package/lib/design/fixtures/PRODUCT.golden.md +67 -0
- package/lib/design/fixtures/foundation.json +133 -0
- package/lib/design/refresh-gate.mjs +154 -0
- package/lib/design/refresh-gate.test.mjs +144 -0
- package/lib/design/write.mjs +275 -0
- package/lib/design/write.test.mjs +241 -0
- package/lib/prompts/parallelism.mjs +79 -0
- package/lib/prompts/parallelism.test.mjs +177 -0
- package/lib/telemetry/collect.mjs +357 -5
- package/lib/telemetry/collect.test.mjs +285 -0
- package/package.json +1 -1
- package/plugins/maestro-skills/plugin.json +4 -0
- package/plugins/maestro-skills/skills/cohort-design.md +153 -0
- package/plugins/maestro-skills/vendor/emilkowalski/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/emilkowalski/UPSTREAM.json +70 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/RECIPES.md +324 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/SKILL.md +199 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animation-vocabulary/SKILL.md +173 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/apple-design/SKILL.md +282 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/emil-design-eng/SKILL.md +674 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/find-animation-opportunities/SKILL.md +132 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/AUDIT.md +115 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/SKILL.md +101 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/PICKER.md +197 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/SKILL.md +90 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/SKILL.md +112 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/STANDARDS.md +187 -0
- package/plugins/maestro-skills/vendor/impeccable/LICENSE +191 -0
- package/plugins/maestro-skills/vendor/impeccable/NOTICE.md +11 -0
- package/plugins/maestro-skills/vendor/impeccable/SKILL.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/UPSTREAM.json +201 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-asset-producer.md +42 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-documenter.md +29 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-finish-reviewer.md +43 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.md +312 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.native.md +58 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/android.md +46 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/animate.md +89 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.md +136 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.native.md +139 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/bolder.md +33 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/clarify.md +94 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/colorize.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft-floor.md +44 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft.md +5 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/critique.md +806 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/asset-producer.md +37 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/documenter.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/delight.md +70 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/distill.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/doctor.md +54 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/document.md +416 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/extract.md +69 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/harden.md +336 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/hooks.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/init.md +131 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/ios.md +51 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/layout.md +84 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live-setup.md +104 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live.md +325 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/new-work.md +147 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/onboard.md +234 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/operate.md +61 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/optimize.md +258 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/overdrive.md +127 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/polish.md +105 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/quieter.md +99 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/routing.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/shape.md +59 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/typeset.md +80 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/visualize.md +46 -0
- package/plugins/maestro-skills/vendor/taste-skill/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/taste-skill/UPSTREAM.json +37 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/minimalist-skill/SKILL.md +85 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/redesign-skill/SKILL.md +178 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/soft-skill/SKILL.md +98 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/taste-skill/SKILL.md +1206 -0
- package/plugins/maestro-skills/vendor/unlazy/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/unlazy/SECURITY.md +72 -0
- package/plugins/maestro-skills/vendor/unlazy/SKILL.md +104 -0
- package/plugins/maestro-skills/vendor/unlazy/UPSTREAM.json +94 -0
- package/plugins/maestro-skills/vendor/unlazy/references/dispatch.md +82 -0
- package/plugins/maestro-skills/vendor/unlazy/references/gates.md +149 -0
- package/plugins/maestro-skills/vendor/unlazy/references/method.md +49 -0
- package/plugins/maestro-skills/vendor/unlazy/references/orchestration.md +107 -0
- package/plugins/maestro-skills/vendor/unlazy/references/parallel.md +133 -0
- package/plugins/maestro-skills/vendor/unlazy/references/token-economy.md +48 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/dispatch-check.mjs +139 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-check.mjs +960 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-lint.mjs +245 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/check-supervisor.mjs +46 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/dispatch.mjs +293 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/gates.mjs +953 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/process-tree.mjs +161 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/regex-worker.mjs +9 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/PLAN.md +116 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-leaf.md +51 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-node.md +51 -0
- package/scripts/ci/check-skill-packs.mjs +388 -0
- package/scripts/ci/check-skill-packs.test.mjs +495 -0
- package/scripts/ci/check.mjs +3 -0
- package/scripts/daemon/agent-daemon-design.test.mjs +238 -0
- package/scripts/daemon/agent-daemon.mjs +108 -0
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +61 -2
- package/scripts/daemon/cadence-consumer.mjs +46 -22
- package/scripts/daemon/prompt-builder.mjs +19 -3
- package/scripts/local-triggers/autoupdate.test.mjs +33 -3
- package/scripts/vendor/skill-packs.mjs +354 -0
- package/scripts/vendor/sync-skill-packs.mjs +242 -0
- 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
|
+
}
|