j.do
Skill0 starsChanged 22 days agoApache-2.0View on GitHub
Execute tasks from the scrum board. Reads from project/todo.md, resolves each entry to its full scrum board context, and drives the developer agent through implementation with the correct sender object and communication contract. Loops until all selected tasks are done or the user exits.
- Deletes or force-pushes
In this repository151 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- j.brainstormSkill
- j.breakSkill
- j.btwSkill
- j.check-inSkill
- j.clearifySkill
- j.close-storySkill
- j.cloud-connectSkill
- j.commitSkill
- j.connectSkill
- j.continueSkill
- j.conventionsSkill
- j.dashboard-shareSkill
- j.dashboardSkill
- j.deep-diveSkill
- j.distributeSkill
- j.docSkill
- j.doc-syncSkill
- j.doooSkill
- j.jenga-configSkill
- j.jenga-permission-levelSkill
- j.jengaSkill
- j.errorSkill
- j.evaluateSkill
- j.examplifySkill
- j.gitignoreSkill
- j.helpSkill
- j.ideaSkill
- j.improveSkill
- j.initSkill
- j.jbpSkill
- j.lgtmSkill
- j.pi-planSkill
- j.playbook-newSkill
- j.playbookSkill
- j.proceedSkill
- j.publishSkill
- j.reconcile-originSkill
- j.reconcileSkill
- j.redoSkill
- j.skillifySkill
- j.smoke-testSkill
- j.spinoffSkill
- j.statusSkill
- j.testSkill
- j.todoSkill
- j.toolsSkill
- j.unchartedSkill
- j.wtfSkill
- skill-nameSkill
- j.brainstormSkill
- j.breakSkill
- j.btwSkill
- j.check-inSkill
- j.clearifySkill
- j.close-storySkill
- j.cloud-connectSkill
- j.commitSkill
- j.connectSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
96 rules. About 0 are common, 25 are distinctive, and 71 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈25
Distinctive · estimate
This author's own choices. Worth a look.
71
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 47
If the file does not exist, emit:
- Distinctive: line 53
If the file is not valid JSON, emit:
- Distinctive: line 84
Do not read the flag file yourself.
- Very specific: line 87
Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" …
- Very specific: line 91
When /do is invoked with a story ID (e.g., /do E##_S##), skip steps 2–4 and enter story-bundle execution mode:
- Distinctive: line 103
Before proceeding, acquire the epic-level sequential lock for this bundle:
- Very specific: line 111
Read bundle_lock_ttl_minutes from project/configs/scope-thresholds.json (already loaded in step 0).
- Very specific: line 112
If age_minutes < bundle_lock_ttl_minutes (lock is non-stale), emit:
- Very specific: line 117
If age_minutes >= bundle_lock_ttl_minutes (lock is stale), delete it with rm -f project/queue/epic-lock-<E##>.json and continue to the …
- Very specific: line 118
If project/queue/epic-lock-<E##>.json does not exist, continue to the write step below.
- Very specific: line 129
Write this JSON to project/queue/epic-lock-<E##>.json.tmp (temporary file in the same directory).
- Distinctive: line 138
After acquiring the epic lock and before writing the bundle manifest, scan all other active bundle manifests to detect file-level overlap …
- Very specific: line 168
If multiple other bundles each have overlap, accumulate all of them under their respective story IDs in pending_conflicts.
- Very specific: line 170
Pass
pending_conflictsforward: when writing the bundle manifest in the next step (step 2 below), initialise the cross_bundle_conflicts … - Very specific: line 187
mode: freshwith anall_terminalnote: every task is already Passed/Merged and there is no manifest to resume, so running would redo … - Very specific: line 188
mode: fresh: no manifest exists. Follow the existing path unchanged: capture anchor_sha, write the manifest as described in "### Bundle … - Very specific: line 209
Check for an existing lock file at project/board/tasks/<task_id>_*.md.lock.
- Very specific: line 210
Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.
- Very specific: line 211
Update the task frontmatter fields status: In Progress and date_started: <YYYY-MM-DD> (today's date).
- Distinctive: line 212
Delete the lock file immediately after the write completes (before step b).
- Very specific: line 217
Check for an existing lock file at project/board/tasks/<task_id>_*.md.lock.
- Distinctive: line 220
Delete the lock file immediately after the write completes.
- Very specific: line 234
Run git diff --name-only HEAD~1 HEAD to obtain the list of files changed by the just-completed task.
- Very specific: line 235
Read the bundle manifest at project/queue/bundle-<E##_S##>.json.
- Very specific: line 236
Add or update the key task_changed_files in the manifest object.
- Very specific: line 237
Write the updated manifest atomically: a.
- Very specific: line 293
Leave the shared bundle-<E##_S##> worktree and its commits untouched.
- Very specific: line 302
Update the task frontmatter field status: Failed.
- Very specific: line 321
Run git rev-parse HEAD to capture the current HEAD SHA as anchor_sha.
- Very specific: line 322
Write project/queue/bundle-<E##_S##>.json with the following structure:
- Distinctive: line 342
Do not proceed to the first task until this file is written successfully.
- Very specific: line 348
Save partial diff: run git diff <anchor_sha> HEAD and write the output to project/queue/bundle-<E##_S##>-partial.patch.
- Distinctive: line 349
Verify clean worktree: run git status --porcelain.
- Very specific: line 354
Reset to anchor: run git reset --hard <anchor_sha>.
- Very specific: line 355
Delete bundle manifest: remove project/queue/bundle-<E##_S##>.json.
- Distinctive: line 357
After completing the failure path, halt bundle execution.
- Distinctive: line 363
No patch file is written on success.
- Very specific: line 365
If /do is invoked with a task ID (E##_S##_T##) or a plain-text title, skip this section and use the normal task execution path (steps 2–8 …
- Very specific: line 368
Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" …
- Distinctive: line 375
Before resolving a task for execution, inspect the selected entry's ID:
- Very specific: line 378
Use the scrum-master agent to read the epic's board file ($(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || …
- Very specific: line 379
Write a story file to $(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo …
- Very specific: line 381
After breakdown, Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo …
- Very specific: line 384
Check $(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo …
- Very specific: line 385
Write a task file to $(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo …
- Very specific: line 386
After breakdown, keep the story entry in project/todo.md (tasks are discovered from it automatically).
- Distinctive: line 395
If the file does not exist, warn the user and skip — do not proceed with a task that has no scrum board definition
- Distinctive: line 400
Before invoking the developer, check whether this task was manually scoped by a human operator.
- Very specific: line 402
Read jenga_assigned from the resolved task's frontmatter.
- Very specific: line 403
If jenga_assigned is true or the field is absent — proceed to step 4.1.2 (the pre-task gate) without any further override check.
- Very specific: line 404
If jenga_assigned is false: a.
- Very specific: line 410
Do not invoke the developer.
- Very specific: line 425
Position: After 4.1 (read-only), before 4.1.5, before the locked-task guard in 4.2, and before every worktree creation and every ### 4.4 …
- Very specific: line 430
Follow "Calling the checker from a skill" in that document: mint a run id for this unit (do-<UTC time>-<rand>), call checklist.sh check …
- Very specific: line 443
Start nothing: no worktree, no slot, no subagent.
- Very specific: line 478
After override validation (step 4.1) and the pre-task gate (step 4.1.2), and before branching on execution_scope in step 4.2, check whether …
- Very specific: line 480
Detect the flag: If the task was invoked as /do <id> with no --trivial flag, skip this entire section and proceed directly to ### 4.2.
- Very specific: line 509
After resolving the task context (step 4), passing override validation (step 4.1), passing the pre-task gate (step 4.1.2, which has already …
- Very specific: line 511
Locked-task dispatch guard (defense-in-depth): Before branching on execution_scope below, read crucial_level from the task frontmatter (per
- Very specific: line 514
If execution_scope is already inline, proceed directly to the inline steps below — no correction needed.
- Very specific: line 515
If execution_scope is anything other than inline (absent, task, story, or epic), auto-correct it to inline in the task frontmatter now, and …
- Distinctive: line 529
Read the task file and load its full content (description, acceptance criteria).
- Distinctive: line 537
Run the smoke test harness before committing anything:
- Very specific: line 538
Run bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo …
- Very specific: line 539
If neither scripts/smoke-harness.sh nor node_modules/@jenga-ai/agent/scripts/smoke-harness.sh exists, log a warning and treat the result as …
- Very specific: line 546
Write status: Failed to the task's frontmatter, then record it (best-effort, see step 7):
- Distinctive: line 551
Do not commit. Do not proceed to the next task.
- Very specific: line 553
Commit the changes using the standard commit convention (task(<task_id>): <short description>) via /commit in inline mode (E32_S04_T03).
- Very specific: line 554
Run the Intent-vs-Diff Check (see ### 5.1.
- Very specific: line 556
Write status: Passed and date_completed: <today> to the task's frontmatter if verification passes.
- Very specific: line 557
Remove the task from project/todo.md.
- Distinctive: line 562
If the implementation cannot be completed inline (scope is larger than anticipated — detected scope creep mid-run):
- Very specific: line 593
Run bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo …
- Very specific: line 601
Commit the changes using the standard commit convention (task(<task_id>): <short description>) via /commit.
- Very specific: line 603
Write status: Passed and date_completed: <today> to the task's frontmatter if self-verification passes.
- Very specific: line 607
If the smoke test fails (non-zero exit): do NOT write status: Failed and do NOT halt.
- Distinctive: line 646
Acquire: Before spawning the developer subagent (or the bundle's shared developer subagent), run:
- Very specific: line 685
Escalation: If the incremented count has reached 3: append one line to project/queue/scrum_triggers.jsonl, matching the existing trigger …
- Very specific: line 704
Agent invocation: Use Agent tool with:
- Very specific: line 719
Create a worktree named <E##_S##_T##-short-slug>
- Very specific: line 735
After the developer agent returns (or after inline execution completes), run the following check if the task has needs_docs: false in its …
- Very specific: line 739
Read needs_docs from the task's frontmatter.
- Very specific: line 741
Run git diff --name-only HEAD~1 HEAD to retrieve the list of changed file names (relative paths, one per line).
- Very specific: line 743
Read the prompt template from skills/j-do/assets/intent-vs-diff-prompt.md.
- Very specific: line 753
If parsing fails (malformed JSON): treat as [], emit: [intent-vs-diff] LLM response was not valid JSON.
- Very specific: line 754
If the array is empty ([]): no action — do not modify the task frontmatter.
- Very specific: line 755
If the array is non-empty: write divergence_flag: true to the task's frontmatter, then emit:
- Distinctive: line 768
After the developer completes the task, confirm the following documentation was written:
- Distinctive: line 772
If either file is missing, ask the developer to produce it before continuing.
- Very specific: line 782
Check for any _INSTRUCTIONS.md files in project/instructions/ whose ID matches the completed task.
- Very specific: line 784
Run the Task-Completion Cleanup Procedure (### 7.1 below) with this task's title, to remove the completed task from project/todo.md and …
- Distinctive: line 794
Remove the entry: Run:
- Distinctive: line 798
Tear down if empty: Run:
- Distinctive: line 820
Never read the flag file here.
- Very specific: line 828
Scoped rapport: only when the record says with_status=true.
- Distinctive: line 832
When there is no break, go back to step 1.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Do — Execute Scrum Board Tasks
skills/j-do/ is the canonical, hand-edited directory for this skill, per CLAUDE.md's "The
Canonical Naming Contract" (the E50 reopening of 2026-09-09, which promoted skills/j-<name>/
from generated twin to sole canonical form). The j- prefix is there for collision safety — a real
directory under a distinct name, so a host tool shipping its own same-named built-in command cannot
shadow it (Claude Code's native skill resolution is a literal-string, directory-name-based match; see
project/documentation/skill-authoring.md's "Invocation Convention").
⚠️
scripts/generate-j-alias.shwas retired byE50_S14and no longer exists — there is nothing to run. This file was previously generated from a bareskills/do/SKILL.mdsource;E50_S15deleted that directory. This file is now the sole canonical, hand-edited source for this skill — edit it directly.
--trivial Flag
Syntax: /do <id> --trivial — a dispatch-time override, distinct from /todo --trivial (a creation-time flag documented in skills/j-todo/SKILL.md). Where /todo --trivial writes a brand-new task straight to execution_scope: inline, /do <id> --trivial overrides an already-existing task's execution_scope — whatever it currently is, including absent (legacy tasks with no execution-scope fields at all) — to inline at the moment it's dispatched. See ### 4.1.5. \--trivial` Dispatch-Time Override` below for the full mechanics.
No softer fallback tier — hard fallback to the full pipeline instead. Unlike light scope (which sits between inline and task), --trivial always forces inline directly, with no intermediate tier to fall back to first. To compensate, a --trivial-forced run that fails scripts/smoke-harness.sh or shows detected scope creep mid-run automatically re-routes to the full task pipeline (worktree + developer + tester) via the same #### Fallback to Full Task-Scope Pipeline procedure the light-tier fallback uses — see ### 4.2's failure-handling branches and the shared Fallback subsection under ### 4.3.
Human-only, same as /todo --trivial. --trivial is invoked by a human typing /do <id> --trivial — it is never applied autonomously by /jenga or the scrum-master's own breakdown/dispatch passes.
Instructions
0. Load threshold config
Read project/configs/scope-thresholds.json.
If the file does not exist, emit:
ERROR: project/configs/scope-thresholds.json not found. Cannot proceed.and halt. Do not fall back to any default values.
If the file is not valid JSON, emit:
ERROR: project/configs/scope-thresholds.json is malformed (invalid JSON). Cannot proceed.and halt.
Extract the following named values for use throughout this skill:
inline_max_files— maximum files a task may touch to qualify for inline execution scopeinline_max_lines— maximum total lines changed for inline scopestory_max_files— maximum files a task may touch to qualify for story-scope bundlingbundle_lock_ttl_minutes— time-to-live in minutes for a story-scope bundle lock
These values must be read fresh on each invocation. Never use hardcoded fallbacks.
Break entry check (E44_S02)
A hard break (/break hard) must stop a run that starts after it was set; the checkpoints in step 8 and in
the bundle loop only fire between tasks, so they would let a freshly started run do one task first. Check once,
here, without consuming anything:
bash "$([ -f skills/j-break/scripts/check-break.sh ] && echo skills/j-break/scripts/check-break.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/check-break.sh)" --peek- Exit
0, or amode=softrecord: continue. A soft break armed before the run starts is deliberately left in place; it is honoured at the first task boundary (step 8), which is what "stop after the next task" means. - A
mode=hardrecord (exit10): halt before dispatching anything, using the halt report in step 8, with "finished task" given as "none — nothing was started". - Any other exit code is the checker itself failing, not a stop request: print its stderr as a warning and continue.
Do not read the flag file yourself. Branch on the exit code and use the record the script prints.
1. Check for project/todo.md
Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" exists. If it exits non-zero, inform the user there are no queued tasks and exit.
1.5. Story-Bundle Execution Mode
When /do is invoked with a story ID (e.g., /do E##_S##), skip steps 2–4 and enter story-bundle execution mode:
- Read the story file from
project/board/stories/<E##_S##>_*.md. Extract thetasks:list. If the story file does not exist, emit:
and halt.ERROR: Story file for <story_id> not found. Cannot execute bundle.
Pre-task gate — once per bundle dispatch. This mode skips steps 2–4, so it does not reach ### 4.1.2 by falling through; run that gate here instead, once, with the story as the unit (<id> = <E##_S##>) and never once per task inside the bundle. It runs before the epic lock below is acquired, so a halt or deferral leaves no lock, manifest or slot to clean up and no task status touched. Pass any remind items to the shared subagent in step 3 alongside the task context.
Epic-Level Bundle Lock
Before proceeding, acquire the epic-level sequential lock for this bundle:
a. Derive the lock file path: project/queue/epic-lock-<E##>.json where <E##> is the epic ID extracted from the story ID.
b. Check for an existing lock:
- If
project/queue/epic-lock-<E##>.jsonexists:- Parse the JSON and read the
started_atfield (ISO 8601 UTC timestamp). - Compute the lock age:
age_minutes = (now_utc - started_at) / 60. - Read
bundle_lock_ttl_minutesfromproject/configs/scope-thresholds.json(already loaded in step 0). - If
age_minutes < bundle_lock_ttl_minutes(lock is non-stale), emit:
and halt — do not proceed with this bundle.BLOCKED: Epic <E##> already has a running bundle (<bundle_story_id>, started <started_at>). Wait for it to complete or for the TTL to expire. - If
age_minutes >= bundle_lock_ttl_minutes(lock is stale), delete it withrm -f project/queue/epic-lock-<E##>.jsonand continue to the write step below.
- Parse the JSON and read the
- If
project/queue/epic-lock-<E##>.jsondoes not exist, continue to the write step below.
c. Write the lock atomically:
- Compose the lock JSON:
{ "epic_id": "<E##>", "bundle_story_id": "<E##_S##>", "started_at": "<current ISO 8601 UTC timestamp>" } - Write this JSON to
project/queue/epic-lock-<E##>.json.tmp(temporary file in the same directory). - Rename (move)
project/queue/epic-lock-<E##>.json.tmptoproject/queue/epic-lock-<E##>.json. This rename is atomic on POSIX filesystems and prevents any observer from reading a partially-written lock file.
Note: Each epic has its own lock file keyed by
<E##>. Bundles belonging to different epics have separate lock files and do not block each other.
Cleanup contract: The epic lock file must be deleted on every exit path — success, failure, and interruption. Register a cleanup/trap handler immediately after writing the lock so that the lock is released even if the skill is interrupted mid-execution (e.g.,
trap 'rm -f project/queue/epic-lock-<E##>.json' EXITin a shell implementation, or equivalent in other runtimes). Deleting a non-existent lock file must be idempotent — always userm -f(neverrmalone).
Pre-execution cross-bundle conflict check
After acquiring the epic lock and before writing the bundle manifest, scan all other active bundle manifests to detect file-level overlap with the current bundle:
- Glob other bundle manifests: list all files matching
project/queue/bundle-*.json. Exclude the current bundle's own file (project/queue/bundle-<E##_S##>.json— it does not exist yet on a fresh bundle and already exists on a resume (step 2 decides which); either way, simply exclude any path whose basename equalsbundle-<E##_S##>.json). - If no other manifests exist: skip steps 3–6 entirely — this step completes silently.
- For each other manifest file found:
a. Parse the JSON and read its
task_changed_filesmap. b. Collect the union of all file-path arrays across every key intask_changed_filesinto a flat, deduplicated set calledother_touched_files. c. Record the other bundle'sstory_id(or derive it from the filename:bundle-<story_id>.json). - Build the expected file set for the current bundle (
current_expected_files): For each task in the current bundle'stasks:list: a. Read the task file atproject/board/tasks/<task_id>_*.md. b. Extract file paths from the task'sscope_rationalefrontmatter field and from the full text of the task's## Descriptionsection. A string is treated as a file path if it contains a forward-slash (/) or a dot-separated extension (e.g..md,.json,.sh,.ts,.js,.py). Extract all such tokens. c. Add all extracted paths tocurrent_expected_files(deduplicated set). - Compute overlap: for each other bundle scanned in step 3, compute the intersection of
other_touched_filesandcurrent_expected_files. - If any overlap is found:
a. Emit the following log line to the console (one line per conflicting bundle):
b. Store the conflict data in memory as[CROSS-BUNDLE CONFLICT] Bundle <current_story_id> and bundle <other_story_id> share expected files: <file1>, <file2>pending_conflicts:
If multiple other bundles each have overlap, accumulate all of them under their respective story IDs in{ "<other_story_id>": ["<file1>", "<file2>"] }pending_conflicts. - Pass
pending_conflictsforward: when writing the bundle manifest in the next step (step 2 below), initialise thecross_bundle_conflictskey in the manifest with the contents ofpending_conflicts(or{}if no conflicts were found). See "### Bundle Manifest and Rollback Anchor" for the updated manifest structure.
This check is non-blocking. Regardless of whether conflicts are found, execution always continues to step 2. The conflict data is recorded for later review only.
Decide fresh versus resume, then invoke the rollback anchor (
E44_S08). Before anything is written, run the read-only helper:bash "$([ -f skills/j-do/scripts/bundle-resume.sh ] && echo skills/j-do/scripts/bundle-resume.sh || echo node_modules/@jenga-ai/agent/skills/j-do/scripts/bundle-resume.sh)" <E##_S##>It prints one JSON object
{mode, story_id, anchor_sha, run, skipped, notes}. Branch on its exit code andmode:- Exit
3(manifest refused: unparseable, wrong story, badanchor_sha, oranchor_shanot an ancestor of HEAD): nothing was changed. Report the stderr cause verbatim and ask the operator:- Discard the stale manifest and start fresh (
rm -f project/queue/bundle-<E##_S##>.json, then re-run this step) - Abort
- Other (describe below)
- Discard the stale manifest and start fresh (
- Exit
4: a story or task file is missing. Report it and stop. mode: freshwith anall_terminalnote: every task is alreadyPassed/Mergedand there is no manifest to resume, so running would redo finished work. Do not start the bundle; report that the story has nothing left to run and stop.mode: fresh: no manifest exists. Follow the existing path unchanged: captureanchor_sha, write the manifest as described in "### Bundle Manifest and Rollback Anchor" below, and iterate the wholetasks:list.mode: resume: a manifest survived a halt (the failure and success paths both delete it), and itsanchor_shais still an ancestor of HEAD. Do not rungit rev-parse HEAD, do not capture a newanchor_shaand do not write the manifest from scratch: the originalanchor_shaandtask_changed_filesare the rollback anchor and the record of what completed. Append the current UTC timestamp to aresumed_atarray in the existing manifest using the atomic tmp-rename pattern of step c.1, leaving every other key as it is. The pre-task gate, epic lock, cross-bundle conflict check and developer slot acquisition (step 3) run exactly as for a fresh bundle, since a halt released both. Step 4 iterates only the helper'srunlist;skippedtasks are alreadyPassed. Board status is authoritative, never the manifest. Show the operatorskipped,runand anynotesbefore starting.- Empty
run(resume only): every task is already done and the halt landed after the last one. Spawn no developer for task work: skip step 4 and go straight to step 5 (post-bundle verification) and step 6 (cleanup, which deletes the manifest).
- Exit
Acquire a developer concurrency slot, then spawn one developer subagent in one shared worktree named
bundle-<E##_S##>. This is the only developer subagent for the entire story bundle — do not spawn additional subagents per task, and correspondingly this bundle acquires exactly one developer slot for its entire lifetime, never one per task inside it.a. Acquire. Follow
### 4.4. Developer Concurrency Slot Enforcementbelow, with<id>= this bundle's story id (<E##_S##>). b. On a full cap (capacity_blockedoutcome): do not spawn the shared subagent. Undo the bundle setup already performed above so no orphaned state survives this aborted attempt: delete the bundle manifest (rm -f project/queue/bundle-<E##_S##>.json) and release the epic lock (rm -f project/queue/epic-lock-<E##>.json) — the same two cleanup actions the failure path below performs, since from the epic lock's perspective an aborted-before-it-started bundle is indistinguishable from a failed one. None of the bundle's tasks have had their status touched yet (the per-taskstatus: In Progresswrite in step 4a below hasn't run), so there is nothing to revert toPending— they are still whatever they were before this bundle attempt (normallyPendingalready). Stop here;/jengaPhase 4's normal Pending-rescan picks the story back up on a later wave once a slot frees up. c. On success: invoke the developer Agent tool with:subagent_type: "developer"name: "<E##_S##>"← Load-bearing identifier: this name is consumed byj.check-in's liveness probe to join running agents to board items viaListAgentsoutput. For bundle execution, use the story ID. Must stay stable — this is not cosmetic.- Sender object and context payload (full task/story files, parent epic summary, pre-flight reminders)
Release this slot exactly once, when the entire bundle concludes — step 6 (success) or step 4d (failure) below — never per individual task inside the bundle.
Execute tasks sequentially in
tasks:list order, within the same shared developer subagent context:For each task in the
tasks:list:a. Before the task begins — write
status: In Progressanddate_started: <today>to the task's frontmatter using the file-locking protocol:- Locate the task file:
project/board/tasks/<task_id>_*.md. - Check for an existing lock file at
project/board/tasks/<task_id>_*.md.lock. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked after the retry, log a warning and skip this status write (do not block task execution). - Create the lock file: write the current ISO 8601 timestamp into
project/board/tasks/<task_id>_*.md.lock. - Update the task frontmatter fields
status: In Progressanddate_started: <YYYY-MM-DD>(today's date). - Delete the lock file immediately after the write completes (before step b).
b. Pass task context to the shared developer subagent: task file content (title, description, acceptance criteria), parent story file, and parent epic file.
c. After successful task: first run the Intent-vs-Diff Check (see
### 5.1. Intent-vs-Diff Checkbelow) for this task. Then writestatus: Passedanddate_completed: <today>to the task's frontmatter using the file-locking protocol:- Check for an existing lock file at
project/board/tasks/<task_id>_*.md.lock. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked, log a warning and skip this status write. - Create the lock file: write the current ISO 8601 timestamp into
project/board/tasks/<task_id>_*.md.lock. - Update the task frontmatter fields
status: Passedanddate_completed: <YYYY-MM-DD>(today's date). - Delete the lock file immediately after the write completes.
The lock must be released before proceeding to task N+1. Never hold a lock across task execution.
Record it in the execution ledger (
E44_S09), immediately after thePassedwrite and before c.1, c.2 and the c.3 break checkpoint, so a halt can never precede its own record:bash "$([ -f skills/j-break/scripts/record-execution.sh ] && echo skills/j-break/scripts/record-execution.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/record-execution.sh)" <task_id> Passed --source do-bundleThis call is best-effort and never blocks or fails a task:
record-execution.shwarns on stderr and exits0on any ledger problem, and only a usage error is non-zero. It appends one line to the gitignoredproject/queue/execution-ledger.jsonl, which/break statusreads to tell "ran this session" from "done earlier". Proceed regardless of its outcome.Remove the task from
project/todo.md— immediately after the ledger record, run step 1 (remove) of the Task-Completion Cleanup Procedure (### 7.1below) with this task'stitle:frontmatter value as<task title>. Run only theremovehalf here:teardownruns exactly once for the whole bundle, never per task (see step 6 and step 4d).removeexits non-zero when no queued entry matches, which is expected when the task was never queued individually; warn and proceed, since a cleanup miss never blocks or fails a task or the bundle. This is a single idempotenttodo.mdedit, so it does not disturb the c, c.1, c.2, c.3 ordering below.c.1. Post-task file extraction — immediately after the status write in step c and before proceeding to the next task, record the files changed by this task into the bundle manifest:
- Run
git diff --name-only HEAD~1 HEADto obtain the list of files changed by the just-completed task. Capture each line as a relative file path. If the command returns empty output (the task made no commits), treat the result as an empty array[]— this is not an error. - Read the bundle manifest at
project/queue/bundle-<E##_S##>.json. - Add or update the key
task_changed_filesin the manifest object. Settask_changed_files[<task_id>]to the captured file list (an array of strings, or[]if no files changed). Iftask_changed_filesdoes not yet exist in the manifest, create it as an empty object first. - Write the updated manifest atomically:
a. Serialise the updated manifest to JSON.
b. Write the JSON to
project/queue/bundle-<E##_S##>.json.tmp. c. Rename (move)project/queue/bundle-<E##_S##>.json.tmptoproject/queue/bundle-<E##_S##>.json. The rename is atomic on POSIX filesystems and prevents any consumer from reading a partially-written file.
This step is mandatory before proceeding to task N+1. The extraction must complete (even if the file list is empty) before the next task begins.
c.2. Conflict report — immediately after step c.1, compare the extracted file list against the files the task was expected to touch, and write a conflict report if unexpected files are found:
- Extract expected files from the task's frontmatter field
scope_rationaleand from the task's## Descriptionsection. Use a best-effort prose heuristic: split the text on whitespace and punctuation, then retain any token that either (a) contains a/character or (b) matches the pattern*.*(a dot surrounded by non-dot characters on both sides, e.g.SKILL.md,foo.json). Collect all retained tokens into a set calledexpected_files. This is intentionally permissive — false positives (expected files that were never actually changed) are acceptable and produce no report. - Compute unexpected files: let
actual_files= the array stored attask_changed_files[<task_id>]in the bundle manifest (from step c.1). Computeunexpected = actual_files − expected_files(set difference: files inactual_filesthat have no match inexpected_files). Matching is case-sensitive and exact against the relative path or the basename of the path — a token likeSKILL.mdmatches any actual file whose basename isSKILL.md(e.g.skills/j-do/SKILL.md). - If
unexpectedis non-empty: a. Write a Markdown conflict report toproject/queue/conflict-<task_id>.mdwith the following structure:
b. Read the bundle manifest at# Conflict Report: <task_id> **Generated:** <ISO 8601 UTC timestamp> **Bundle:** <E##_S##> ## Task ID <task_id> ## Expected Files Files inferred from scope_rationale and description: - <file1> - ... ## Actual Files Changed Files changed by this task (from git diff): - <file1> - ... ## Unexpected Files Files changed that were not anticipated by the task definition: - <file1> - ...project/queue/bundle-<E##_S##>.json. c. Append<task_id>to theconflict_reportsarray in the manifest. If the keyconflict_reportsdoes not yet exist, create it as an empty array first. d. Write the updated manifest atomically (tmp-file rename pattern — same as step c.1.4). - If
unexpectedis empty: skip this step entirely. Do not write a conflict report. Do not modify the bundle manifest.
This step is non-blocking. A conflict report does NOT affect the task's
Passedstatus or halt bundle execution. It is informational only — a human can reviewproject/queue/conflict-<task_id>.mdafter the bundle completes.c.3. Break checkpoint (
E44_S02) — after step c.2, never between c and c.1 and never between c.1 and c.2: a break must not be able to leave the manifest without this task's file list, or without the conflict report c.2 would have written. Run:bash "$([ -f skills/j-break/scripts/check-break.sh ] && echo skills/j-break/scripts/check-break.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/check-break.sh)"- Exit
0: proceed to the next task, unchanged. - Exit
10: halt the bundle here. The task that just finished is alreadyPassedand recorded in the manifest, so nothing is cut off mid-task. Do not run the failure path (no reset, no partial patch) and do not run the success path (no manifest deletion). Instead:- Keep the bundle manifest at
project/queue/bundle-<E##_S##>.jsonexactly as it stands. Itsanchor_shaandtask_changed_filesare the rollback anchor and the record of what completed. - Release the developer concurrency slot from step 3 and release the epic lock (
rm -f project/queue/epic-lock-<E##>.json). Both cleanup contracts apply on every exit path, and a halted bundle holds neither. - Leave the shared
bundle-<E##_S##>worktree and its commits untouched. - Report using the halt report in step 8 — including its scoped rapport when the record says
with_status=true— and state explicitly: which tasks completed (those nowPassed), which were not started (the rest of thetasks:list), that the manifest and rollback anchor are intact, and that the epic lock and slot have been released.
- Keep the bundle manifest at
- Any other exit: the checker failed, not a stop request. Warn with its stderr and proceed.
Resume. Entering
/do <E##_S##>again resumes the halted bundle (step 2): the helper finds the surviving manifest, keeps its originalanchor_sha, skips tasks alreadyPassedon the board and runs only the rest. If the operator prefers, the not-started tasks can still be dispatched individually with/do <E##_S##_T##>.d. After task failure — write
status: Failedto the task's frontmatter using the file-locking protocol:- Check for an existing lock file at
project/board/tasks/<task_id>_*.md.lock. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked, log a warning and skip this status write. - Create the lock file: write the current ISO 8601 timestamp into
project/board/tasks/<task_id>_*.md.lock. - Update the task frontmatter field
status: Failed. - Delete the lock file immediately after the write completes.
Then record it (best-effort, see step c):
bash "$([ -f skills/j-break/scripts/record-execution.sh ] && echo skills/j-break/scripts/record-execution.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/record-execution.sh)" <task_id> Failed --source do-bundle. Then run step 1 (remove) of the Task-Completion Cleanup Procedure (### 7.1below) for this task'stitle:, exactly as in step c (non-blocking if no entry matches). Then invoke the rollback anchor cleanup procedure (see "### Bundle Manifest and Rollback Anchor" — failure path). After the rollback anchor completes, release the developer concurrency slot acquired in step 3 above:"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <E##_S##> <orchestrator_session_id>(per### 4.4step 2 below). Then release the epic lock: runrm -f project/queue/epic-lock-<E##>.json. Then run step 2 (teardown) of the Task-Completion Cleanup Procedure (### 7.1below) once, to deleteproject/todo.mdif it is now effectively empty. Mark the bundle as failed and halt — do not execute any remaining tasks in the sequence.
- Locate the task file:
- Post-bundle verification (runs only if all tasks completed without failure):
- Inspect each task in the bundle for
needs_docs: truein its frontmatter. - If any task has
needs_docs: true: invoke the tester agent once for the full story. Pass the story ID, the list of all task IDs in the bundle, and the shared worktree path. The tester is responsible for updating individual task statuses. - If all tasks have
needs_docs: false: the developer agent self-verifies — reviews each task's implementation against its acceptance criteria without invoking the tester. After self-verification passes, writestatus: Passedanddate_completed: <YYYY-MM-DD>(today's date) to each task file using the file-locking protocol (steps c.1–c.4 above), and record each one (best-effort, see step c):bash "$([ -f skills/j-break/scripts/record-execution.sh ] && echo skills/j-break/scripts/record-execution.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/record-execution.sh)" <task_id> Passed --source do-bundle. No tester invocation occurs.
- Inspect each task in the bundle for
- Cleanup: on successful bundle completion, invoke the rollback anchor success path (see "### Bundle Manifest and Rollback Anchor" — success path) to delete the bundle manifest. Then release the developer concurrency slot acquired in step 3 above:
"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <E##_S##> <orchestrator_session_id>(per### 4.4step 2 below). Then release the epic lock: runrm -f project/queue/epic-lock-<E##>.json. Finally run step 2 (teardown) of the Task-Completion Cleanup Procedure (### 7.1below) once, to deleteproject/todo.mdif it is now effectively empty. Each task's entry was already removed in step 4c, so noremoveruns here; the post-bundlePassedrewrite in step 5 re-writes a status whose entry is already gone and does not callremoveagain.
Bundle Manifest and Rollback Anchor
The bundle manifest records the repository state before any task in the bundle executes. It serves as the anchor for rollback on failure and as a reference for conflict reports (E32_S07) and concurrency locks (E32_S08).
Before the first task executes
- Run
git rev-parse HEADto capture the current HEAD SHA asanchor_sha. Write
project/queue/bundle-<E##_S##>.jsonwith the following structure:{ "story_id": "<E##_S##>", "anchor_sha": "<SHA from git rev-parse HEAD>", "started_at": "<ISO 8601 UTC timestamp>", "tasks": ["<E##_S##_T##>", "..."], "task_changed_files": {}, "conflict_reports": [], "cross_bundle_conflicts": {} }story_id: the story ID for this bundle (e.g.E32_S05)anchor_sha: the full 40-character SHA returned bygit rev-parse HEADstarted_at: current UTC timestamp in ISO 8601 format (e.g.2026-08-16T12:00:00Z)tasks: ordered array of task IDs drawn from the story'stasks:frontmatter listtask_changed_files: starts as an empty object{}; populated after each task completes (see step 4c.1). Each key is a task ID and each value is an array of relative file paths changed by that task.conflict_reports: starts as an empty array[]; populated after each task's step 4c.2 conflict check. Each entry is a task ID string for which a conflict report was written toproject/queue/conflict-<task_id>.md.cross_bundle_conflicts: populated from the pre-execution conflict check (see "Pre-execution cross-bundle conflict check" above). Each key is thestory_idof a conflicting bundle and each value is an array of overlapping file paths. Initialised with thepending_conflictsdata collected during the conflict check; if no conflicts were found, initialised as{}.
Do not proceed to the first task until this file is written successfully.
On any task failure (failure path)
Execute the following steps in order. Do not skip any step.
- Save partial diff: run
git diff <anchor_sha> HEADand write the output toproject/queue/bundle-<E##_S##>-partial.patch. This captures all commits made since the anchor, providing a record of partial work for diagnosis. - Verify clean worktree: run
git status --porcelain. If the output is non-empty, there are uncommitted changes that would causegit reset --hardto error or lose work. In this case, emit:
Then runROLLBACK WARNING: worktree has uncommitted changes. Stashing before reset.git stashbefore proceeding. This ensures the reset completes without git complaints. - Reset to anchor: run
git reset --hard <anchor_sha>. The repository returns to the exact state it was in before the bundle started. - Delete bundle manifest: remove
project/queue/bundle-<E##_S##>.json.
After completing the failure path, halt bundle execution. Do not execute any remaining tasks in the sequence.
On successful bundle completion (success path)
- Delete bundle manifest: remove
project/queue/bundle-<E##_S##>.json.
No patch file is written on success.
If /do is invoked with a task ID (E##_S##_T##) or a plain-text title, skip this section and use the normal task execution path (steps 2–8 below).
2. List tasks and let the user choose
Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" list to display the queued tasks. Ask the user:
- Execute a specific task (by number or title)
- Execute the next task from the top of the list
- Exit
3. Detect and break down Epics or Stories (scrum-master phase)
Before resolving a task for execution, inspect the selected entry's ID:
- Epic (
E##) — the entry refers to a whole epic that has not yet been broken into stories. Use the scrum-master agent to read the epic's board file ($(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")epics/) and decompose it into stories. For each story produced:- Write a story file to
$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")stories/. - Run
bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" add '<story title>: <E##_S##>'After breakdown, Runbash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" remove '<epic entry title>'and go back to step 2 so the new stories are visible.
- Write a story file to
- Story (
E##_S##) with no tasks — the entry refers to a story that has not yet been broken into tasks. Check$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")tasks/for any task files whose front-matterstory_idmatches this story. If none exist, use the scrum-master agent to read the story's board file and decompose it into tasks. For each task produced:- Write a task file to
$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")tasks/. After breakdown, keep the story entry inproject/todo.md(tasks are discovered from it automatically). Go back to step 2.
- Write a task file to
- Story (
E##_S##) with existing tasks, or Task (E##_S##_T##) — no breakdown needed; proceed to step 4.
4. Resolve the task to full scrum board context
Each todo entry uses the format: <mission title>: <E##_S##_T##> (or E##_S## if no task ID).
Before starting:
- Locate and read the matching file from
$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")tasks/(or$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")stories/if story-level) - If the file does not exist, warn the user and skip — do not proceed with a task that has no scrum board definition
- Present a brief summary of the task: title, acceptance criteria, parent story, parent epic
4.1. Override validation
Before invoking the developer, check whether this task was manually scoped by a human operator.
- Read
jenga_assignedfrom the resolved task's frontmatter. - If
jenga_assignedistrueor the field is absent — proceed to step 4.1.2 (the pre-task gate) without any further override check. - If
jenga_assignedisfalse: a. Readoverride_justificationfrom the task's frontmatter. b. Ifoverride_justificationis absent or its value is an empty string, halt execution and emit:
Do not invoke the developer. Do not remove the task fromOVERRIDE VALIDATION ERROR [<task_id>]: jenga_assigned is false but override_justification is missing or empty. Add a justification before proceeding.project/todo.md. Return control to the user. c. Ifoverride_justificationis present and non-empty, log:
Then proceed to step 4.1.2 (the pre-task gate).Override acknowledged for <task_id>: <override_justification>
4.1.2. Pre-task gate
Fires pre-flight checklist phase pre-task (project/documentation/preflight-checklists.md) exactly once per
dispatched unit of work, here. Every path that starts work reaches this one step: the task path falls
through 4.1 into it on the way to 4.1.5, 4.2, 4.3 and step 5; Story-Bundle mode (### 1.5) calls it
explicitly; the Fallback to Full Task-Scope Pipeline re-uses its result and does not fire it again. The unit
is whatever step 4 resolved, or the story for a bundle.
Position. After 4.1 (read-only), before 4.1.5, before the locked-task guard in 4.2, and before every
worktree creation and every ### 4.4 slot acquisition. 4.1.5 and the locked guard write frontmatter, so
firing first means a halt leaves the item exactly as it was: still Pending, no status write, no
execution_scope rewrite, no slot consumed, nothing to release, project/todo.md entry kept.
Follow "Calling the checker from a skill" in that document: mint a run id for this unit (do-<UTC time>-<rand>),
call checklist.sh check pre-task --run "$RUN_ID", and branch on the exit code and each item's action. Exit
3 from this base phase is a genuine error, not "no items". Keep RUN_ID for the rest of the unit,
including any Fallback; each pass through the step 8 loop is a new unit and mints a new one. A block halt
stops here, before any work begins.
Who answers a judgment or confirm item. This /do execution's own session, never the developer
subagent. If the user can answer a prompt in this session now (a standalone /do, including one run by
/dooo, per ### 4.4 case (a)), put the item to the user with the numbered prompt and tick it only on their
explicit confirmation. If /do is itself a background sub-agent (a caller-supplied session id, ### 4.4 case
(b), such as /jenga Phase 4) or you cannot tell, there is no live user channel: never self-certify. An
exit 10 or 11 outcome defers the unit instead:
- Start nothing: no worktree, no slot, no subagent; write no status. If the caller already marked the item
In Progress, revert it toPendingwith### 4.4step 1a's locking protocol. - Append
{"event": "preflight_deferred", "agent": "orchestrator", "session_id": "<orchestrator_session_id>", "item_id": "<id>", "phase": "pre-task", "run_id": "<RUN_ID>", "items": ["<checklist item ids>"], "date": "<ISO 8601 UTC>"}toproject/logs/events.json(viascripts/events-append.sh, dual-path form as above). Never writecapacity_blocked; this is a different cause. - Report "deferred: pre-flight checklist needs a user decision (
<ids>:<reasons>)", never "failed".
Mark the situation. Immediately after minting the run id and before the first check, write
the situation marker, and clear it on every exit path (success, a block halt, a declined
confirm, a deferral, an error):
MARKER="$([ -f scripts/checklist-marker.sh ] && echo scripts/checklist-marker.sh || echo node_modules/@jenga-ai/agent/scripts/checklist-marker.sh)"
TOKEN="$(bash "$MARKER" write --situation pre-task --run "$RUN_ID" --skill j.do)"
bash "$MARKER" clear --token "$TOKEN"This is what lets the enforcing hook see the phase; the protocol, the stack semantics and the TTL are
in "Situation marker" in that document. A failed write is not a reason to abandon the phase — the
gate above still runs and only the hook-enforced backstop is lost: report its stderr and continue.
The marker's lifetime is the unit's, not this gate step's:
write it here and clear it when the unit finishes or is deferred, so a /j-commit run inside the unit nests
its own pre-commit frame on top rather than replacing it. A Fallback re-route is the same unit — it reuses
this marker exactly as it reuses RUN_ID, and neither re-writes nor clears it.
A unit that may outlive marker_ttl_minutes (60) should periodically run bash "$MARKER" refresh --token "$TOKEN" — never a second write, which would push a duplicate frame; exit 3 means the frame already expired, so write again and keep the new token.
Items whose action is remind never halt or defer: show them, and pass them to the developer subagent as a
"Pre-flight reminders" block in the context payload (### 5, ### 4.3, bundle spawn, Fallback). The inline
path has no subagent, so they are simply shown. With no registry, or an empty one, the gate is silent and
/do behaves exactly as before.
4.1.5. --trivial Dispatch-Time Override
After override validation (step 4.1) and the pre-task gate (step 4.1.2), and before branching on execution_scope in step 4.2, check whether this invocation was /do <id> --trivial.
- Detect the flag. If the task was invoked as
/do <id>with no--trivialflag, skip this entire section and proceed directly to### 4.2. - If
--trivialis present, read the task's currentexecution_scopefrom frontmatter. Treat an absent value astask, per the epic's backward-compatibility rule (a task that omitsexecution_scopeis treated asexecution_scope: task). Call this the prior tier — this covers both tasks nobody flagged as trivial at creation (an already-assignedstory/task/lighttier) and legacy tasks with noexecution_scopeset at all. - Overwrite
execution_scopetoinlinein the task's frontmatter, unconditionally —--trivialalways forcesinline, never a softer "lightest safe tier." - Record the override for audit, reusing the existing
jenga_assigned/override_justificationpairing already defined in$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)for exactly this situation ("scope overridden by a human"), rather than inventing a new field:- Set
jenga_assigned: false(if not alreadyfalse). - Set (or append to, if already present)
override_justification:override_justification: "/do --trivial dispatch-time override on <date>: execution_scope forced from '<prior_tier>' to 'inline' by human operator." - Also set (or append to)
scope_rationale, mirroring the phrasing conventionskills/j-todo/scripts/add_trivial_task.shalready uses for the creation-time flag, so both audit fields agree on the prior tier:scope_rationale: "forced inline via /do --trivial (dispatch-time override); prior execution_scope was '<prior_tier>'" - Emit a non-fatal log line (styled like 4.2's
AUTO-CORRECTIONmessage):TRIVIAL OVERRIDE [<task_id>]: execution_scope forced from "<prior_tier>" to "inline" via --trivial dispatch-time override.
- Set
- Set an in-session marker (this dispatch is a
--trivial-forced run) — this does not need to be persisted to frontmatter; it only needs to survive for the remainder of this/doinvocation. This marker is distinct from an organically-assignedexecution_scope: inlinetask (one the scrum-master or/jengaassignedinlineto directly, with no--trivialinvolved) because its failure handling differs — see### 4.2's failure-handling steps below. Do not confuse a--trivial-forced run with an organicinlinetask when applying those steps. - Proceed to
### 4.2. Inline Execution Pathwithexecution_scopenowinline. Everything else about inline execution (implementation, smoke test invocation, commit convention) is identical between an organicinlinetask and a--trivial-forced one — only the two failure-handling branches in### 4.2differ, per the marker set in step 5 above.
Precedence with the locked-task dispatch guard. If crucial_level: locked (checked by ### 4.2's locked-task dispatch guard, which always runs and always wins), --trivial is redundant but harmless — the task was already going to be forced inline. The locked-task guard's own audit fields take precedence for that correction; do not double-write conflicting override_justification text. A locked task's fallback behavior remains "halt and report," never the --trivial fallback below, regardless of whether --trivial was also passed.
4.2. Inline Execution Path (execution_scope: inline)
After resolving the task context (step 4), passing override validation (step 4.1), passing the pre-task gate (step 4.1.2, which has already fired once for this unit), and applying the --trivial dispatch-time override if present (step 4.1.5), read execution_scope from the task frontmatter.
Locked-task dispatch guard (defense-in-depth). Before branching on execution_scope below, read crucial_level from the task frontmatter (per $([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)'s Crucial Flag Fields). If crucial_level: locked:
- This task MUST be routed through the inline execution path below — no worktree, no developer subagent — regardless of what
execution_scopecurrently reads. This guards against a locked task reaching dispatch with a non-inlineexecution_scope(a race, a manually edited file, or a task added to a story'stasks:list afterskills/jenga/SKILL.mdPhase 0.5's Rule 4 last ran). - If
execution_scopeis alreadyinline, proceed directly to the inline steps below — no correction needed. - If
execution_scopeis anything other thaninline(absent,task,story, orepic), auto-correct it toinlinein the task frontmatter now, and record the correction using the same logged-note convention asskills/jenga/SKILL.mdPhase 0.5 Rule 4 (do not invent a second, inconsistent logging mechanism):- Append to the task's
override_justificationfrontmatter field:override_justification: "/do dispatch guard auto-correction <date>: execution_scope forced from '<previous_value>' to 'inline' because crucial_level: locked." - Then emit (non-fatally — do not halt):
AUTO-CORRECTION [<task_id>]: crucial_level=locked requires execution_scope=inline; corrected from "<previous_value>" to "inline". - Then proceed to the inline steps below with the now-corrected
execution_scope: inline.
- Append to the task's
- Under no circumstance does a
crucial_level: lockedtask fall through to step 5 (developer agent invocation / worktree creation) — this applies whether the task was resolved individually or would otherwise have entered the normal per-task worktree-creation flow.
If execution_scope: inline (including tasks corrected above), execute the task directly in the current session without spawning a developer subagent:
- Read the task file and load its full content (description, acceptance criteria). Do NOT create a worktree. Do NOT spawn a developer subagent.
- Before implementation begins — write
status: In Progressanddate_started: <today>to the task's frontmatter using the same file-locking protocol as### 1.5step 4a above:- Locate the task file:
project/board/tasks/<task_id>_*.md. - Check for an existing lock file at
project/board/tasks/<task_id>_*.md.lock. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked after the retry, log a warning and skip this status write (do not block task execution). - Create the lock file: write the current ISO 8601 timestamp into
project/board/tasks/<task_id>_*.md.lock. - Update the task frontmatter fields
status: In Progressanddate_started: <YYYY-MM-DD>(today's date). - Delete the lock file immediately after the write completes.
- Locate the task file:
- Implement the task inline — make the required changes to files directly in the current session.
- Run the smoke test harness before committing anything:
- Run
bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo node_modules/@jenga-ai/agent/scripts/smoke-harness.sh)" <changed_file>..., passing the paths changed in step 3. With no arguments the harness infers them fromgit diff --name-only HEAD. It exits0on pass and1on failure. - If neither
scripts/smoke-harness.shnornode_modules/@jenga-ai/agent/scripts/smoke-harness.shexists, log a warning and treat the result as a pass:WARNING [<task_id>]: smoke-harness.sh not found. Smoke test skipped (stub pass).
- Run
- If the smoke test exits non-zero:
- If this is a
--trivial-forced run (marker set in step 4.1.5 — andcrucial_levelis notlocked, which never falls back, per 4.1.5's precedence note): do NOT writestatus: Failed.--trivialalways forcesinlinewith no softer "lightest safe tier" to fall back to first, so a smoke-harness failure here goes straight to the shared#### Fallback to Full Task-Scope Pipelineprocedure below (origin:trivial). Do not proceed with the remaining inline steps below — the Fallback procedure takes over from here. - Otherwise (an organically-assigned
inlinetask,--trivialnot involved): behavior is unchanged from before —- Write
status: Failedto the task's frontmatter, then record it (best-effort, see step 7):bash "$([ -f skills/j-break/scripts/record-execution.sh ] && echo skills/j-break/scripts/record-execution.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/record-execution.sh)" <task_id> Failed --source do. - Emit:
INLINE TASK FAILED [<task_id>]: smoke test returned non-zero exit code. Task marked Failed. Halting. - Do not commit. Do not proceed to the next task.
- Write
- If this is a
- If the smoke test passes:
- Commit the changes using the standard commit convention (
task(<task_id>): <short description>) via/commitin inline mode (E32_S04_T03). - Run the Intent-vs-Diff Check (see
### 5.1. Intent-vs-Diff Checkbelow) for this task. - Self-verify the implementation against the acceptance criteria.
- Write
status: Passedanddate_completed: <today>to the task's frontmatter if verification passes. - Remove the task from
project/todo.md.
- Commit the changes using the standard commit convention (
inlinetasks do not invoke the tester agent — the smoke test and self-verification are the only gates.inlinetasks always haveneeds_docs: false— skip plan and summary documentation for the implemented task.- Continue to
### 6. Verify documentation, then### 7. After successful completion.
If the implementation cannot be completed inline (scope is larger than anticipated — detected scope creep mid-run):
- If this is a
--trivial-forced run (andcrucial_levelis notlocked): invoke the shared#### Fallback to Full Task-Scope Pipelineprocedure below (origin:trivial) — the same procedure the smoke-harness-failure branch above uses, not a second bespoke re-route. - Otherwise (an organically-assigned
inlinetask): abort and re-route to the normal developer path (step 5), unchanged from before. - Regardless of
--trivial, ifcrucial_level: locked, do not re-route via either path above; re-attempt inline or halt and report, per the locked-task dispatch guard above.
If execution_scope is light (and crucial_level is not locked — the locked-task dispatch guard above already ran and takes precedence over any scope check), proceed to ### 4.3. Light Execution Path below instead of step 5.
If execution_scope is not inline and not light (or is absent / task / story / epic) and crucial_level is not locked, proceed to step 5 (invoke the developer agent) as normal.
4.3. Light Execution Path (execution_scope: light)
After resolving the task context (step 4), passing override validation (step 4.1), passing the pre-task gate (step 4.1.2, already fired once for this unit; do not fire it again here), and applying the --trivial dispatch-time override if present (step 4.1.5 — note --trivial always forces inline, so a light-scoped task only reaches this section if --trivial was not passed), if execution_scope: light and crucial_level is not locked (per the locked-task dispatch guard in 4.2, which runs first and always wins), route the task through this path instead of the full task-scope pipeline in step 5.
light sits between inline and task: unlike inline, it spawns a real developer subagent (so it can handle small branching logic that inline's main-session execution isn't suited for); unlike task, it does not create a dedicated worktree and does not invoke the tester as a separate step.
- Before spawning the developer subagent — write
status: In Progressanddate_started: <today>to the task's frontmatter using the same file-locking protocol as### 1.5step 4a above:- Locate the task file:
project/board/tasks/<task_id>_*.md. - Check for an existing lock file at
project/board/tasks/<task_id>_*.md.lock. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked after the retry, log a warning and skip this status write (do not block task execution). - Create the lock file: write the current ISO 8601 timestamp into
project/board/tasks/<task_id>_*.md.lock. - Update the task frontmatter fields
status: In Progressanddate_started: <YYYY-MM-DD>(today's date). - Delete the lock file immediately after the write completes.
- Locate the task file:
Acquire a developer concurrency slot, then spawn a developer subagent. Acquire first, per
### 4.4. Developer Concurrency Slot Enforcementbelow (<id>= this task's id). On a full cap (capacity_blockedoutcome), do not spawn anything —### 4.4already reverts this task's status toPending, logs thecapacity_blockedevent, and tracks the consecutive-block count; stop here and let/jengaPhase 4 retry this task on a later wave once a slot frees up. On a successful acquire, spawn the subagent using the Agent tool with:subagent_type: "developer"name: "<task_id>"← Load-bearing identifier: this name is consumed byj.check-in's liveness probe to join running agents to board items viaListAgentsoutput. Must stay stable — this is not cosmetic.- Sender object and context payload from
### 5 - Explicit instruction added to the dispatch prompt: do not create a worktree — implement directly against the current checkout (the session's existing working tree), not an isolated
.claude/worktrees/<slug>copy
This is the one concrete difference from the step-5
taskpath: everything else about how the subagent implements the task (reading the task file, following acceptance criteria, following repo conventions) is unchanged.- After the developer subagent reports implementation complete, first release the developer concurrency slot acquired in step 2:
"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <task_id> <orchestrator_session_id>(per### 4.4step 2) — this subagent's session has ended, so the slot is released now regardless of what it reports, before the smoke test result is even known. Then run the smoke test harness using the same invocation convention as### 4.2. Inline Execution Path:- Run
bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo node_modules/@jenga-ai/agent/scripts/smoke-harness.sh)" <changed_file>..., passing the paths the subagent changed. With no arguments the harness infers them fromgit diff --name-only HEAD. It exits0on pass and1on failure. - If neither
scripts/smoke-harness.shnornode_modules/@jenga-ai/agent/scripts/smoke-harness.shexists, log a warning and treat the result as a pass:WARNING [<task_id>]: smoke-harness.sh not found. Smoke test skipped (stub pass).
- Run
- If the smoke test passes:
- The developer subagent self-verifies the implementation against the task's acceptance criteria. No tester subagent is invoked for a
light-scoped task — this is a deliberate, documented exception to "the tester is the sole status-writer" (agents/tester.md), mirroring the same exception already established forinlinescope in step 6 of### 4.2. Since no tester runs, the developer/orchestrator is the one who writes the terminal status for alight-scoped task. - Commit the changes using the standard commit convention (
task(<task_id>): <short description>) via/commit. - Run the Intent-vs-Diff Check (see
### 5.1. Intent-vs-Diff Checkbelow) for this task. - Write
status: Passedanddate_completed: <today>to the task's frontmatter if self-verification passes. - Remove the task from
project/todo.md. - Continue to
### 6. Verify documentation, then### 7. After successful completion.
- The developer subagent self-verifies the implementation against the task's acceptance criteria. No tester subagent is invoked for a
- If the smoke test fails (non-zero exit): do NOT write
status: Failedand do NOT halt. Instead, invoke#### Fallback to Full Task-Scope Pipelinebelow (origin:light).
Fallback to Full Task-Scope Pipeline
The pre-task gate does not fire here. A fallback is a re-route of a unit already gated once at ### 4.1.2, not a new unit. Reuse that gate's RUN_ID and result: do not call checklist.sh check pre-task again, and do not re-ask any judgment item the user already confirmed. Its remind items go to this fallback's developer subagent too.
This is a self-contained, reusable procedure with two current callers — ### 4.2's --trivial-forced inline failure branches (origin: trivial) and ### 4.3's light-scope smoke-harness failure (origin: light) — given a task that was attempted under a reduced-overhead execution scope and failed its smoke-harness check (or, for trivial, showed detected scope creep mid-run), do the following. The only thing that varies by caller is the notice text in step 5; steps 1–4 and 6 are identical regardless of origin.
- Do not mark the task
Failed. A smoke-harness failure (or detected scope creep) under a reduced-overhead scope means the scope was too small for the task, not that the task itself is unworkable — the correct response is to retry under full isolation, not to reject the work. - Create a worktree for the task, named
<E##_S##_T##-short-slug>per standard Worktree Management conventions, if one does not already exist for this task. (A task dispatched underlightscope, or forcedinlinevia--trivial, never had one — both premises skip worktree creation — so this step always creates a fresh worktree in that case.) - Acquire a developer concurrency slot (per
### 4.4. Developer Concurrency Slot Enforcementbelow,<id>= this task's id). This fallback spawn is a distinct developer-subagent lifecycle from whateverlight/trivialattempt preceded it — that attempt's own slot, if any, was already acquired and released around it (### 4.3step 2/3, or no slot at all for a--trivial-forced inline attempt, which never spawns a subagent) — so this step always acquires its own fresh slot. On a full cap (capacity_blockedoutcome), do not spawn anything here either: apply### 4.4's Pending-revert/log/consecutive-block handling for this task id and stop the fallback. The task remains exactly as the reduced-overhead attempt left it (any commits already made by that attempt stay in the worktree/branch just created in step 2), and/jengaPhase 4 retries it on a later wave once a slot frees up. - Spawn a developer subagent in that worktree and have it pick up from the current state of the code (the changes already made by the reduced-overhead attempt are still present in the working tree / already committed, if any commit occurred — the subagent continues from there rather than starting over).
- Invoke the tester agent per the normal
### 5. Invoke the developer agentflow's contract — full sender object, commit SHAs, worktree path. The tester is responsible for the terminal status write, exactly as in the standardtask-scope pipeline. Once this developer subagent's session ends — tester-verified, failed, or errored — release the slot acquired in step 3:"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <task_id> <orchestrator_session_id>(per### 4.4step 2). - Emit a clear, non-fatal fallback notice to the user/orchestrator, using the message matching the caller's origin:
- origin
light:LIGHT SCOPE FALLBACK [<task_id>]: smoke test failed; re-routing to full task-scope pipeline (worktree + developer + tester). - origin
trivial:TRIVIAL OVERRIDE FALLBACK [<task_id>]: smoke test failed (or scope creep detected); re-routing to full task-scope pipeline (worktree + developer + tester).
- origin
- Resume normal
task-scope processing (steps 6–8 below) once the tester returns a verdict.
4.4. Developer Concurrency Slot Enforcement (Shared Procedure)
This is a self-contained, reusable procedure with four current callers — the Story-Bundle Execution Mode's developer-subagent spawn (### 1.5 step 3, one acquire for the whole bundle, never one per task inside it), the Light Execution Path's developer-subagent spawn (### 4.3 step 2/3), the standard task-scope developer invocation (### 5 below), and the Fallback to Full Task-Scope Pipeline's developer-subagent spawn (step 3/5 above). Every one of these is a genuine "spawn a developer subagent" moment — inline scope (### 4.2, including a --trivial-forced run that hasn't yet fallen back) is the only path that never spawns a subagent and therefore never calls this procedure at all.
<id> is the task id (E##_S##_T##) for a single-task dispatch (the ### 5, ### 4.3, and Fallback call sites), or the story id (E##_S##) for the bundle path — one acquire call covers the whole bundle, never one per task inside it.
<orchestrator_session_id> is the session_id value already being populated into this dispatch's sender object per assets/sender_template.json (### 5's "Sender object" step) — the same session identity already used for this invocation's sender object, handoff files, and event log entries. Reuse that exact value; do not fabricate a second, separate identifier for this call. This is what keeps every developer spawn issued by the same orchestrating run sharing one counter file (project/queue/concurrency-slots-<orchestrator_session_id>.json), regardless of which of the four call sites above triggered it.
Where that value comes from — two cases (E32_S16_T02). ### 5's "Sender object" step says to populate session_id, but does not by itself say where that value originates. It is always exactly one of the following two cases, decided once at the top of this /do invocation, before any of the four call sites above ever runs:
- (a) Standalone invocation.
/dowas invoked directly — by a human, or by another skill that did not pass along a session identity of its own (e.g. a bare/do, or/do <id>typed by a user). In this case/dogenerates its ownsession_id, exactly as it always has — this is no behavior change, only making the previously-implicit behavior explicit. Follow this repo's existing session-id string convention for an orchestrator-minted id (seeskills/jenga/SKILL.mdPhase 0.9'sjenga-<UTC ISO 8601 basic timestamp>format for the same convention applied to an orchestrating skill) — a short prefix naming the invoking context plus a UTC timestamp, with no/or..characters, since the value is used verbatim in filenames such as this section's own counter file. - (b) Caller-supplied invocation.
/dowas invoked by another orchestrator that already has its own session identity and passes it along as part of the dispatch context — for example/jengaPhase 4 threading its ownorchestrator_session_id(generated once inskills/jenga/SKILL.mdPhase 0.9) into each sub-agent's sender object, or Phase 3.5 step 7b's bundle/do <E##_S##>call doing the same. In this case/doMUST reuse that caller-supplied value verbatim as this invocation'ssession_id— it must never generate a new one. This is what lets every/dosub-agent a single/jengarun (or any other orchestrator) dispatches share oneproject/queue/concurrency-slots-<id>.jsoncounter file, which is the entire point of<orchestrator_session_id>in this section.
Detecting which case applies is mechanical: if the dispatch context already contains a session_id populated by the calling skill/agent, this is case (b) and that value is used as-is; if it does not, this is case (a) and /do mints its own. Once decided, that single value is what gets copied into assets/sender_template.json's session_id field in ### 5 below and referenced everywhere in this document as <orchestrator_session_id>.
- Acquire. Before spawning the developer subagent (or the bundle's shared developer subagent), run:
Per that script's own exit-code contract:"$([ -f scripts/acquire-concurrency-slot.sh ] && echo scripts/acquire-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/acquire-concurrency-slot.sh)" developer <id> <orchestrator_session_id>- Exit
0— slot acquired. Proceed to spawn the subagent exactly as documented at the calling site, and carry out step 2 below (release) once that subagent's session ends. - Exit
3— thedeveloperrole is at/over its configured cap (max_concurrent_developers) after stale-reclaim; the counter file was not mutated. This, and only this exit code, is acapacity_blockedoutcome. Do NOT spawn anything. Handle it as follows: a. Revert the item's board status toPending, using the same file-locking protocol already used elsewhere in this skill for status writes. b. Read the current holders snapshot for thedeveloperrole fromproject/queue/concurrency-slots-<orchestrator_session_id>.json(.developer.holders). If the file does not exist yet, treat the snapshot as{}. Note this snapshot may still include not-yet-reclaimed stale (TTL-expired) entries, since a blocked acquire call never mutates the counter file — it is a best-effort audit snapshot, not a guaranteed-fresh one. c. Read/update the per-item consecutive-block counter per step 3 below, and use the resulting count as this event'swavevalue. d. Append one entry toproject/logs/events.jsonwithbash "$([ -f scripts/events-append.sh ] && echo scripts/events-append.sh || echo node_modules/@jenga-ai/agent/scripts/events-append.sh)" '<json-record>'(JSONL, append only; never rewrite the file):
e. Do not proceed to step 2 (release) — nothing was acquired. Report this outcome to the user/orchestrator as "deferred: developer capacity is currently full for this session", never as "failed".{ "event": "capacity_blocked", "agent": "orchestrator", "session_id": "<orchestrator_session_id>", "role": "developer", "item_id": "<id>", "wave": <consecutive-block count from step 3, after incrementing>, "holders": <the holders snapshot from (b)>, "date": "<ISO 8601 UTC timestamp>" } - Exit
1,2, or5— a usage error, awith-lock.shtimeout, or an environment error (missingjq, missing/invalid config). This is not a capacity block — the cap itself was never actually evaluated. Treat it as an ordinary/doerror: do not spawn the subagent, do not write acapacity_blockedevent, do not touch the consecutive-block counter, and fall through to the pre-existing failure→skip→Pendinghandling (skills/jenga/SKILL.md's Edge Cases: "/dofailure (background agent) — treated as a skip; mark the item's status back toPending..."). Report the script's stderr so the underlying script/lock/environment problem is visible, rather than silently reporting it as a capacity issue.
- Exit
- Release — on every subagent exit path. Once the spawned developer subagent's session ends — whether it reports success, failure, or an error/exception — run:
This call is unconditional and must be reached from every branch that follows a successful acquire in step 1: the success path, the ordinary"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <id> <orchestrator_session_id>/dofailure→skip→Pendingpath, and any other error/exception branch that aborts the calling section early.release-concurrency-slot.shis idempotent by its own contract (a no-op success if the holder entry is already absent), so calling it defensively — even in a code path where it is uncertain whether the acquire actually landed — is always safe and never an error. Per-item consecutive-block counter (across waves). Maintain a small session-scoped state file at
project/queue/capacity-block-state-<orchestrator_session_id>.json, shaped as a flat map from item id to its current consecutive-block count:{ "<id>": <int> }This file lives alongside
project/queue/concurrency-slots-<orchestrator_session_id>.jsonunder the identical session-scoped naming convention — it belongs to exactly the one orchestrating session named in its filename and must never be read or written for a differentorchestrator_session_id, for the same reasonscripts/acquire-concurrency-slot.sh's own header comment gives for its own counter file. Because/dois very often re-dispatched as a brand-new background subagent on every wave (per/jengaPhase 4's Pending-rescan loop), this state cannot rely on in-session memory alone — persisting it here is what lets the "3 consecutive blocked waves" count survive across those separate dispatches. Because multiple different items can each be blocked in the same wave, all writing to this one shared file, every read-modify-write against it must go throughscripts/with-lock.sh(mirroringproject/queue/concurrency-slots-<session_id>.json's own locking discipline) rather than the bare tmp-file-rename pattern used for the (single-writer-at-a-time) bundle manifest elsewhere in this skill.- On a
capacity_blockedoutcome (step 1's exit-3 branch): read this item's current count (default0if the key is absent), increment it by1, and write the incremented value back. Use this same incremented value as thewavefield in thecapacity_blockedevent (step 1d above).- Escalation. If the incremented count has reached
3: append one line toproject/queue/scrum_triggers.jsonl, matching the existing trigger shape documented intemplates/SCRUM_BOARD_SCHEMA.md's Queue Trigger Types section (the same{"type": ..., ..., "sender": {...}, "message": "..."}shape already used bystory_rollupentries):
Then reset this item's count back to{"type": "capacity_starvation", "item_id": "<id>", "wave": 3, "role": "developer", "date": "<ISO 8601 UTC timestamp>", "sender": {"agent": "orchestrator", "session_id": "<orchestrator_session_id>", "task_id": "<id>", "date": "<ISO 8601 UTC timestamp>"}, "message": "Item <id> has been capacity_blocked for 3 consecutive waves against the developer role cap (max_concurrent_developers)."}0in the state file. This is a passive audit/escalation write only — it does not itself retry anything; retry keeps working purely via/jengaPhase 4's existing Pending-rescan-per-wave loop, unrelated to this trigger.capacity_starvationhandling (surfacing a plain warning, no automatic remediation) is documented separately inagents/scrum-master.md's Drain Scrum Triggers Queue procedure (E32_S15_T06) — out of scope for this file.
- Escalation. If the incremented count has reached
- On a successful acquire (step 1's exit-0 branch): if this item already has an entry in the state file, reset its count back to
0(a successful dispatch clears the streak — the counter tracks consecutive blocked waves, not a cumulative total). If it has no entry, there is nothing to reset.
- On a
capacity_blocked vs. the existing failure→skip→Pending path. A capacity_blocked outcome and /do's pre-existing failure handling (documented in skills/jenga/SKILL.md's Edge Cases as "/do failure (background agent) — treated as a skip; mark the item's status back to Pending and continue the loop with remaining candidates.") arrive at the same board-visible result — the item ends up Pending again, picked back up by /jenga Phase 4's next wave rescan — but for a different, never-conflated reason:
- Ordinary failure: the developer subagent (or bundle subagent) was actually spawned and ran; either the implementation itself failed or an unexpected error occurred mid-run. No
capacity_blockedevent is written, and the consecutive-block counter above is untouched. Retry works purely because the item isPendingagain — no concurrency accounting is involved. capacity_blocked: no subagent was ever spawned at all — the item never got a chance to run because the session'sdeveloperrole was already at its configured cap (max_concurrent_developersinproject/configs/scope-thresholds.json) at dispatch time. This is a passive audit write only: it does not itself drive retry, and it must never be reported to the user as "the task failed" — it is reported as "the task was deferred, developer capacity was full for this session."
5. Invoke the developer agent
The pre-task gate (### 4.1.2) has already fired for this unit, before this section and before the slot below; do not fire it again here.
Acquire a developer concurrency slot first. Follow ### 4.4. Developer Concurrency Slot Enforcement above with <id> = this task's id, before doing anything else in this section. On a full cap (capacity_blocked outcome), do not invoke the developer agent at all — ### 4.4 already reverts the task's status to Pending, logs the capacity_blocked event, and tracks the consecutive-block count for this task; simply stop here and let /jenga Phase 4 retry this task on a later wave once a slot frees up. This is a distinct outcome from the ordinary /do failure→skip→Pending path below — see ### 4.4's "capacity_blocked vs. the existing failure→skip→Pending path" note.
On a successful acquire, invoke the developer Agent tool with the following parameters:
Agent invocation: Use Agent tool with:
subagent_type: "developer"name: "<task_id>"← Load-bearing identifier: this name is consumed byj.check-in's liveness probe to join running agents to board items viaListAgentsoutput. It must match the task ID format (E##_S##_T##) and stay stable. This is not cosmetic — do not change the naming convention without coordinating withj.check-inimplementation.- Full context and sender object (see below)
Sender object: Copy assets/sender_template.json and populate all known fields — session_id per ### 4.4's standalone-vs-caller-supplied cases immediately above (generate one if this is a standalone invocation, case (a); reuse the caller-supplied value verbatim if one was already passed into the dispatch context, case (b) — never regenerate in that case), plus task_id, story_id, epic_id, and the current ISO 8601 UTC date.
Context payload (plain text alongside the sender object):
- Full task/story file content (title, description, acceptance criteria)
- Parent story and epic summaries (read from board files)
- Any relevant context from
project/PROJECT_SUMMARY.md - A "Pre-flight reminders" block with the
textof eachpre-taskitem whoseactionwasremind(omit when there are none)
The developer agent will:
- Log the incoming sender object to
project/logs/events.json(viascripts/events-append.sh) - Create a worktree named
<E##_S##_T##-short-slug> - Implement, commit at milestones, and invoke the tester agent
- Return when the tester has verified the work
Release the slot on every exit path. Once the developer agent's session ends — whether it returns having implemented and been verified by the tester, fails outright, or errors — release the slot immediately: "$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <task_id> <orchestrator_session_id> (per ### 4.4 step 2). This applies uniformly to the success path above, /jenga's existing "/do failure (background agent)" skip-to-Pending path (skills/jenga/SKILL.md's Edge Cases), and any other error/exception that aborts this section early.
Record a tester-written Failed (E44_S09). Step 7 only runs on success, so a task the tester marks Failed would otherwise never reach the execution ledger. As soon as the developer agent returns, read the task's status from its frontmatter; if it is Failed, run, best-effort, before following the failure path above:
bash "$([ -f skills/j-break/scripts/record-execution.sh ] && echo skills/j-break/scripts/record-execution.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/record-execution.sh)" <task_id> Failed --source doIt never blocks or fails the task (see step 7). Any other status is left to step 7.
5.1. Intent-vs-Diff Check (needs_docs: false only)
After the developer agent returns (or after inline execution completes), run the following check if the task has needs_docs: false in its frontmatter. If needs_docs: true, skip this check entirely — the full documentation lifecycle handles divergence detection for those tasks.
Steps:
- Read
needs_docsfrom the task's frontmatter. Ifneeds_docs: true(or absent and defaulting totrue), skip to step 6. - Run
git diff --name-only HEAD~1 HEADto retrieve the list of changed file names (relative paths, one per line). - Read the prompt template from
skills/j-do/assets/intent-vs-diff-prompt.md. Extract the prompt block (the content between the triple backticks under## Prompt). - Substitute the placeholders:
{description}— full text of the task's## Descriptionsection{acceptance_criteria}— full text of the task's## Acceptance Criteriasection{changed_files}— newline-separated output from step 2
- Invoke the LLM with the constructed prompt (using the orchestrating agent's LLM access). Ask for a JSON array response only.
- Parse the LLM response as a JSON array:
- If parsing fails (malformed JSON): treat as
[], emit:[intent-vs-diff] LLM response was not valid JSON; treating as no divergence., and continue. - If the array is empty (
[]): no action — do not modify the task frontmatter. - If the array is non-empty: write
divergence_flag: trueto the task's frontmatter, then emit:[DIVERGENCE WARNING] Task <task_id>: the following files were changed but are not mentioned or inferable from the task description: - <file1> - <file2> This is a non-blocking warning. Task outcome is not affected.
- If parsing fails (malformed JSON): treat as
This check is non-blocking. It does not change the task's Passed/Failed outcome. It only writes divergence_flag: true and emits a warning for human review. Execution continues regardless of the check result.
Prompt calibration: The prompt in skills/j-do/assets/intent-vs-diff-prompt.md is tuned to flag only files with zero plausible connection to the stated task. Test files, documentation files, lock files, and clearly implied files are excluded from flagging. See the ## False-Positive Tuning Rationale section in the prompt template for full details.
6. Verify documentation
After the developer completes the task, confirm the following documentation was written:
- Execution plan —
project/documentation/plans/<E##_S##_T##>-plan.mdmust exist (written by the developer before starting work) - Execution summary —
project/documentation/summaries/<E##_S##_T##>-summary.mdmust exist (written by the developer before invoking the tester)
If either file is missing, ask the developer to produce it before continuing.
Additionally, if the completed work introduces user-facing changes, update README.md and WARP.md accordingly.
7. After successful completion
- Record the terminal status in the execution ledger (
E44_S09). Whether/dowrote the status itself (inlineandlightself-verification) or the tester wrote it and/doobserved it, read the task's final status from its frontmatter and, if it isPassed,Passed with remarks,FailedorMerged, run:
This call is best-effort and never blocks or fails a task:bash "$([ -f skills/j-break/scripts/record-execution.sh ] && echo skills/j-break/scripts/record-execution.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/record-execution.sh)" <task_id> <status> --source dorecord-execution.shwarns on stderr and exits0on any ledger problem. It feeds/break status's "ran this session" marker. - Check for any
_INSTRUCTIONS.mdfiles inproject/instructions/whose ID matches the completed task. If found, present them to the user and explain that these actions must be completed before the feature will work correctly. - Invoke the
/commitskill to commit the work (if not already committed by the developer) - Run the Task-Completion Cleanup Procedure (
### 7.1below) with this task's title, to remove the completed task fromproject/todo.mdand delete the file if it is now effectively empty
7.1. Task-Completion Cleanup Procedure (Shared Procedure)
This is a self-contained, reusable procedure: it removes a finished task's entry from project/todo.md and tears the file down if nothing is left. It is named and documented here, rather than only as an inline bullet of /do's own loop, so that any skill that dispatches tasks outside /do's step 1-8 loop can run it by name (Task-Completion Cleanup Procedure, skills/j-do/SKILL.md ### 7.1) instead of re-deriving or duplicating the two commands. Callers: /do step 7 (above) today; /do's story-bundle path (### 1.5 step 4c and 4d call remove per task; step 4d and step 6 call teardown once per bundle, wired by E37_S05_T03); /jenga Phase 4's direct dispatch and /dooo are wired to it by E37_S05_T02 and E37_S05_T04.
When to run. Immediately after a task reaches a terminal status (Passed, Passed with remarks or Failed), in the same session that observed that status. Do not defer it to a later pass or session.
Input. <task title> is the entry's title exactly as it appears in project/todo.md (the same string /do step 2 listed and step 4 resolved), not the task id.
- Remove the entry. Run:
bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" remove '<task title>' - Tear down if empty. Run:
This deletesbash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" teardownproject/todo.mdonly if it is now effectively empty; otherwise it leaves the file in place.
Constraints. scripts/todo_manager.sh is the sole underlying mechanism. This procedure introduces no new queue file, lock primitive or script, and adds no behavior of its own: the two invocations and their arguments are exactly those step 7 previously carried inline.
Why a named section, not a new script. The two calls are already a deterministic, two-line sequence over an existing script, so wrapping them in a new script would add a file without removing any agent judgment. What was missing was a stable, citable handle; a heading in the file that already owns the logic gives other skills that handle at zero extra mechanism (the same approach ### 4.4 takes for concurrency-slot enforcement).
8. Break checkpoint, then loop
Runs only after step 7 has fully completed: the task's status is written, the work is committed and the todo.md entry is removed. Before going back to step 1, check whether a break was requested (/break, E44):
bash "$([ -f skills/j-break/scripts/check-break.sh ] && echo skills/j-break/scripts/check-break.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/check-break.sh)"- Exit
0— no break. Go back to step 1, unchanged. This is the overwhelmingly common case and costs one file-existence test. - Exit
10— a break was requested. Do not return to step 1. The record is on stdout asmode=<soft|hard> with_status=<true|false> requested_at=<ts> requested_by=<who>. Asoftbreak was consumed by that call; ahardbreak is still in place. Halt and print the halt report. - Any other exit — the checker itself failed, which is not a stop request. Print its stderr as a warning and go back to step 1.
Never read the flag file here. Branch on the exit code and use the record the script prints.
Halt report. State plainly that this is a requested stop and not a failure:
- Finished: the task that just completed and its status (or "none — nothing was started" when halting from the entry check).
- Remaining: the items still in
project/todo.md; for a halted story bundle, the not-started tasks (the helper'srunlist) instead, since a bundle uses notodo.mdentries. - Mode: soft or hard.
- Resume:
/proceedfor a soft break; for a hard break,/break clearfirst, then/proceed. For a halted story bundle (the halt came from step 1.5 c.3), offer firstresume the bundle (/do <E##_S##>)(after/break clearwhen the break is hard), and individual task dispatch with/do <E##_S##_T##>as the alternative. List completed (skipped), not started (run) and the intactanchor_shaby runningbash "$([ -f skills/j-do/scripts/bundle-resume.sh ] && echo skills/j-do/scripts/bundle-resume.sh || echo node_modules/@jenga-ai/agent/skills/j-do/scripts/bundle-resume.sh)" <E##_S##>and reading its JSON, never from memory. - Scoped rapport: only when the record says
with_status=true. Runbash "$([ -f skills/j-break/scripts/render-execution-status.sh ] && echo skills/j-break/scripts/render-execution-status.sh || echo node_modules/@jenga-ai/agent/skills/j-break/scripts/render-execution-status.sh)"and print its output verbatim. If it fails, say the rapport is unavailable and why. Never improvise a rapport from the board.
Nothing is aborted mid-flight: the current task has already run to completion by the time this checkpoint is reached.
When there is no break, go back to step 1.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

