The v1.10.0/.1 feature shipped without user-facing docs. Adds:
- SKILL.md: warmWorktree in the Workflow args list + comment, and a Build-phase
paragraph on fast worktree provisioning (CoW-seed node_modules/vendor/Pods,
Rust sccache, frozen-install fallback, warmWorktree:false = plain install).
- README.md: the ship-playbook section now describes fast worktree provisioning.
Three corrections to the v1.10.0 warm/seed step:
- Gitignore is now surgical: .ulpi/.gitignore gets a `seed-worktree.sh` line
(appended if the file exists, else created with just that line) instead of
`*`, which would have hidden the plans/reviews/status files the workflow
writes under .ulpi and that you may commit.
- Raise the sccache cap to 50G (and stop/start the server so it takes effect):
the 10G default LRU-evicts a large dep graph (datafusion+arrow+candle) mid-run
and silently kills the hit rate. The cap is also set in the engineer's Rust
export so a cargo-autostarted server comes back at 50G, not 10G.
- Warm primes sccache into a THROWAWAY CARGO_TARGET_DIR (mktemp -d), not
${ROOT}/target — sccache's cache is target-dir-independent, so the shared
cache is populated without clobbering the local incremental fast-loop cargo
(e.g. dev-fast.sh) relies on. Also retires the torn-clone race.
Every build/fix engineer runs in an isolation:'worktree' checkout with empty
deps/artifacts, so each cold-installs or cold-compiles from scratch — per lane,
per layer, and again on resume. This seeds each worktree from the primary
checkout instead, per stack, and shares Rust compilation via sccache.
- One tested seed script (written to .ulpi/ by an early warm step, gitignored):
CoW-clones node_modules / vendor / Pods from ROOT when the lockfile is unchanged
(clonefile/reflink — instant same-volume), else a frozen --prefer-offline
install. Never a slow copy: the macOS path gates on same device id (cp -c
silently full-copies cross-volume), the Linux path uses --reflink=always
(errors instead of a silent fallback).
- Rust is sccache-only — NO target/ clone (redundant with sccache, multi-GB×N
disk, torn-clone risk). Engineers export RUSTC_WRAPPER=sccache
CARGO_INCREMENTAL=0 in the build shell (raw cargo and wrapper scripts inherit
it); the warm step primes sccache from ROOT before the fan-out.
- Warm starts right after preflight, overlapping the LLM-bound plan/plan-review
phases (~0 added wall-clock on fresh runs; a harmless near-noop on resume).
- Default on; CFG.warmWorktree=false restores today's plain per-worktree install
verbatim. Go left untouched (GOCACHE/GOMODCACHE already global).
Plan review's fix rounds edit the plan FILES on disk (planFixBrief: .json
source of truth, re-render .md) but return only FIX_RESULT {applied,notes} —
the in-memory `plan` object is never updated. buildPlan() reads plan.tasks
directly and never re-reads the files, so when a fix round runs the build
walks the PRE-FIX plan: founder-review fixes to writeScope / dependsOn /
layers / agent / validate silently don't reach the build.
Fix: planReviewLoop now reports whether it applied a fix; after any review
that did, reload the plan from disk (validate; keep the pre-fix object if the
reload fails to parse) so the build walks the fixed DAG. No-op when the review
is clean (no fix → no reload).
Extends the launch-* family to X (Twitter) and LinkedIn — own-audience social
launches — grounded in current sources and reviewed across multiple adversarial
rounds (9 findings fixed, 0 blockers/highs; facts independently re-confirmed clean).
New skills:
- launch-x — X launch orchestrator: writes .ulpi/launch/x/ (POST thread, PLAN
golden-hour runbook, OUTREACH, CHECKLIST, analytics). The link goes in a REPLY,
not the main tweet (link-in-body reach penalty); golden-hour replies are the top
lever (X's open-source code: a reply the author replies back to = 75.0, the
highest positive signal); X Premium is ~table-stakes for reach (Buffer's 18.8M-
post study, third-party). Phoenix (xAI, Jan 2026) replaced the heavy-ranker, so
2026 weights are labeled non-official.
- launch-linkedin — LinkedIn launch orchestrator: writes .ulpi/launch/linkedin/.
Problem-first hook (survives the ~140-char mobile "see more" fold), link in the
FIRST COMMENT (it's the preview card that hurts, and the penalty is contested/
smaller in 2026 — don't over-engineer), dwell-time-first (an officially confirmed
signal). Sanctioned team amplification ("Notify Employees") vs banned engagement
pods + engagement-bait (both official violations).
Unlike Product Hunt / Hacker News, these are own-audience posts with NO vote-
manipulation rule — you MAY ask your audience and team to genuinely engage. The
compliance work shifts to platform-penalized anti-patterns: engagement-bait,
bought/coordinated engagement / pods, and the link-in-body reach penalty.
Shared: launch-copy gains x + linkedin asset profiles; launch-outreach gains
x + linkedin engagement-allowed compliance modes + the engagement-bait scan.
README adds rows + sections for both. Each composes launch-copy / launch-outreach
/ launch-analytics with graceful fallback.
Adds a launch-skill family (one skill per platform + a shared core), grounded in
verified current sources and reviewed across 4 adversarial rounds (~43 findings
fixed, 0 blockers/highs; compliance + facts independently re-confirmed clean).
New skills:
- launch-product-hunt — PH platform orchestrator: grounds the product, writes a
paste-ready package to .ulpi/launch/product-hunt/ (LISTING, PLAN with an
hour-by-hour Pacific runbook, OUTREACH, CHECKLIST gate). Verified PH specs
(tagline 60, description 500, gallery 1270x760, topics <=3, four newsletters,
Golden Kitty -> Orbit, Coming Soon discontinued, re-launch 6mo + significant
update); official vs third-party labeled.
- launch-hacker-news — Show HN platform skill: writes POST/PLAN/CHECKLIST to
.ulpi/launch/hacker-news/. Grounded verbatim in showhn.html / newsguidelines /
newsfaq / dang; ranking formula labeled non-official. Forbids ALL vote
mobilization (stricter than PH); ready-to-ship gate (no signup wall, survives
the hug of death); technical/no-hype voice.
- launch-copy — shared copywriting engine; caller passes an asset profile
(assets/limits/voice/compliance); drafts A/B/C/D angle versions grounded in the
real product.
- launch-outreach — shared outreach; per-platform compliance mode (PH waves OK,
HN no mobilization); paste-ready, scan-clean message library.
- launch-analytics — shared UTM + GA4 conversion tracking.
Architecture: platforms compose the three shared skills with graceful fallback
(install-hint + built-in fallback, never hard-fail); one shared product brief at
.ulpi/launch/positioning.md, written once and reused. Compliance-first throughout
— coaches only compliant promotion, a banned-phrase scan gates every message,
refuses vote manipulation.
README: adds table rows + full sections for all five skills.
Replaces the per-LAYER batch build with a PER-TASK PIPELINE and fixes the
whole-suite-validate false-block that ground clean slices to death. Hardened
across ~16 adversarial verify→fix rounds (each round: find → independently
confirm; the final round confirmed 0).
Per-task pipeline (build-loop.md, workflow-template.js buildPlan/runTask):
- each task is its own unit: build → integrate → review → fix, run CONCURRENTLY
within a DAG layer. A task's review fires the moment IT integrates (not batched
after the whole layer), and fix loops run in parallel across tasks — only the
git merge serializes, via a single-slot merge lock. Layers stay barriered (DAG).
- integrate is MERGE-ONLY: no per-task whole-workspace VALIDATE_ALL. The old
per-task `pnpm -r test` turned an OTHER task's failure (half-migrated tree) into
a per-task blocker, looping a CLEAN-reviewed slice to MAX_FIX then false-blocking
it. Whole-tree validation now runs ONCE at the final validate gate.
- merge CONFLICTS are resolved (combine both sides, validate BEFORE commit, abort
only if truly unresolvable), not bailed. Merge is idempotent under retry
(clears stale MERGE_HEAD; merge-base ancestry short-circuit; branch-gone fails
CLOSED → dev_done/rebuild, never falsely 'integrated').
Fail-closed gates: a died/absent/empty reviewer (native/codex/kiro gateNotRun), a
died go-live audit, a blocking VERDICT with no finding, or a RED final validate all
keep openRegister non-empty → never a false converged. reviewOnce surfaces verdict;
per-task/impl/audit each guard the blocking-verdict-with-empty-findings case.
Durable resume + modes:
- wf-status.mjs --resume <id> emits the exact Workflow() relaunch from the status
file (planPath + checkpointResume, no resumeFromRunId, no args to hand-assemble);
the skill stores launchArgs at launch. SKILL.md Step 0 makes new-run vs resume
first-class.
- LAST STEP: a converged (clean) run archives the delivered plan to
.ulpi/plans/done/ (planArchived; skipped on needs_fix / external plans; non-fatal).
build-loop.md / playbook-state.md / SKILL.md / README updated; helper + template
syntax-checked; pure helpers unit-tested.
Hardened the build loop across nine adversarial verification passes (12→12→7→1→
4→4→3→3→0 confirmed findings; the false-clean invariant was independently
confirmed closed before shipping).
Checkpoint resume (durable, not cache-based):
- buildPlan reads the status file (readCheckpoints, before the plan-done write so
it isn't clobbered) and SKIPS every task already done — 'passed' (skip engineer
+ review) or on-branch (integrated/reviewing/fixing/blocked → skip engineer,
re-review). Independent of the runtime agent cache, which any template edit busts.
- SKILL.md: on resume REUSE the prior status file; do not overwrite it.
DAG dependency gate (build a task only when its deps are really on the branch):
- per-task dependsOn in the PLAN schema; planBrief requires complete deps +
topological layers + slice-scoped validate + merging inseparable tasks;
validatePlan rejects mis-ordered/cyclic/duplicate/omitted-task/unknown-dep plans
(and derives topological layers when none are given).
- runtime gate: a fresh task builds only once every dependsOn is integrated on
WORKING_BRANCH; a missing dep ⇒ dep_blocked (root-cause attribution, cascades).
Integrate matched by branch/id (id-agnostic), not a TASK-NNN regex.
Slice-scoped review (no false end-state blocks) + fail-closed gates:
- reviewer judges only the task's writeScope vs its acceptance; fix loop acts only
on in-scope blockers; fileInScope handles globs/abs-paths/empty-scope.
- a configured gate that did NOT run is NEVER clean: died/absent/empty reviewer
(native/codex/kiro gateNotRun), died go-live audit, and a RED FINAL workspace
validate (resume-safe, checks out WORKING_BRANCH) each keep openRegister
non-empty. Returns planReviewRan/implReviewRan/auditRan/workspaceValidatePassed/
endStateUngated/blockedTaskCount so the skill reports honestly. No uncaught throw
crashes the run (build/review/integrate/audit/plan all fail-soft).
Live status: status-writer agents per phase + DAG layer; durable .ulpi/workflows/
<id>.json; new statuses dep_blocked/dev_done. build-loop.md, status-tracking.md,
SKILL.md (rules 11-12 + Phase-2/3), README updated. Template syntax-checked; pure
helpers unit-tested.
reconstruct() already attributes reviews/fixes to tasks via the agent transcript
and reports latest-verdict-per-task (v1.6.2). This adds the last piece: surface
WHICH task each running agent is on. A started-with-no-result agent → its prompt
names the role+task → header shows "1 running NOW → fix:TASK-035#1" (and review:/
build:/integrate:…), plus --json.runningNow. No more bare "1 running".
The journal's review/fix results carry no task id, so the result-shape pass
couldn't tell "integrated" from "integrated but review-blocked" or see
review-pending. But each agent's transcript (agent-<id>.jsonl) starts with its
prompt, which names the role + task ("READ-ONLY review of TASK-032", "Fix
TASK-032 …", "integrate … wf/build/TASK-007"); agentId joins it to the journal
result + whether it finished.
- reconstruct(): join prompt(role+task) × result × started → true per-task status
pending/building/dev_done/integrated/reviewing/fixing/passed/blocked/dev_failed,
incl. review-PENDING and fixing. Reads only each transcript's first line; ~0.3s
over ~280 agents.
- render / --json / --write now use a DISJOINT status partition (sums to total)
with an onBranch rollup; --write stamps the true per-task status into the file.
- Verified on the live 75-task run: 13 passed, 24 review-blocked, 1 reviewing,
14 dev-failed, 23 not started (was reported flatly as "38 integrated").
status-tracking.md documents the prompt-join. Helper syntax-checked.
wf-status.mjs scoped runs to the current project by slugifying cwd with only
'/' → '-', but Claude's project dir slug replaces EVERY non-alphanumeric char
(so '_' and '.' too): /Users/x/work_cip/ulpi-v6 → -Users-x-work-cip-ulpi-v6.
Any path with an underscore/dot never matched its own project, so the default
(no --all) reported "no workflow runs found for this project". Slugify cwd with
/[^a-zA-Z0-9]/g → '-' to match. Verified against the real ulpi-v6 run dir.
A build task can fail because its acceptance/validate need state owned by a
not-yet-integrated task (e.g. TASK-053 needs the TASK-015 catalog rows + the
TASK-054 registry test). The build integrates layer-by-layer with a barrier, so
the real cause is a PLAN defect: the task was placed at/before its dependencies.
Fix it by following the DAG, not by retrying around it at runtime.
- PLAN schema: add per-task `dependsOn` (ids whose output the task needs).
- planBrief: require complete `dependsOn`; `layers` MUST be a topological order
(a task strictly after everything it depends on); each `validate` slice-scoped
(not a whole-suite e2e); merge two pieces that can't validate independently.
- validatePlan: ABORT a plan whose layers violate `dependsOn` (a task at/before a
dependency, or a dep not scheduled earlier / missing) — refuse to build on a
base where required upstream isn't integrated. Unit-tested against the 053 case.
- founderBrief (plan review): BLOCK incomplete deps, non-topological layers,
whole-suite validates, and mutually-blocking split tasks.
- loadPlanBrief (resume): preserve `dependsOn`.
SKILL.md guardrail + build-loop.md "Follow the DAG" section + README updated.
Template syntax-checked; topological mis-order detection unit-tested.
For a run launched before v1.5.0 (no status file, incl. an in-flight one),
`wf-status.mjs --write` now reconstructs a complete .ulpi/workflows/<run>.json
from the journal: plan name/path, working branch (from the preflight result),
the full task list with title/agent/branch + per-task status, layer count, live
counts + merge conflicts, inferred phases, and a planPath-based resume command.
The journal holds no launch args (gate config / hardRules / validate / prompt are
script inputs, not agent results), so `--args '<json>'` folds them in to complete
the resume recipe. Marked partial:true. Documented in references/status-tracking.md
(new "Backfill" section) + SKILL.md. Verified against a live 75-task run.
Two fixes to the delivery Workflow (forward-only; does not affect in-flight runs,
which execute from their own persisted script snapshot).
1. Slice-scoped per-task review (stops false BLOCK storms).
The build lands one DAG slice at a time, so per-task reviews ran against a
half-migrated tree and BLOCKed tasks for whole-codebase end-state gaps a LATER
task owns (e.g. "legacy paste path still exists" vs a task with no scope to remove
it) — burning all 3 fix rounds, then recording a false "blocked" (observed 72/81
blocked, ~184 BLOCKs dominated by one end-state finding).
- reviewerBrief now judges only the task's own writeScope + diff against ITS
acceptance criteria, and is given the rest of the plan so an unmet end-state
invariant a later task owns becomes an OBSERVATION attributed to that task,
never a BLOCK on the current slice.
- The fix loop acts only on in-scope blockers (fileInScope matcher); out-of-scope
findings are counted (crossTaskDeferred) and deferred, not retried.
- Impl review (step 12) is now the explicit end-state gate over the integrated tree.
2. Live status tracking.
- Durable <root>/.ulpi/workflows/<id>.json: skill creates it pre-launch (the
Workflow sandbox has no FS), stamps runId + resume command, and the Workflow
updates it at every phase + DAG layer via cheap haiku status-writer agents
(all writes sequential → race-free; non-fatal, never blocks the build).
- helpers/wf-status.mjs: journal reader reconstructing per-task status from the
run's journal.jsonl (zero template changes, works mid-flight); cwd-scoped,
--list/--json/--write, reads the durable file for phase/verdict. Ported and
generalized from a tool first built in the ulpi-v6 repo.
- Three verbs (status/stop/resume) documented; new references/status-tracking.md.
build-loop.md, playbook-state.md, SKILL.md (guardrails + EXTREMELY-IMPORTANT rules)
and README updated. Template + helper syntax-checked; fileInScope unit-tested 8/8.
- kiro-review SKILL.md: list valid kiro model names inline (auto, claude-opus-4.8/4.7/4.6,
claude-sonnet-4.6/4.5/4, claude-haiku-4.5) so it's self-sufficient; note opus/sonnet aliases invalid.
- ship-playbook: new KIRO_MODEL const (default claude-opus-4.8 = latest Opus, override via CFG.kiroModel)
injected into all three kiro briefs (build via hand-over-to-kiro, per-task review + routed review via
kiro-review) → '--model claude-opus-4.8'. Documented kiroModel in the args contract.
Helper gains --agent <name> (run kiro as a custom .kiro/agents/<name>.json agent) and
--model <name> (override the model). Documented the real kiro model names from
'kiro-cli chat --list-models' in references/kiro-cli.md → Models (auto, claude-opus-4.6/4.7/4.8,
claude-sonnet-4/4.5/4.6, claude-haiku-4.5, deepseek-3.2, minimax, glm-5, qwen3-coder-next).
Verified live (kiro-cli 2.6.0):
- A kiro-NATIVE agent (valid model + fs_read/fs_write/execute_bash tools) implements end-to-end.
- The ulpi .kiro/agents/*.json do NOT work as-is: model 'opus' is invalid (hard error) and the
Claude-style tool names (Read/Write/Skill) leave the agent with no working tools (writes nothing).
- --model auto rescues an agent that pins an invalid model.
So running specific agents works once the agent JSON is kiro-native (valid model + native tools) —
that generation is the AGENTS/ulpi ecosystem's job, not this repo. --skill injection remains the
zero-agent path that already works. Helpers byte-identical; bash 3.2 + set -u safe.
Doing skill-referencing in the helper, not as a prompt instruction the agent must
remember (same lesson as moving the prompt mechanics into the helper).
run-kiro.sh: new repeatable --skill <name> flag. Resolves .kiro/skills/<name>/SKILL.md
(or ~/.kiro/skills/<name>/), prepends it to kiro's STDIN under a <skill> tag with a
'read references/ on demand' note, and warns+skips an uninstalled name. Kiro has no
Skill tool, so this deterministic inject is how kiro follows a skill in a one-shot
--no-interactive run. bash 3.2/set -u safe (no arrays).
SKILL.md (both): Step 2.5 now just names the skills → pass --skill; removed the manual
<skills> prompt blocks. ship-playbook kiro brief passes --skill for the task's stackSkill.
Tested live end-to-end (kiro-cli 2.6.0): implement mode + --skill made kiro create a
file whose first line obeyed the injected skill's mandatory rule — proving the skill
drives behavior, not just that bytes arrived.
Kiro has a native skills feature (default agent auto-discovers .kiro/skills/ and
~/.kiro/skills/) but NO Skill tool and no --trust-tools token for it; skill bodies
load on demand, so a one-shot --no-interactive run can't rely on auto-activation
(confirmed against kiro.dev + AWS Amazon Q docs). The handoff never told kiro to
use the relevant skill, so kiro built without the stack conventions.
Fix (deterministic, no new tool/trust): the prompt-builder INLINES the relevant
.kiro/skills/<name>/SKILL.md into kiro's prompt under a <skill> tag and tells kiro
to follow it + fs_read its references/ on demand.
- hand-over-to-kiro: new Step 2.5 (reference relevant skills) + <skills> block in
both prompt templates + a Skills section in references/kiro-cli.md documenting
kiro's mechanism (auto-discovery, resources file://skill://, no Skill tool).
- kiro-review: inline a stack/convention skill so kiro reviews against it.
- ship-playbook kiro build brief: tell the handoff to inline the task's stackSkill.
Documents the native alternative too (--agent with resources:[file://...] preload).
The kiro skills hand-rolled a fragile inline launch (BSD mktemp choking on the
.txt suffix, stale files, kiro running on an empty prompt) and used
--trust-all-tools / -a even for read-only reviews (which the auto-mode classifier
blocks on non-mutating tasks). Modeled the fix on the codex companion: encapsulate
the mechanics in a helper, feed the prompt over stdin (not argv/shell).
helpers/run-kiro.sh (new, in both skills): locates kiro-cli, REFUSES to launch on
an empty prompt, scopes trust by mode (review=fs_read,execute_bash;
implement=fs_read,fs_write,execute_bash; autonomous=--trust-all-tools opt-in),
captures a git baseline, and feeds the prompt via STDIN (verified kiro-cli 2.6.0
reads stdin) — no mktemp, no heredoc, no argv, no shell parsing of the prompt.
SKILL.md (both): agent writes the prompt with the Write tool, then calls the
helper; trust scoped by task; never default to --trust-all-tools. ship-playbook
kiro build brief switched to implement mode (scoped write trust), not all-tools.
Tested live (kiro-cli 2.6.0): both helpers run review mode end-to-end, empty-prompt
guard returns exit 65.
Accept a pre-built, pre-reviewed plan and start at the build phase, skipping
plan-writing and plan-review. The skill passes either the parsed plan object
(args.plan) or a path (args.planPath); the Workflow sandbox can't read files, so
a path is loaded + validated + normalized by a load-plan agent. New validatePlan()
guards the DAG shape (tasks need id/agent/writeScope/validate; layers must
reference real ids) for supplied AND freshly-written plans, aborting cleanly if
malformed. PROMPT is no longer required when resuming (the plan is the spec).
Returns planSupplied so Phase 3 reports that plan-review was skipped by design.
SKILL.md: RESUME intake drops gate Q1 (plan writer) + Q2 (plan reviewer), asks the
other five, validates .ulpi/plans/<name>.json, passes planPath. README: note the
resume path. Distinct from the runtime's resumeFromRunId (mid-run crash recovery).
A rate-limit storm previously surfaced as a wall of false 'blocked: engineer
validate failed' tasks: agent() returns null when a subagent dies on a terminal
API error after the runtime's own retries (the 0-token door-rejections), or
throws on a transient fault (cut off mid-flight). Those are not real build/review
failures.
Added withRetry(fn) + sleep + isRateLimit + RETRY_DELAYS ([3s,10s,30s], up to 4
attempts). Retries on null return or a rate-limit-shaped throw (429/overloaded/
529/quota); genuine errors (e.g. agent-not-found) still surface immediately.
Wired into makeGate (covers the parallel engineers/reviewers/verify lenses) and
via a rAgent() wrapper on the 9 non-gated sequential calls (plan, preflight,
integrate/re-integrate, plan-fix, cleanup, routed reviews).
Sandbox-safe: no Date.now/Math.random; sleep is setTimeout-guarded and degrades
to immediate retry. Resume-safe: cached results return non-null on attempt 1.
Worst case unchanged (still null after 4 attempts). Concurrency caps and control
flow untouched.
- Write all artifacts under .ulpi/design/ (matching .ulpi/plans, .ulpi/issues):
DESIGN.md (locked, project-wide) + <feature>.md (flows, components, handoff).
- New design-system-routing.md: brief->system map (Radix/shadcn, Material 3,
Carbon, Fluent, Polaris, GOV.UK, HIG) + honesty rule; design_system field
locked in DESIGN.md. A design decision delegated to the builder, not code.
- Step 7 is now an explicit build handoff: target agent + chosen system +
acceptance criteria ('implement exactly this; do not redesign'). Reinforced
the boundary: building is delegated to an engineering agent; we own the spec.
- Dropped the optional mock-image line to stay a design-spec skill, not a
generator. Text/ASCII wireframes in the flow/component templates remain.
- Add sections + table rows for browse-qa, review-crate, bugfix-crate,
create-tests-extract, normalize-agent-for-claude, normalize-skill-for-claude,
nodejs, nestjs, docker (53 skills now documented, was 44).
- Rewrite ship-playbook to the current one-pass / seven-gate / no-autonomous-loop
reality (was described as a looping 2-question workflow).
- Rewrite frontend-design-ui-ux to v3.0.0: locked design language, anti-slop,
browse inspiration intake, scored pre-flight, a11y rigor, design-not-code.
- Note the new Available Skills & MCP feature in map-project and map-project-monorepo.
The build and verify phases fanned out (engineers per DAG layer, reviewers per
layer, and dedupVerify's one-verifier-per-finding x up to 2 lenses) bounded only
by the runtime's min(16, cpu-2) cap, enough agents in flight to trip Claude API
rate limits (429s). Added two independent concurrency gates: buildGate
(MAX_BUILD_PARALLEL=4) for the heavy isolation worktree engineers, agentGate
(MAX_PARALLEL=6) for the light read-only reviewers/verifiers. Gated at the leaf
spawns (buildSpawn, taskReviewSpawn, verifyFinding lenses) so the cap holds no
matter how parallel nests; serial fix loop and single agents untouched. Total
agent count unchanged, only concurrency is bounded. Constants tunable (not 1-2).
ship-playbook 1.1.0.
The audit fanned out finders, then one verifier per finding x up to 2 lenses,
plus a critic round, bounded only by the runtime's min(16, cpu-2) cap — enough
agents in flight at once to trip Claude API rate limits (429s). Added a global
concurrency gate (MAX_PARALLEL, default 6) and a gAgent() wrapper; routed every
spawn point (gates, finders, dedup, verify lenses, critic, follow-up finders)
through it. Control flow and parallel() barriers unchanged; total agent count
unchanged (~40-80) — only how many run at once is bounded, so they proceed in
waves. MAX_PARALLEL documented as a tunable (not 1-2). go-live-audit 1.1.0.
Both map skills now discover project-committed skills (.claude/skills/) and
enabled MCP servers (.mcp.json / .claude/settings*.json) and write an inline
'Available Skills & MCP — prefer these' section into CLAUDE.md (root CLAUDE.md
for the monorepo), plus a one-line pointer at the top of the file. Kept inline,
not in a lazy reference, because only always-loaded text changes behavior, so a
fresh agent stops forgetting the repo's own capabilities. New skills-and-mcp.md
reference carries discovery sources, redaction rules (never copy MCP secrets),
and the output format; SKILL.md steps, when-to-load, output-contract sections,
and verification gates updated. map-project 2.1.0, map-project-monorepo 3.1.0.
reviewerBrief told the read-only reviewer to confirm the per-task validate
'actually passes', so it re-ran the test command (vitest) in a read-only
sandbox; the runner's temp-dir mkdir EPERMs, surfacing a false 'tests not
green' CONCERN that re-triggered the fix loop even though integrate already
ran and passed that validate in a writable worktree. Now the reviewer reviews
statically and treats any check it cannot run in the sandbox as an OBSERVATION
(never BLOCK/CONCERN), so the loop converges once the real BLOCK is fixed.
Same guard added to implBrief and auditBrief.
Root cause of "ship-playbook can't run these": all three set disable-model-invocation: true, which blocks
the Skill tool entirely — so neither the ship-playbook orchestrator (Phase 3 map refresh) nor its
workflow subagents (kiro review role) could invoke them, even though they were installed and loaded as
user slash commands. A restart never fixed it; the flag is intentional, not a load-timing issue.
The flag was too blunt: the real intent (per the bodies) is "don't run PROACTIVELY" because they mutate
durable memory (map-project*) or run an external CLI (kiro-review) — but they SHOULD be composable by an
explicit user-invoked workflow like /ship-playbook. Fix: remove disable-model-invocation (keep
user-invocable: true so the slash commands still work), and reword the EXTREMELY-IMPORTANT rule +
Guardrail from "explicit-user-only" to "no proactive invocation, but composable by an explicit
user-invoked workflow". Not changed: claude-review / codex-review stay user-only — ship-playbook does
not compose them (native review is an inline agent, codex review is the codex:codex-rescue plugin).
A live run showed reviewer agents as "nextjs-reviewer"/"go-reviewer" — wrong. The reviewer is the FULL
engineer name + "-reviewer": nextjs-senior-engineer + nextjs-senior-engineer-reviewer, go-senior-engineer
+ go-senior-engineer-reviewer, etc. (matches the registry and the workflow's `reviewer = agent + '-reviewer'`).
The Step 1 examples were ambiguous ("nextjs-senior-engineer(+-reviewer)"), so the instance abbreviated
wrong. Now the skill spells out the full pair names and the install commands
(@nextjs-senior-engineer + @nextjs-senior-engineer-reviewer), and explicitly says never to shorten to
@nextjs-reviewer.
Follow-up to the loaded-list fix: a skill installed to .claude/skills/ is only loaded into the
available-skills list at Claude STARTUP, so "installed on disk" != "usable this session". The previous
"use the loaded list, not disk" rule was right about usability but mislabeled an installed-but-not-yet-
restarted skill as plain "missing" — confusing when the skill IS available on disk.
Now three states:
- ready: in the loaded available-skills list / subagent_type options → usable now, not listed.
- installed, needs restart: not loaded, but present on disk via a SYMLINK-AWARE check
(test -e / ls -laL .claude/skills/<name>, NOT find -type d) → shown with "Restart Claude to load",
no install command.
- missing: not loaded and not on disk → shown with the full install command.
The two tables now list not-ready items with their state+action; the "present/ready" section is
explicitly forbidden (only show what needs action). kiro-cli binary detection is separate (name +
~/.local/bin PATH fallback).
A live run reported installed skills (map-project, kiro-review, go-live-audit, hand-over-to-kiro) as
MISSING. Cause: the dependency check shelled out to `find ... -type d`, which (1) skips symlinks — skills
are registered as symlinks into a shared store (.claude/skills/X -> ../../.agents/skills/X), so
`-type d` misses them — and (2) searched the wrong root (~/.claude instead of the project's
.claude/skills/).
Step 1 now states: a skill is present if it's in your available-skills list; an agent is present if it's
in your Agent subagent_type options. Those loaded lists are the source of truth. Do NOT use find/ls to
detect skills or agents (symlinks + varying roots). Disk/PATH checks are only for external CLIs
(kiro-cli). Added a sanity check: if the method reports an obviously-present skill as missing, the method
is broken — trust the loaded list.
A live run reported "kiro not installed" though kiro-cli 2.6.0 was present. Two reasons, both now
documented in the Step 1 dependency check:
1. Wrong name: it checked bare `kiro`, but the CLI binary is `kiro-cli` (bare `kiro` is usually the
Kiro IDE symlink, not the CLI agent).
2. PATH: kiro-cli is commonly in ~/.local/bin, which the sandboxed Bash PATH may not include, so
`command -v kiro-cli` can return empty even when installed.
Step 1 now says to detect kiro-cli with a location-tolerant check
(`command -v kiro-cli || ls ~/.local/bin/kiro-cli ~/.kiro/bin/kiro-cli /usr/local/bin/kiro-cli`) and
confirm `kiro-cli --version`.
Note (not changed here): kiro-review and hand-over-to-kiro already use the correct `kiro-cli` name but
`which kiro-cli` / `kiro-cli --version` are still PATH-blind — worth the same location fallback later.
A live run inserted its own scope/feature-clarification questions (full platform? which SSO protocol?)
BEFORE the workflow gate questions, which is not what the user expects. The skill didn't forbid that
improvisation. Now the intake order is FIXED and binding:
1. Dependency check + continue/restart (Step 1)
2. The SEVEN gate questions (Step 2) — ask these FIRST, immediately after the dep check; NEVER precede
them with scope/feature questions ("full platform or just the API?", "which protocol?"). The prompt
IS the scope.
3. Scope grounding + project facts + git preflight (Step 3) — narrow a broad prompt HERE (after the
gates), inferring from the repo or letting the plan phase challenge scope; fold the result into the
prompt arg.
Reinforced in EXTREMELY-IMPORTANT rule 2, Step 2 intro, and the new Step 3 scope-grounding paragraph.
- planHarness: NEW role — who WRITES the plan (native|codex|kiro). The plan agent now routes per
planHarness (was always native). Added to args, reviewConfig, and the intake as question 1.
- Intake reordered to true execution order: who writes the plan, who reviews the plan, who writes the
code, who reviews the code, impl review, go-live audit, map project (7 questions).
- Option ordering rule: native first (Recommended), then codex/kiro, skip as the LAST provided option
(renders second-to-last, before the auto-added "Other"). Plan/code writing are not skippable.
- Dependency check (Phase 1 Step 1): now TWO separate tables, missing-only, with FULL install commands —
skills via "npx skills add https://github.com/ulpi-io/skills --skill <name>"; agents via
"npx agentshq add ulpi-io/agents@<agent-name>". Omit a table if nothing is missing.
- map-project clarified end to end: held as mapRefresh, explicitly NOT a Workflow arg (the skill runs
it in Phase 3 after the build, since it regenerates CLAUDE.md from finished code). Noted at Q7, at the
args list, and in success criteria.
Verified: workflow reads + consumes all 6 workflow args (planHarness/planReview/buildHarness/taskReview/
implReview/goLive); SKILL asks 7 questions and passes the 5 role args + goLive; map runs skill-side.
JS syntax OK; no dangling global-harness refs.
Two changes:
1. INDEPENDENT roles (replaces the single global harness). Code writing and each review now pick their
own executor — native | codex | kiro — fully independently, so writing and reviewing can use
DIFFERENT harnesses (e.g. write codex, review kiro). Per-task review and impl review are kept
separate (each its own pick, each skippable). Args:
- buildHarness: native|codex|kiro (who writes each task + fixes)
- planReview / taskReview / implReview: skip|native|codex|kiro (each its own reviewer)
Removed the global arg + claude+harness/useHarness/reviewBoth; reviews are now single-
reviewer per gate via a routeReview() helper + planReviewLoop(plan, who). Audit composes the native
go-live-audit (dropped the separate harness audit lane).
2. NEW first intake step — dependency check & setup. Before the gate questions, the skill detects the
stack and shows what would help (composed skills plan-to-task-list-with-dag/plan-founder-review/
go-live-audit/map-project; the stack's *-senior-engineer +-reviewer agents; stack skills; optional
codex/kiro tooling) as present/missing with install commands, then offers continue-now or
install-and-restart (new skills/agents load only at startup). Only offers codex/kiro for roles whose
tooling is installed.
Reconciled across SKILL.md (intake reorder to 6 questions, rules 1/2/3/5/6, description, goal, step map,
Phase 2 args + step descriptions, Phase 3, guardrails, success criteria), workflow-template.js (config,
routing, return reviewConfig/noReviewGate), harness-routing.md (per-role routing matrices),
playbook-state.md. JS syntax verified; no global-harness remnants.
The skill ran every gate every time and burned tokens. Each expensive gate is now independently
dialable from full-rigor down to skip; defaults are lighter (but the user can still go full swing).
1. Reorder intake to execution order (7 questions, two AskUserQuestion calls): second harness → plan
review → build handoff → per-task review → impl review → go-live audit → map refresh.
2. Per-task review skippable — taskReview ∈ skip|native|harness. 'skip' removes the per-task reviewer
AND the fix loop (biggest save); task passes on engineer validate.
3. Final impl review skippable — implReview ∈ skip|claude|claude+harness.
4. Plan/founder review — planReview ∈ skip|claude|claude+harness.
+ harness is now ONE global external review harness (none|codex|kiro) used by any claude+harness gate
or taskReview=harness; buildHarness (who writes) stays a separate axis.
+ MUSTs preserved: full-swing config (everything + harness + goLive) and delegating build AND
per-task review to codex/kiro both supported.
+ Guardrail: warn (not block) when taskReview AND implReview are both skipped — nothing checks the
build; return surfaces noReviewGate + reviewConfig and Phase 3 caveats the verdict.
5. Worktrees: integrate now removes each merged worktree IMMEDIATELY (per-merge, not batch); preflight
and final dangling-sweep kept.
Reconciled across SKILL.md (intake, rules 2/3/5/6, Phase 2 steps, Phase 3, success criteria, output
contract, description), workflow-template.js (config, gating, handoff routing, return), harness-routing.md,
playbook-state.md, build-loop.md. Removed MAX_ROUNDS/maxRounds remnants. JS syntax verified.
Diagnosed in a live run: build agents run with isolation:'worktree', leaving full repo checkouts under
.claude/worktrees/. They are not auto-removed (committed) and pile up across runs (~35 observed). Since
tsc/eslint . don't respect .gitignore, the integrate validate walked every sibling worktree and failed
on pollution regardless of the task code — the per-task fix loop could never win, so it ground to
escalation. The features were fine; the gate was measuring leftover worktrees.
Fix (config/hygiene, no feature-code change):
- cleanupWorktrees(): a Bash agent that removes worktrees under .claude/worktrees/ or on wf/build/*
branches (incl. prior runs' leftovers) and . Never touches the main checkout.
Runs at preflight (clear prior leftovers before building) and at end (clear this run's).
- integrateBrief: after merging, remove the just-merged build worktrees + prune BEFORE validating, and
scope the validate so it never scans .claude/worktrees/** or sibling agent dirs (.factory/.gemini/
.opencode/.trae/.vibe) — without relaxing any real lint/type rule.
- Docs: build-loop.md 'Merge the layer' + task-exit, SKILL guardrail ('if the gate fails on files
outside the task scope, suspect leftover worktrees first').
Per design decision: the recursion loop was re-entering the playbook at plan review (wrong kind of
review) and auto-looping for hours. The workflow now runs ONE forward pass and stops:
Plan → Plan review (bounded, once) → Build → Impl review (plan-vs-implementation) → Verify → Audit
and RETURNS the verified findings as openRegister. No automatic re-plan/re-build. When findings remain,
the skill presents them and the USER decides: run a fix round (re-invoke with findings as the prompt),
hand-fix, or accept-with-risk. Impl review is the holistic plan-vs-implementation gate; Verify (former
'Recurse') just dedups + adversarially verifies and reports; Audit runs only when build+impl are
verified-clean.
Code: removed the for-round loop, MAX_ROUNDS, recursion PROMPT reassignment, history; dropped the round
param from planBrief/nativeReviewLoop/harnessReviewLoop/buildPlan; meta phase 'Recurse' → 'Verify';
return shape now { converged, ranReal, plan, build, openRegister, missingAgents, harness, goLive }.
Docs: SKILL.md (description, goal, step map, rules 1 & 7, Phase 2 step list, Phase 3 feedback flow,
guardrails, success criteria, When-To-Load), playbook-state.md (single-pass state machine, clean-vs-
feedback, user-driven fix rounds), harness-routing.md (Phase C bounded / F+G once) all reconciled.
Two issues from a live run that ground through plan-review r1.0→r1.3:
1. Plan review looped to MAX_REVIEW with two strong reviewers (Opus ∥ codex) surfacing fresh CONCERNs
each pass. OBSERVATIONs already never blocked, but CONCERNs churned. Fixes:
- MAX_REVIEW 4 → 2 (a plan is not code).
- Non-convergence early-exit: both plan-review loops now stop the moment a fix round fails to REDUCE
the BLOCK/CONCERN count (blockingCount + prev tracking), so reviewers can't grind a plan.
2. The display 'skipped step 2 (Plan review) → step 3 (Harness plan review)'. That was the B1 merge
(native review runs inside the harness loop), but the two phase labels made it look like a skip.
Collapsed to ONE 'Plan review' phase: meta.phases drops 'Harness plan review'; both loops + the
round-loop tag agents as phase:'Plan review'; SKILL.md step map (steps 4–9 → Plan review) and
playbook-state.md (states C+D → one C) updated to match.
New skill: delegate an implementation task to the Kiro CLI — the write-side counterpart to kiro-review.
Original by Sabeur Thabti (@thabti, https://github.com/thabti/hand-over-to-kiro), MIT; LICENSE preserved,
credited in SKILL.md / README / harness-routing.md. Normalized to our conventions (frontmatter, semver,
<EXTREMELY-IMPORTANT>, phased Step 0-5 with success criteria, Guardrails, Output Contract) and aligned
to skill-security-rules.md (install is docs-only, never curl|bash; rephrase + boundary tags; trust opt-in).
ship-playbook: buildHarness=kiro now routes through /hand-over-to-kiro (was an ad-hoc CLI brief);
Phase 1 notifies the user if a chosen codex/kiro build/review handoff's tooling is missing; README +
harness-routing.md updated with the handoff matrix and credit.
Adversarial ingest-review (15 agents) found and fixed:
- TRUST CONTRADICTION (real): --no-interactive has no UI to approve tools, so the carried-over
'no trust by default' meant a kiro build would no-op. Reframed: a trust flag is REQUIRED; prefer
scoped --trust-tools, use --trust-all-tools for autonomous; ship-playbook's headless kiro build now
explicitly requests --trust-all-tools.
- Verification widened: git diff --stat HEAD + git status --short + git log baseline..HEAD (bare
git diff missed staged/committed work).
- Injection rationale corrected (quoted substitution + literal heredoc + rephrasing are the defense),
temp-file trap-cleanup on timeout/error, collision-safe heredoc sentinel, stale build comment fixed.
Rejected: adding disable-model-invocation — it would block ship-playbook's Skill-tool invocation and
the skill's trigger-phrase design (it must stay model-invocable).
Two new intake selectors, independent of the plan/impl cross-review harness:
- buildHarness (who WRITES each task): native | codex | kiro
- taskReviewHarness (who REVIEWS each task): native | codex | kiro
Wiring (workflow-template.js): buildSpawn()/taskReviewSpawn() route each task's engineer and reviewer:
- native → the plan's specialist agent (resolveAgent + missingAgents reporting, unchanged)
- codex → codex:codex-rescue (write-capable for build, read-only brief for review)
- kiro → Kiro CLI: kiro-review skill for review; direct CLI for build with self-implementation fallback
All four build call sites (engineer, parallel initial review, fix, loop review) go through the routers.
SKILL.md: intake asks both (one/two AskUserQuestion calls, up to 4 each); launch args carry
buildHarness/taskReviewHarness; EXTREMELY-IMPORTANT rules 3/4, the Build step, and success criteria
reconciled. harness-routing.md: new 'Per-task build & review handoff' matrix documenting the routing.
Speed (each safe — removes redundant work or parallelizes read-only work):
- #1 Drop the redundant native plan-review: harnessReviewLoop already runs native ∥ harness, so the
separate nativeReviewLoop ran native twice (up to 2×MAX_REVIEW). Now one loop — native-only when no
harness, else the native∥harness loop.
- #2 Parallelize per-task build reviews: the initial reviews are read-only, so they run in parallel
across the layer; the fix→re-integrate stays SERIAL (it git-merges the shared working branch —
concurrent merges would race). Biggest build-phase win.
- #3 Gate the go-live audit (heaviest phase) to run only on a round whose build+impl are VERIFIED-clean
(post-dedupVerify, not raw findings) — a round that recurses on build/impl findings no longer wastes
the audit; a round whose findings all get refuted still gets audited and can converge.
- #4 (effort tiers) intentionally NOT applied — no per-agent effort knob; cheaper model on fix agents
is a real quality risk.
Reliability + UX:
- #5 Resume note: re-pass the SAME args object on resume — resumeFromRunId reuses cached agent results
but the script re-runs from the top, so omitting args empties CFG and hits the FILL: hard-throw.
- New: optional project-map refresh — Phase-1 intake asks (map-project / map-project-monorepo / no,
defaulted by repo layout); Phase 3 runs the chosen skill last on a real (ranReal) run so CLAUDE.md
reflects the build. build-loop.md reconciled to the consented general-purpose fallback.