agentleFS
samwelmungasamwelmunga/jenga-npm/.agents/skills/j-do/SKILL.md

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

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
  1. Distinctive:

    If the file does not exist, emit:

    line 47
  2. Distinctive:

    If the file is not valid JSON, emit:

    line 53
  3. Distinctive:

    Do not read the flag file yourself.

    line 84
  4. Very specific:

    Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" …

    line 87
  5. Very specific:

    When /do is invoked with a story ID (e.g., /do E##_S##), skip steps 2–4 and enter story-bundle execution mode:

    line 91
  6. Distinctive:

    Before proceeding, acquire the epic-level sequential lock for this bundle:

    line 103
  7. Very specific:

    Read bundle_lock_ttl_minutes from project/configs/scope-thresholds.json (already loaded in step 0).

    line 111
  8. Very specific:

    If age_minutes < bundle_lock_ttl_minutes (lock is non-stale), emit:

    line 112
  9. Very specific:

    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 …

    line 117
  10. Very specific:

    If project/queue/epic-lock-<E##>.json does not exist, continue to the write step below.

    line 118
  11. Very specific:

    Write this JSON to project/queue/epic-lock-<E##>.json.tmp (temporary file in the same directory).

    line 129
  12. Distinctive:

    After acquiring the epic lock and before writing the bundle manifest, scan all other active bundle manifests to detect file-level overlap …

    line 138
  13. Very specific:

    If multiple other bundles each have overlap, accumulate all of them under their respective story IDs in pending_conflicts.

    line 168
  14. Very specific:

    Pass pending_conflicts forward: when writing the bundle manifest in the next step (step 2 below), initialise the cross_bundle_conflicts …

    line 170
  15. Very specific:

    mode: fresh with an all_terminal note: every task is already Passed/Merged and there is no manifest to resume, so running would redo …

    line 187
  16. Very specific:

    mode: fresh: no manifest exists. Follow the existing path unchanged: capture anchor_sha, write the manifest as described in "### Bundle …

    line 188
  17. Very specific:

    Check for an existing lock file at project/board/tasks/<task_id>_*.md.lock.

    line 209
  18. Very specific:

    Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.

    line 210
  19. Very specific:

    Update the task frontmatter fields status: In Progress and date_started: <YYYY-MM-DD> (today's date).

    line 211
  20. Distinctive:

    Delete the lock file immediately after the write completes (before step b).

    line 212
  21. Very specific:

    Check for an existing lock file at project/board/tasks/<task_id>_*.md.lock.

    line 217
  22. Distinctive:

    Delete the lock file immediately after the write completes.

    line 220
  23. Very specific:

    Run git diff --name-only HEAD~1 HEAD to obtain the list of files changed by the just-completed task.

    line 234
  24. Very specific:

    Read the bundle manifest at project/queue/bundle-<E##_S##>.json.

    line 235
  25. Very specific:

    Add or update the key task_changed_files in the manifest object.

    line 236
  26. Very specific:

    Write the updated manifest atomically: a.

    line 237
  27. Very specific:

    Leave the shared bundle-<E##_S##> worktree and its commits untouched.

    line 293
  28. Very specific:

    Update the task frontmatter field status: Failed.

    line 302
  29. Very specific:

    Run git rev-parse HEAD to capture the current HEAD SHA as anchor_sha.

    line 321
  30. Very specific:

    Write project/queue/bundle-<E##_S##>.json with the following structure:

    line 322
  31. Distinctive:

    Do not proceed to the first task until this file is written successfully.

    line 342
  32. Very specific:

    Save partial diff: run git diff <anchor_sha> HEAD and write the output to project/queue/bundle-<E##_S##>-partial.patch.

    line 348
  33. Distinctive:

    Verify clean worktree: run git status --porcelain.

    line 349
  34. Very specific:

    Reset to anchor: run git reset --hard <anchor_sha>.

    line 354
  35. Very specific:

    Delete bundle manifest: remove project/queue/bundle-<E##_S##>.json.

    line 355
  36. Distinctive:

    After completing the failure path, halt bundle execution.

    line 357
  37. Distinctive:

    No patch file is written on success.

    line 363
  38. Very specific:

    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 …

    line 365
  39. Very specific:

    Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" …

    line 368
  40. Distinctive:

    Before resolving a task for execution, inspect the selected entry's ID:

    line 375
  41. Very specific:

    Use the scrum-master agent to read the epic's board file ($(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || …

    line 378
  42. Very specific:

    Write a story file to $(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo …

    line 379
  43. Very specific:

    After breakdown, Run bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo …

    line 381
  44. Very specific:

    Check $(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo …

    line 384
  45. Very specific:

    Write a task file to $(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo …

    line 385
  46. Very specific:

    After breakdown, keep the story entry in project/todo.md (tasks are discovered from it automatically).

    line 386
  47. Distinctive:

    If the file does not exist, warn the user and skip — do not proceed with a task that has no scrum board definition

    line 395
  48. Distinctive:

    Before invoking the developer, check whether this task was manually scoped by a human operator.

    line 400
  49. Very specific:

    Read jenga_assigned from the resolved task's frontmatter.

    line 402
  50. Very specific:

    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.

    line 403
  51. Very specific:

    If jenga_assigned is false: a.

    line 404
  52. Very specific:

    Do not invoke the developer.

    line 410
  53. Very specific:

    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 …

    line 425
  54. Very specific:

    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 …

    line 430
  55. Very specific:

    Start nothing: no worktree, no slot, no subagent.

    line 443
  56. Very specific:

    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 …

    line 478
  57. Very specific:

    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.

    line 480
  58. Very specific:

    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 …

    line 509
  59. Very specific:

    Locked-task dispatch guard (defense-in-depth): Before branching on execution_scope below, read crucial_level from the task frontmatter (per

    line 511
  60. Very specific:

    If execution_scope is already inline, proceed directly to the inline steps below — no correction needed.

    line 514
  61. Very specific:

    If execution_scope is anything other than inline (absent, task, story, or epic), auto-correct it to inline in the task frontmatter now, and …

    line 515
  62. Distinctive:

    Read the task file and load its full content (description, acceptance criteria).

    line 529
  63. Distinctive:

    Run the smoke test harness before committing anything:

    line 537
  64. Very specific:

    Run bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo …

    line 538
  65. Very specific:

    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 …

    line 539
  66. Very specific:

    Write status: Failed to the task's frontmatter, then record it (best-effort, see step 7):

    line 546
  67. Distinctive:

    Do not commit. Do not proceed to the next task.

    line 551
  68. Very specific:

    Commit the changes using the standard commit convention (task(<task_id>): <short description>) via /commit in inline mode (E32_S04_T03).

    line 553
  69. Very specific:

    Run the Intent-vs-Diff Check (see ### 5.1.

    line 554
  70. Very specific:

    Write status: Passed and date_completed: <today> to the task's frontmatter if verification passes.

    line 556
  71. Very specific:

    Remove the task from project/todo.md.

    line 557
  72. Distinctive:

    If the implementation cannot be completed inline (scope is larger than anticipated — detected scope creep mid-run):

    line 562
  73. Very specific:

    Run bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo …

    line 593
  74. Very specific:

    Commit the changes using the standard commit convention (task(<task_id>): <short description>) via /commit.

    line 601
  75. Very specific:

    Write status: Passed and date_completed: <today> to the task's frontmatter if self-verification passes.

    line 603
  76. Very specific:

    If the smoke test fails (non-zero exit): do NOT write status: Failed and do NOT halt.

    line 607
  77. Distinctive:

    Acquire: Before spawning the developer subagent (or the bundle's shared developer subagent), run:

    line 646
  78. Very specific:

    Escalation: If the incremented count has reached 3: append one line to project/queue/scrum_triggers.jsonl, matching the existing trigger …

    line 685
  79. Very specific:

    Agent invocation: Use Agent tool with:

    line 704
  80. Very specific:

    Create a worktree named <E##_S##_T##-short-slug>

    line 719
  81. Very specific:

    After the developer agent returns (or after inline execution completes), run the following check if the task has needs_docs: false in its …

    line 735
  82. Very specific:

    Read needs_docs from the task's frontmatter.

    line 739
  83. Very specific:

    Run git diff --name-only HEAD~1 HEAD to retrieve the list of changed file names (relative paths, one per line).

    line 741
  84. Very specific:

    Read the prompt template from skills/j-do/assets/intent-vs-diff-prompt.md.

    line 743
  85. Very specific:

    If parsing fails (malformed JSON): treat as [], emit: [intent-vs-diff] LLM response was not valid JSON.

    line 753
  86. Very specific:

    If the array is empty ([]): no action — do not modify the task frontmatter.

    line 754
  87. Very specific:

    If the array is non-empty: write divergence_flag: true to the task's frontmatter, then emit:

    line 755
  88. Distinctive:

    After the developer completes the task, confirm the following documentation was written:

    line 768
  89. Distinctive:

    If either file is missing, ask the developer to produce it before continuing.

    line 772
  90. Very specific:

    Check for any _INSTRUCTIONS.md files in project/instructions/ whose ID matches the completed task.

    line 782
  91. Very specific:

    Run the Task-Completion Cleanup Procedure (### 7.1 below) with this task's title, to remove the completed task from project/todo.md and …

    line 784
  92. Distinctive:

    Remove the entry: Run:

    line 794
  93. Distinctive:

    Tear down if empty: Run:

    line 798
  94. Distinctive:

    Never read the flag file here.

    line 820
  95. Very specific:

    Scoped rapport: only when the record says with_status=true.

    line 828
  96. Distinctive:

    When there is no break, go back to step 1.

    line 832

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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.sh was retired by E50_S14 and no longer exists — there is nothing to run. This file was previously generated from a bare skills/do/SKILL.md source; E50_S15 deleted 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 scope
  • inline_max_lines — maximum total lines changed for inline scope
  • story_max_files — maximum files a task may touch to qualify for story-scope bundling
  • bundle_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 a mode=soft record: 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=hard record (exit 10): 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:

  1. Read the story file from project/board/stories/<E##_S##>_*.md. Extract the tasks: list. If the story file does not exist, emit:
    ERROR: Story file for <story_id> not found. Cannot execute bundle.
    and halt.

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##>.json exists:
    1. Parse the JSON and read the started_at field (ISO 8601 UTC timestamp).
    2. Compute the lock age: age_minutes = (now_utc - started_at) / 60.
    3. Read bundle_lock_ttl_minutes from project/configs/scope-thresholds.json (already loaded in step 0).
    4. If age_minutes < bundle_lock_ttl_minutes (lock is non-stale), emit:
      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.
      and halt — do not proceed with this bundle.
    5. 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 write step below.
  • If project/queue/epic-lock-<E##>.json does not exist, continue to the write step below.

c. Write the lock atomically:

  1. Compose the lock JSON:
    {
      "epic_id": "<E##>",
      "bundle_story_id": "<E##_S##>",
      "started_at": "<current ISO 8601 UTC timestamp>"
    }
  2. Write this JSON to project/queue/epic-lock-<E##>.json.tmp (temporary file in the same directory).
  3. Rename (move) project/queue/epic-lock-<E##>.json.tmp to project/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' EXIT in a shell implementation, or equivalent in other runtimes). Deleting a non-existent lock file must be idempotent — always use rm -f (never rm alone).

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:

  1. 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 equals bundle-<E##_S##>.json).
  2. If no other manifests exist: skip steps 3–6 entirely — this step completes silently.
  3. For each other manifest file found: a. Parse the JSON and read its task_changed_files map. b. Collect the union of all file-path arrays across every key in task_changed_files into a flat, deduplicated set called other_touched_files. c. Record the other bundle's story_id (or derive it from the filename: bundle-<story_id>.json).
  4. Build the expected file set for the current bundle (current_expected_files): For each task in the current bundle's tasks: list: a. Read the task file at project/board/tasks/<task_id>_*.md. b. Extract file paths from the task's scope_rationale frontmatter field and from the full text of the task's ## Description section. 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 to current_expected_files (deduplicated set).
  5. Compute overlap: for each other bundle scanned in step 3, compute the intersection of other_touched_files and current_expected_files.
  6. If any overlap is found: a. Emit the following log line to the console (one line per conflicting bundle):
    [CROSS-BUNDLE CONFLICT] Bundle <current_story_id> and bundle <other_story_id> share expected files: <file1>, <file2>
    b. Store the conflict data in memory as pending_conflicts:
    {
      "<other_story_id>": ["<file1>", "<file2>"]
    }
    If multiple other bundles each have overlap, accumulate all of them under their respective story IDs in pending_conflicts.
  7. Pass pending_conflicts forward: when writing the bundle manifest in the next step (step 2 below), initialise the cross_bundle_conflicts key in the manifest with the contents of pending_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.

  1. 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 and mode:

    • Exit 3 (manifest refused: unparseable, wrong story, bad anchor_sha, or anchor_sha not an ancestor of HEAD): nothing was changed. Report the stderr cause verbatim and ask the operator:
      1. Discard the stale manifest and start fresh (rm -f project/queue/bundle-<E##_S##>.json, then re-run this step)
      2. Abort
      3. Other (describe below)
    • Exit 4: a story or task file is missing. Report it and stop.
    • mode: fresh with an all_terminal note: every task is already Passed/Merged and 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: capture anchor_sha, write the manifest as described in "### Bundle Manifest and Rollback Anchor" below, and iterate the whole tasks: list.
    • mode: resume: a manifest survived a halt (the failure and success paths both delete it), and its anchor_sha is still an ancestor of HEAD. Do not run git rev-parse HEAD, do not capture a new anchor_sha and do not write the manifest from scratch: the original anchor_sha and task_changed_files are the rollback anchor and the record of what completed. Append the current UTC timestamp to a resumed_at array 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's run list; skipped tasks are already Passed. Board status is authoritative, never the manifest. Show the operator skipped, run and any notes before 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).
  2. 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 Enforcement below, with <id> = this bundle's story id (<E##_S##>). b. On a full cap (capacity_blocked outcome): 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-task status: In Progress write in step 4a below hasn't run), so there is nothing to revert to Pending — they are still whatever they were before this bundle attempt (normally Pending already). Stop here; /jenga Phase 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 by j.check-in's liveness probe to join running agents to board items via ListAgents output. 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.

  3. 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 Progress and date_started: <today> to the task's frontmatter using the file-locking protocol:

    1. Locate the task file: project/board/tasks/<task_id>_*.md.
    2. 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).
    3. Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.
    4. Update the task frontmatter fields status: In Progress and date_started: <YYYY-MM-DD> (today's date).
    5. 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 Check below) for this task. Then write status: Passed and date_completed: <today> to the task's frontmatter using the file-locking protocol:

    1. 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.
    2. Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.
    3. Update the task frontmatter fields status: Passed and date_completed: <YYYY-MM-DD> (today's date).
    4. 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 the Passed write 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-bundle

    This call is best-effort and never blocks or fails a task: record-execution.sh warns on stderr and exits 0 on any ledger problem, and only a usage error is non-zero. It appends one line to the gitignored project/queue/execution-ledger.jsonl, which /break status reads 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.1 below) with this task's title: frontmatter value as <task title>. Run only the remove half here: teardown runs exactly once for the whole bundle, never per task (see step 6 and step 4d). remove exits 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 idempotent todo.md edit, 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:

    1. Run git diff --name-only HEAD~1 HEAD to 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.
    2. Read the bundle manifest at project/queue/bundle-<E##_S##>.json.
    3. Add or update the key task_changed_files in the manifest object. Set task_changed_files[<task_id>] to the captured file list (an array of strings, or [] if no files changed). If task_changed_files does not yet exist in the manifest, create it as an empty object first.
    4. 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.tmp to project/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:

    1. Extract expected files from the task's frontmatter field scope_rationale and from the task's ## Description section. 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 called expected_files. This is intentionally permissive — false positives (expected files that were never actually changed) are acceptable and produce no report.
    2. Compute unexpected files: let actual_files = the array stored at task_changed_files[<task_id>] in the bundle manifest (from step c.1). Compute unexpected = actual_files − expected_files (set difference: files in actual_files that have no match in expected_files). Matching is case-sensitive and exact against the relative path or the basename of the path — a token like SKILL.md matches any actual file whose basename is SKILL.md (e.g. skills/j-do/SKILL.md).
    3. If unexpected is non-empty: a. Write a Markdown conflict report to project/queue/conflict-<task_id>.md with the following structure:
      # 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>
      - ...
      b. Read the bundle manifest at project/queue/bundle-<E##_S##>.json. c. Append <task_id> to the conflict_reports array in the manifest. If the key conflict_reports does 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).
    4. If unexpected is 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 Passed status or halt bundle execution. It is informational only — a human can review project/queue/conflict-<task_id>.md after 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 already Passed and 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:
      1. Keep the bundle manifest at project/queue/bundle-<E##_S##>.json exactly as it stands. Its anchor_sha and task_changed_files are the rollback anchor and the record of what completed.
      2. 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.
      3. Leave the shared bundle-<E##_S##> worktree and its commits untouched.
      4. 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 now Passed), which were not started (the rest of the tasks: list), that the manifest and rollback anchor are intact, and that the epic lock and slot have been released.
    • 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 original anchor_sha, skips tasks already Passed on 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: Failed to the task's frontmatter using the file-locking protocol:

    1. 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.
    2. Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.
    3. Update the task frontmatter field status: Failed.
    4. 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.1 below) for this task's title:, 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.4 step 2 below). Then release the epic lock: run rm -f project/queue/epic-lock-<E##>.json. Then run step 2 (teardown) of the Task-Completion Cleanup Procedure (### 7.1 below) once, to delete project/todo.md if it is now effectively empty. Mark the bundle as failed and halt — do not execute any remaining tasks in the sequence.
  4. Post-bundle verification (runs only if all tasks completed without failure):
    • Inspect each task in the bundle for needs_docs: true in 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, write status: Passed and date_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.
  5. 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.4 step 2 below). Then release the epic lock: run rm -f project/queue/epic-lock-<E##>.json. Finally run step 2 (teardown) of the Task-Completion Cleanup Procedure (### 7.1 below) once, to delete project/todo.md if it is now effectively empty. Each task's entry was already removed in step 4c, so no remove runs here; the post-bundle Passed rewrite in step 5 re-writes a status whose entry is already gone and does not call remove again.

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

  1. Run git rev-parse HEAD to capture the current HEAD SHA as anchor_sha.
  2. Write project/queue/bundle-<E##_S##>.json with 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 by git rev-parse HEAD
    • started_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's tasks: frontmatter list
    • task_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 to project/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 the story_id of a conflicting bundle and each value is an array of overlapping file paths. Initialised with the pending_conflicts data 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.

  1. Save partial diff: run git diff <anchor_sha> HEAD and write the output to project/queue/bundle-<E##_S##>-partial.patch. This captures all commits made since the anchor, providing a record of partial work for diagnosis.
  2. Verify clean worktree: run git status --porcelain. If the output is non-empty, there are uncommitted changes that would cause git reset --hard to error or lose work. In this case, emit:
    ROLLBACK WARNING: worktree has uncommitted changes. Stashing before reset.
    Then run git stash before proceeding. This ensures the reset completes without git complaints.
  3. Reset to anchor: run git reset --hard <anchor_sha>. The repository returns to the exact state it was in before the bundle started.
  4. 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)

  1. 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:
    1. 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/.
    2. 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, Run bash "$([ -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.
  • 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-matter story_id matches 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:
    1. 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 in project/todo.md (tasks are discovered from it automatically). Go back to step 2.
  • 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:

  1. 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)
  2. If the file does not exist, warn the user and skip — do not proceed with a task that has no scrum board definition
  3. 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.

  1. Read jenga_assigned from the resolved task's frontmatter.
  2. 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.
  3. If jenga_assigned is false: a. Read override_justification from the task's frontmatter. b. If override_justification is absent or its value is an empty string, halt execution and emit:
    OVERRIDE VALIDATION ERROR [<task_id>]: jenga_assigned is false but override_justification is missing or empty. Add a justification before proceeding.
    Do not invoke the developer. Do not remove the task from project/todo.md. Return control to the user. c. If override_justification is present and non-empty, log:
    Override acknowledged for <task_id>: <override_justification>
    Then proceed to step 4.1.2 (the pre-task gate).

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 to Pending with ### 4.4 step 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>"} to project/logs/events.json (via scripts/events-append.sh, dual-path form as above). Never write capacity_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.

  1. 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.
  2. If --trivial is present, read the task's current execution_scope from frontmatter. Treat an absent value as task, per the epic's backward-compatibility rule (a task that omits execution_scope is treated as execution_scope: task). Call this the prior tier — this covers both tasks nobody flagged as trivial at creation (an already-assigned story/task/light tier) and legacy tasks with no execution_scope set at all.
  3. Overwrite execution_scope to inline in the task's frontmatter, unconditionally — --trivial always forces inline, never a softer "lightest safe tier."
  4. Record the override for audit, reusing the existing jenga_assigned / override_justification pairing 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 already false).
    • 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 convention skills/j-todo/scripts/add_trivial_task.sh already 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-CORRECTION message):
      TRIVIAL OVERRIDE [<task_id>]: execution_scope forced from "<prior_tier>" to "inline" via --trivial dispatch-time override.
  5. 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 /do invocation. This marker is distinct from an organically-assigned execution_scope: inline task (one the scrum-master or /jenga assigned inline to directly, with no --trivial involved) because its failure handling differs — see ### 4.2's failure-handling steps below. Do not confuse a --trivial-forced run with an organic inline task when applying those steps.
  6. Proceed to ### 4.2. Inline Execution Path with execution_scope now inline. Everything else about inline execution (implementation, smoke test invocation, commit convention) is identical between an organic inline task and a --trivial-forced one — only the two failure-handling branches in ### 4.2 differ, 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_scope currently reads. This guards against a locked task reaching dispatch with a non-inline execution_scope (a race, a manually edited file, or a task added to a story's tasks: list after skills/jenga/SKILL.md Phase 0.5's Rule 4 last ran).
  • If execution_scope is already inline, proceed directly to the inline steps below — no correction needed.
  • If execution_scope is anything other than inline (absent, task, story, or epic), auto-correct it to inline in the task frontmatter now, and record the correction using the same logged-note convention as skills/jenga/SKILL.md Phase 0.5 Rule 4 (do not invent a second, inconsistent logging mechanism):
    • Append to the task's override_justification frontmatter 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.
  • Under no circumstance does a crucial_level: locked task 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:

  1. Read the task file and load its full content (description, acceptance criteria). Do NOT create a worktree. Do NOT spawn a developer subagent.
  2. Before implementation begins — write status: In Progress and date_started: <today> to the task's frontmatter using the same file-locking protocol as ### 1.5 step 4a above:
    1. Locate the task file: project/board/tasks/<task_id>_*.md.
    2. 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).
    3. Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.
    4. Update the task frontmatter fields status: In Progress and date_started: <YYYY-MM-DD> (today's date).
    5. Delete the lock file immediately after the write completes.
  3. Implement the task inline — make the required changes to files directly in the current session.
  4. 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 from git diff --name-only HEAD. It exits 0 on pass and 1 on failure.
    • 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 a pass:
      WARNING [<task_id>]: smoke-harness.sh not found. Smoke test skipped (stub pass).
  5. If the smoke test exits non-zero:
    • If this is a --trivial-forced run (marker set in step 4.1.5 — and crucial_level is not locked, which never falls back, per 4.1.5's precedence note): do NOT write status: Failed. --trivial always forces inline with 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 Pipeline procedure below (origin: trivial). Do not proceed with the remaining inline steps below — the Fallback procedure takes over from here.
    • Otherwise (an organically-assigned inline task, --trivial not involved): behavior is unchanged from before —
      • Write status: Failed to 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.
  6. If the smoke test passes:
    • Commit the changes using the standard commit convention (task(<task_id>): <short description>) via /commit in inline mode (E32_S04_T03).
    • Run the Intent-vs-Diff Check (see ### 5.1. Intent-vs-Diff Check below) for this task.
    • Self-verify the implementation against the acceptance criteria.
    • Write status: Passed and date_completed: <today> to the task's frontmatter if verification passes.
    • Remove the task from project/todo.md.
  7. inline tasks do not invoke the tester agent — the smoke test and self-verification are the only gates.
  8. inline tasks always have needs_docs: false — skip plan and summary documentation for the implemented task.
  9. 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 (and crucial_level is not locked): invoke the shared #### Fallback to Full Task-Scope Pipeline procedure below (origin: trivial) — the same procedure the smoke-harness-failure branch above uses, not a second bespoke re-route.
  • Otherwise (an organically-assigned inline task): abort and re-route to the normal developer path (step 5), unchanged from before.
  • Regardless of --trivial, if crucial_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.

  1. Before spawning the developer subagent — write status: In Progress and date_started: <today> to the task's frontmatter using the same file-locking protocol as ### 1.5 step 4a above:
    1. Locate the task file: project/board/tasks/<task_id>_*.md.
    2. 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).
    3. Create the lock file: write the current ISO 8601 timestamp into project/board/tasks/<task_id>_*.md.lock.
    4. Update the task frontmatter fields status: In Progress and date_started: <YYYY-MM-DD> (today's date).
    5. Delete the lock file immediately after the write completes.
  2. Acquire a developer concurrency slot, then spawn a developer subagent. Acquire first, per ### 4.4. Developer Concurrency Slot Enforcement below (<id> = this task's id). On a full cap (capacity_blocked outcome), do not spawn anything — ### 4.4 already reverts this task's status to Pending, logs the capacity_blocked event, and tracks the consecutive-block count; stop here and let /jenga Phase 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 by j.check-in's liveness probe to join running agents to board items via ListAgents output. 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 task path: everything else about how the subagent implements the task (reading the task file, following acceptance criteria, following repo conventions) is unchanged.

  3. 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.4 step 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 from git diff --name-only HEAD. It exits 0 on pass and 1 on failure.
    • 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 a pass:
      WARNING [<task_id>]: smoke-harness.sh not found. Smoke test skipped (stub pass).
  4. 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 for inline scope in step 6 of ### 4.2. Since no tester runs, the developer/orchestrator is the one who writes the terminal status for a light-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 Check below) for this task.
    • Write status: Passed and date_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.
  5. If the smoke test fails (non-zero exit): do NOT write status: Failed and do NOT halt. Instead, invoke #### Fallback to Full Task-Scope Pipeline below (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.

  1. 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.
  2. 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 under light scope, or forced inline via --trivial, never had one — both premises skip worktree creation — so this step always creates a fresh worktree in that case.)
  3. Acquire a developer concurrency slot (per ### 4.4. Developer Concurrency Slot Enforcement below, <id> = this task's id). This fallback spawn is a distinct developer-subagent lifecycle from whatever light/trivial attempt preceded it — that attempt's own slot, if any, was already acquired and released around it (### 4.3 step 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_blocked outcome), 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 /jenga Phase 4 retries it on a later wave once a slot frees up.
  4. 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).
  5. Invoke the tester agent per the normal ### 5. Invoke the developer agent flow's contract — full sender object, commit SHAs, worktree path. The tester is responsible for the terminal status write, exactly as in the standard task-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.4 step 2).
  6. 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).
  7. 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. /do was 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 /do generates its own session_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 (see skills/jenga/SKILL.md Phase 0.9's jenga-<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. /do was invoked by another orchestrator that already has its own session identity and passes it along as part of the dispatch context — for example /jenga Phase 4 threading its own orchestrator_session_id (generated once in skills/jenga/SKILL.md Phase 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 /do MUST reuse that caller-supplied value verbatim as this invocation's session_id — it must never generate a new one. This is what lets every /do sub-agent a single /jenga run (or any other orchestrator) dispatches share one project/queue/concurrency-slots-<id>.json counter 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>.

  1. Acquire. Before spawning the developer subagent (or the bundle's shared developer subagent), run:
    "$([ -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>
    Per that script's own exit-code contract:
    • 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 — the developer role 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 a capacity_blocked outcome. Do NOT spawn anything. Handle it as follows: a. Revert the item's board status to Pending, using the same file-locking protocol already used elsewhere in this skill for status writes. b. Read the current holders snapshot for the developer role from project/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's wave value. d. Append one entry to project/logs/events.json with bash "$([ -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):
      {
        "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>"
      }
      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".
    • Exit 1, 2, or 5 — a usage error, a with-lock.sh timeout, or an environment error (missing jq, missing/invalid config). This is not a capacity block — the cap itself was never actually evaluated. Treat it as an ordinary /do error: do not spawn the subagent, do not write a capacity_blocked event, do not touch the consecutive-block counter, and fall through to the pre-existing failure→skip→Pending handling (skills/jenga/SKILL.md's Edge Cases: "/do failure (background agent) — treated as a skip; mark the item's status back to Pending..."). Report the script's stderr so the underlying script/lock/environment problem is visible, rather than silently reporting it as a capacity issue.
  2. Release — on every subagent exit path. Once the spawned developer subagent's session ends — whether it reports success, failure, or an error/exception — run:
    "$([ -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>
    This call is unconditional and must be reached from every branch that follows a successful acquire in step 1: the success path, the ordinary /do failure→skip→Pending path, and any other error/exception branch that aborts the calling section early. release-concurrency-slot.sh is 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.
  3. 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>.json under 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 different orchestrator_session_id, for the same reason scripts/acquire-concurrency-slot.sh's own header comment gives for its own counter file. Because /do is very often re-dispatched as a brand-new background subagent on every wave (per /jenga Phase 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 through scripts/with-lock.sh (mirroring project/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_blocked outcome (step 1's exit-3 branch): read this item's current count (default 0 if the key is absent), increment it by 1, and write the incremented value back. Use this same incremented value as the wave field in the capacity_blocked event (step 1d above).
      • Escalation. If the incremented count has reached 3: append one line to project/queue/scrum_triggers.jsonl, matching the existing trigger shape documented in templates/SCRUM_BOARD_SCHEMA.md's Queue Trigger Types section (the same {"type": ..., ..., "sender": {...}, "message": "..."} shape already used by story_rollup entries):
        {"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)."}
        Then reset this item's count back to 0 in the state file. This is a passive audit/escalation write only — it does not itself retry anything; retry keeps working purely via /jenga Phase 4's existing Pending-rescan-per-wave loop, unrelated to this trigger. capacity_starvation handling (surfacing a plain warning, no automatic remediation) is documented separately in agents/scrum-master.md's Drain Scrum Triggers Queue procedure (E32_S15_T06) — out of scope for this file.
    • 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.

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_blocked event is written, and the consecutive-block counter above is untouched. Retry works purely because the item is Pending again — 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's developer role was already at its configured cap (max_concurrent_developers in project/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 by j.check-in's liveness probe to join running agents to board items via ListAgents output. 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 with j.check-in implementation.
  • 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 text of each pre-task item whose action was remind (omit when there are none)

The developer agent will:

  • Log the incoming sender object to project/logs/events.json (via scripts/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 do

It 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:

  1. Read needs_docs from the task's frontmatter. If needs_docs: true (or absent and defaulting to true), skip to step 6.
  2. Run git diff --name-only HEAD~1 HEAD to retrieve the list of changed file names (relative paths, one per line).
  3. 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).
  4. Substitute the placeholders:
    • {description} — full text of the task's ## Description section
    • {acceptance_criteria} — full text of the task's ## Acceptance Criteria section
    • {changed_files} — newline-separated output from step 2
  5. Invoke the LLM with the constructed prompt (using the orchestrating agent's LLM access). Ask for a JSON array response only.
  6. 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: true to 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.

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.md must exist (written by the developer before starting work)
  • Execution summary — project/documentation/summaries/<E##_S##_T##>-summary.md must 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 /do wrote the status itself (inline and light self-verification) or the tester wrote it and /do observed it, read the task's final status from its frontmatter and, if it is Passed, Passed with remarks, Failed or Merged, run:
    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 do
    This call is best-effort and never blocks or fails a task: record-execution.sh warns on stderr and exits 0 on any ledger problem. It feeds /break status's "ran this session" marker.
  • Check for any _INSTRUCTIONS.md files in project/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 /commit skill to commit the work (if not already committed by the developer)
  • Run the Task-Completion Cleanup Procedure (### 7.1 below) with this task's title, to remove the completed task from project/todo.md and 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.

  1. 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>'
  2. Tear down if empty. Run:
    bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" teardown
    This deletes project/todo.md only 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 as mode=<soft|hard> with_status=<true|false> requested_at=<ts> requested_by=<who>. A soft break was consumed by that call; a hard break 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:

  1. Finished: the task that just completed and its status (or "none — nothing was started" when halting from the entry check).
  2. Remaining: the items still in project/todo.md; for a halted story bundle, the not-started tasks (the helper's run list) instead, since a bundle uses no todo.md entries.
  3. Mode: soft or hard.
  4. Resume: /proceed for a soft break; for a hard break, /break clear first, then /proceed. For a halted story bundle (the halt came from step 1.5 c.3), offer first resume the bundle (/do <E##_S##>) (after /break clear when the break is hard), and individual task dispatch with /do <E##_S##_T##> as the alternative. List completed (skipped), not started (run) and the intact anchor_sha by running 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##> and reading its JSON, never from memory.
  5. Scoped rapport: only when the record says with_status=true. Run bash "$([ -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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.