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

j.jenga

Skill0 starsChanged 22 days agoApache-2.0View on GitHub

Interactive-by-default board orchestrator with a fully automated escape hatch. Bare `/jenga` renders a picker and confirmation tree before scoping the run; `/jenga <ids>` resolves an explicit fuzzy-ID scope and confirms it; `/jenga *` reproduces the original zero-prompt behavior — decomposing any unbroken Epics into Stories, any unbroken Stories into Tasks, queuing all unqueued Tasks into todo.md, then executing every eligible item with no user prompts — until the board is fully started.

The file

Show its rules and text

We split this skill into the rules it gives an agent

25 rules. About 0 are common, 10 are distinctive, and 15 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈10

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 15

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the file does not exist, emit:

    line 38
  2. Distinctive:

    If the file is not valid JSON, emit:

    line 44
  3. Distinctive:

    Before accepting any task for decomposition or execution, the executing agent must validate the task's scope fields.

    line 60
  4. Very specific:

    If the task frontmatter contains an execution_scope field, its value must be one of: task, story, epic, inline.

    line 66
  5. Distinctive:

    If the value is anything else, halt immediately with:

    line 68
  6. Distinctive:

    Do not proceed with this task.

    line 74
  7. Very specific:

    If execution_scope is present, scope_rationale must also be present and must contain at least one digit (0–9) or the word "file" …

    line 78
  8. Very specific:

    If scope_rationale is absent, or present but contains no digit and does not contain the word "file", halt with:

    line 80
  9. Very specific:

    If execution_scope is "epic", the task must also have epic_scope_approval: true set explicitly in its frontmatter.

    line 90
  10. Very specific:

    If epic_scope_approval is absent or is not exactly true, halt with:

    line 92
  11. Very specific:

    If the task frontmatter contains crucial_level: locked (per $([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo …

    line 102
  12. Very specific:

    If the task frontmatter contains none of execution_scope, scope_rationale, needs_docs, jenga_assigned, or override_justification, treat the …

    line 124
  13. Distinctive:

    If all applicable rules pass (or the task is a legacy task), proceed to the next phase for that task without any error output.

    line 132
  14. Distinctive:

    No argument at all → bare branch.

    line 164
  15. Very specific:

    Wait for the user's chat reply, then invoke skills/jenga/scripts/render-picker.sh <state_file> "<raw_reply>" (continue mode).

    line 177
  16. Distinctive:

    After this phase completes (bare and scoped branches via confirmation, wildcard branch immediately), proceed to Phase 1.

    line 323
  17. Distinctive:

    Every wave dispatched by Phase 4 step 3, across every loop-back at Phase 4 step 5c.

    line 349
  18. Very specific:

    If Phase 0.75 produced a scoped set, restrict this phase to epics that are members of that set (directly selected, or flagged in its …

    line 362
  19. Very specific:

    Read all files in project/board/epics/.

    line 364
  20. Very specific:

    If Phase 0.75 produced a scoped set, restrict this phase to stories that are members of that set (directly selected, expanded from an …

    line 370
  21. Very specific:

    Read all files in project/board/stories/.

    line 372
  22. Very specific:

    If Phase 0.75 produced a scoped set, restrict this phase to tasks that are members of that set (directly selected, or expanded from an …

    line 378
  23. Very specific:

    Read all files in project/board/tasks/.

    line 380
  24. Very specific:

    After this phase, todo.md reflects the full set of in-scope work (or the full board, under *).

    line 382
  25. Distinctive:

    Before dispatching individual tasks in Phase 4, check each story for bundle eligibility.

    line 386

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

Jenga — Auto-Implementation Orchestrator

Purpose

/jenga is interactive by default — it never silently commits to the whole board without showing the user what it's about to run and letting them scope or edit that selection first. It has three entry modes:

  • Bare /jenga (no argument) — renders a numbered picker of the full board, then an editable confirmation tree, before anything executes.
  • /jenga <ids> (explicit comma-separated scope) — resolves the given IDs via the fuzzy-ID grammar, skipping the picker, then still shows the same editable confirmation tree before executing.
  • /jenga * (literal wildcard) — the explicit escape hatch. Skips both the picker and the confirmation step entirely and reproduces the original hands-free "commit to everything on the board" pipeline: the entire board is fully decomposed, fully queued, and fully executing — without any user interaction, no user prompts. This is the only path where /jenga runs with no user prompts at all.

Once a run's scope is established (by confirmation, or unconditionally under *), /jenga runs the same underlying phases against that scope: entry mode resolution → decompose → queue → execute → loop.

Instructions

Phase 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.

Phase 0.5 — Pre-flight Validation

Before accepting any task for decomposition or execution, the executing agent must validate the task's scope fields. The threshold values loaded in Phase 0 may be referenced in error messages for context, but are not required for the core validation rules below.

For each task read from the board, apply the following checks in order:

Rule 1 — Valid execution_scope value

If the task frontmatter contains an execution_scope field, its value must be one of: task, story, epic, inline.

If the value is anything else, halt immediately with:

VALIDATION ERROR [<task_id>]: execution_scope "<value>" is not a valid scope. Allowed: task, story, epic, inline.

Do not proceed with this task.

Rule 2 — scope_rationale must contain a measurable claim

If execution_scope is present, scope_rationale must also be present and must contain at least one digit (0–9) or the word "file" (case-insensitive).

If scope_rationale is absent, or present but contains no digit and does not contain the word "file", halt with:

VALIDATION ERROR [<task_id>]: scope_rationale is missing or lacks a numeric/file-count claim. Provide a rationale that includes a digit (e.g. "touches 2 files") or the word "file".

Do not proceed with this task.

Rule 3 — epic scope requires explicit human approval

If execution_scope is "epic", the task must also have epic_scope_approval: true set explicitly in its frontmatter. A missing epic_scope_approval field and a value of false are both rejection conditions.

If epic_scope_approval is absent or is not exactly true, halt with:

VALIDATION ERROR [<task_id>]: execution_scope=epic requires epic_scope_approval: true (set by human operator). This field must be added manually — it is never assigned autonomously.

Do not proceed with this task.

Rule 4 — crucial_level: locked forces execution_scope: inline

If the task frontmatter contains crucial_level: locked (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), execution_scope for that task MUST be inline — only the current foreground/inline session can pause mid-run for a live confirmation; a backgrounded subagent has no live channel back to the user.

This rule auto-corrects and continues; unlike Rules 1-3, it never halts.

  • If execution_scope is present and its value is anything other than inline, correct it to inline directly in the task file, and record a logged note of the correction by appending to that task's override_justification frontmatter field (the auditable mechanism for this rule — do not use events.json or any other location) a line of the form:

    override_justification: "Rule 4 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".
  • If execution_scope is absent entirely, set it to inline directly in the task frontmatter. Do not fall through to the Backward-compatibility default of execution_scope: task documented immediately below — a locked item overrides that default even when no other execution-scope fields are present. No override_justification note is required in this case, since there is no prior value being overridden.

Proceed to the next rule (or the next phase, if this was the last applicable rule) after applying the correction.

Backward compatibility — legacy tasks

If the task frontmatter contains none of execution_scope, scope_rationale, needs_docs, jenga_assigned, or override_justification, treat the task as a legacy task:

  • Assume execution_scope: task
  • Assume needs_docs: true
  • Skip all three rules above and proceed without error.

Validation success

If all applicable rules pass (or the task is a legacy task), proceed to the next phase for that task without any error output.


Phase 0.75 — Entry Mode Resolution

This phase determines how /jenga was invoked and, for two of the four entry modes, produces a scoped set — a confirmed list of board IDs (epics/stories/tasks) that Phases 1-4 must restrict themselves to. All board scanning, ID parsing, cascade expansion, and rendering used by this phase already live in skills/jenga/scripts/ per this repo's "Scripts Over Inline Logic" principle — this phase never re-implements any of that logic inline. The executing agent's job here is limited to: invoking the right script with the right arguments, relaying its STDOUT verbatim to the user when the contract calls for that, capturing the STATE_FILE: line from STDERR for the next turn, and forwarding the user's raw reply back into the next invocation unmodified.

First, check for the --enrich flag (E53_S13_T01, ported from the now-retired /route's board

  • docs enrichment — see the Natural-language branch's step 3 and the Skill Matching & Invocation Contract's Report format below for what it actually does). If the raw argument passed to /jenga begins with the exact leading token --enrich followed by at least one space, strip that token (and the single space after it) before anything else runs, and remember enrichment_requested = true for the remainder of this invocation. The remaining text — never the original argument with the flag still attached — is what every step below (including detect-nl-intent.sh) treats as "the raw argument passed to /jenga"; this is what keeps the flag from ever being visible to detect-nl-intent.sh's all_resolved/nl_intent/mixed classification. If --enrich is not present as a leading token, enrichment_requested = false and the argument is used as-is — byte-for-byte the same behavior as before this flag existed.

--enrich alone (nothing after it, once whitespace is stripped) reduces to an empty remaining argument — treat this exactly like no argument at all (bare branch); the flag has nothing to enrich without free-form text and bare /jenga never does skill matching. --enrich * reduces to the wildcard branch the same way (remaining text is the literal *); the wildcard branch also never does skill matching, so enrichment_requested is simply never consulted there. Concretely, enrichment_requested is only ever read in the natural-language branch below, and only once that branch's own step 3 confirms a single-skill match — it is inert everywhere else. Passing --enrich on a scoped (<ids>) argument is likewise a no-op: the scoped branch does not perform skill matching either, so there is nothing for the flag to attach to.

Then, determine the invocation form from the remaining argument (if any) passed to /jenga:

  • No argument at all → bare branch.
  • The argument is the literal string * → wildcard branch.
  • Any other non-empty argument → invoke skills/jenga/scripts/detect-nl-intent.sh "<remaining argument>" (E53_S01_T01) and branch on its classification field:
    • all_resolved or mixed → scoped branch (below) — this is the same branch as before; only its internal mechanics changed (see below).
    • nl_intent → natural-language branch (below) — new for E53_S01, no new sigil or entry point, purely a new outcome of this same argument-shape detection.

Wildcard branch (/jenga *)

Skip both the picker and the confirmation step entirely. There is no scoped set — proceed to Phase 1 unrestricted, exactly as /jenga behaved before this phase existed.

Bare branch (/jenga)

  1. Invoke skills/jenga/scripts/render-picker.sh with no arguments (start mode). Relay its STDOUT (the numbered checklist) to the user verbatim — no summarizing, no reformatting. Capture the STATE_FILE: path from STDERR.
  2. Wait for the user's chat reply, then invoke skills/jenga/scripts/render-picker.sh <state_file> "<raw_reply>" (continue mode).
    • Error turn (plain text on STDOUT, state file retained) — relay verbatim and return to step 2 for another reply.
    • Cancellation — relay the cancellation acknowledgement and halt the entire /jenga run; do not proceed to any later phase.
    • Resolved (JSON object on STDOUT, state file removed) — extract resolved_ids_csv and continue to the shared confirmation step below.

Scoped branch (/jenga <ids>)

This branch is entered when detect-nl-intent.sh (invoked above) classifies the argument as all_resolved or mixed — the picker is skipped entirely in this branch. detect-nl-intent.sh has already invoked resolve-id.sh internally and reduced its per-segment output to one of these two shapes; skills/jenga/SKILL.md never parses resolve-id.sh's raw array itself (see detect-nl-intent.sh's own header comment for the full classification contract, E53_S01_T01).

  1. On all_resolved, take the resolved_ids (or resolved_ids_csv) field directly from detect-nl-intent.sh's output and continue to the shared confirmation step below.
  2. On mixed, halt this phase (do not proceed to confirmation or Phase 1) and report each entry in detect-nl-intent.sh's rejected array — its input and reason — to the user verbatim; a partial or ambiguous ID is never guessed. The user must re-invoke /jenga <ids> with corrected input.

Natural-language branch (/jenga <free-form text>)

This branch is entered when detect-nl-intent.sh classifies the argument as nl_intent — every comma-delimited segment failed the ID grammar, so the raw argument is treated as natural-language intent rather than a malformed ID list. This is purely a new outcome of the same argument-shape detection above — no new sigil, trigger prefix, or separate entry point is introduced.

  1. Load the catalog — invoke skills/jenga/scripts/load-nl-catalog.sh with no arguments (E53_S01_T02). Its stdout is the full skill catalog (name/description/keywords/examples/prefered_agent per skill), sourced exclusively from lib/generate-skill-allow-list.js's generated inventory — see the script's own header for the full contract. Never re-derive this catalog by re-scanning skills/ inline.
  2. Match — run this section's own Skill Matching & Invocation Contract (below) — the three-pass keyword → example-similarity → description match, including its tie-break and no-match handling — against this catalog, treating detect-nl-intent.sh's raw_argument field as the prompt.
  3. Confident single match — if enrichment_requested is true (the --enrich flag was passed, per Phase 0.75's preamble above), first invoke skills/jenga/scripts/enrich-nl-prompt.sh "<raw_argument>" (E53_S13_T01, ported from /route's Steps 3-5) and assemble the enriched composite message from its JSON output, in this exact order:

    • Part A — Matched skill (full content) — the matched skill's full SKILL.md body (everything after its YAML front-matter), wrapped as <!-- SKILL: /<matched-skill-name> --> ... <!-- END SKILL -->.
    • Part B — Board & documentation context — a ## 📋 Relevant Board Context list (one line per board_items entry: - [<status>] **<id>** — <title> (\<file>`)) followed by a ## 📄 Relevant Documentationlist (one line perdocsentry: `` -<path>` — <summary> ``). Omit either sub-list entirely (not an empty heading) when its array is empty.
    • Part C — Original prompt — ## 🗣 Original Prompt followed by the raw prompt, verbatim, in a blockquote.

    Then report the routing decision using the Skill Matching & Invocation Contract's Report format (including its Board items found/Docs found lines, populated from enrich-nl-prompt.sh's board_items_found/docs_found fields, since enrichment_requested is true here), and invoke the matched skill exactly as the Invoke rule already does — but deliver the enriched composite message as the working input instead of the raw prompt when enrichment ran. When enrichment_requested is false (the default, unflagged path — unchanged from before this flag existed), skip enrich-nl-prompt.sh entirely: report using the Report format's default (no Board items found/Docs found lines) and invoke the matched skill directly with the raw prompt, exactly as before. Either way: load agents/<prefered_agent>.md when the matched skill specifies metadata.prefered_agent, otherwise execute the skill instructions directly. The matched skill's own execution takes over from here — do not continue into this /jenga invocation's Phase 1.

  4. No match, or an ambiguous multi-way tie (single-skill match) — before surfacing the Skill Matching & Invocation Contract's generic disambiguation options, attempt a playbook fallback (E53_S02): invoke skills/jenga/scripts/match-playbook.sh "<raw_argument>". This step only ever runs when step 3 above did NOT already commit to a confident single-skill match — a confident single-skill match always wins outright and this playbook fallback is never even invoked in that case. Branch on match-playbook.sh's classification field:
    • playbook_match → continue to step 5 (Playbook proposal and execution) below.
    • ambiguous or no_match → continue to step 6 (Fall through to the Skill Matching & Invocation Contract's disambiguation) below — the exact behavior this branch already had before E53_S02, unchanged.
  5. Playbook proposal and execution — entered only on a playbook_match result from step 4. A proposed playbook is an ordered chain of skills (e.g. the canonical brainstorm -> j.todo -> j.do -> j.dev-done -> j.mirror-public chain defined in skills/jenga/playbooks/brainstorm-to-mirror.json) that must be confirmed, editable, and confirmable per CLAUDE.md's Interaction Pattern before any step executes — the same confirm-before-execute posture /jenga already applies to the bare/scoped branches via render-confirmation.sh. a. Resolve conditional metadata (E53_S04_T02/T04) — before rendering, inspect the matched playbook's own steps array (as returned by load-playbooks.sh's catalog, not the flattened string list) for any StepObject carrying a conditional: {"depends_on": "<name>", "predicate": "..."} field. Build a JSON object mapping each such step's name to its depends_on step's name only (e.g. {"stepC": "stepA"} — the confirmation display needs no predicate detail, only which step to point at). If no step carries a conditional, this object is empty/omitted. Also resolve composition origin metadata (E53_S05_T01/T03) — inspect the SAME steps array (match-playbook.sh's steps field is already load-playbooks.sh's fully FLATTENED, composition-resolved catalog output — composition is invisible to match-playbook.sh itself; there is nothing further to "flatten" at this point, only to read) for any StepObject carrying _origin_playbook/_origin_depth (present only on steps whose _origin_depth is greater than 1 — see load-playbooks.sh's header "COMPOSITION RESOLUTION"). Build a JSON object mapping each such step's name to {"playbook_id": "<_origin_playbook>", "depth": <_origin_depth>}. If no step in this playbook came from a composed/nested playbook, this object is empty/omitted. b. Render and confirm the chain — invoke skills/jenga/scripts/render-playbook-confirmation.sh "<playbook_id>" "<name>" "<comma-separated steps>" ["<json-conditionals from 5a>" ["<json-origins from 5a>"]] (start mode, using match-playbook.sh's playbook_id/name/steps fields verbatim; the 4th argument is omitted entirely when 5a produced no conditionals, and the 5th argument is omitted entirely when 5a produced no composition-origin metadata — omitting both reproduces the exact pre-E53_S04 3-arg call, and omitting only the 5th reproduces the exact pre-E53_S05 4-arg call). Relay STDOUT (the numbered chain + instructions, now marking any conditional step with "(may be skipped depending on step N's result)" and any composed/nested step with indentation plus "(from playbook: <id>, depth N)") to the user verbatim. Capture the STATE_FILE: path from STDERR. c. Wait for the user's chat reply, then invoke skills/jenga/scripts/render-playbook-confirmation.sh <state_file> "<raw_reply>" (continue mode).
    • Toggle or error turn (plain text on STDOUT, state file retained) — relay verbatim and return to step 5c for another reply. This loops exactly as the existing bare/scoped confirmation flow's own toggle/error turns already do.
    • Cancellation — relay the cancellation acknowledgement and halt the entire /jenga invocation immediately, with no step executed — identical posture to the existing picker/confirmation cancellation edge cases already documented for the bare/scoped branches (see "## Edge Cases" below).
    • Confirmed (JSON object on STDOUT, state file removed) — take the confirmed steps array (checked-only, in original playbook order) and continue to step 5d. d. Initialize the sequential runner — re-derive the <json-conditionals> object for run-playbook-step.sh init from the same StepObject data as 5a, but in init's own richer shape ({"<step>": {"depends_on": "...", "predicate": "..."}}, E53_S04_T02), and apply one filtering rule first: drop any conditional whose depends_on step is not present in the CONFIRMED step list from 5c. The user may have unchecked the depended-on step at confirmation; init would otherwise reject such a conditional outright (its existence check requires depends_on to be an earlier step in the run), and the more sensible fallback than a hard failure over the user's own edit is to treat that now-conditionless step as always-running for this particular execution. Also re-derive the composition origin metadata (E53_S05_T04) for init's optional 5th argument, from the same _origin_playbook/_origin_depth data as 5a, filtered the same way (drop any entry for a step the user unchecked at confirmation — init requires every key to be present in the confirmed step list). Invoke skills/jenga/scripts/run-playbook-step.sh init "<playbook_id>" "<name>" "<comma-separated confirmed steps>" ["<json-conditionals, filtered>" ["<json-origins, filtered>"]] (the 4th argument is omitted when empty, and the 5th is omitted when empty — omitting both reproduces the exact pre-E53_S04 3-arg call, and omitting only the 5th reproduces the exact pre-E53_S05 4-arg call). Its step_ready result names the first step to invoke. e. Execute steps in a loop — for the step named by the runner's most recent step_ready result: i. Evaluate whether this step should run (E53_S04_T02) — invoke skills/jenga/scripts/run-playbook-step.sh should-skip <state_file>.

      • {"skip": true, ...} — do not invoke the step. Call skills/jenga/scripts/run-playbook-step.sh advance <state_file> skipped directly (never passed/failed for a step that never ran), then handle its result exactly as iv-vi below (step_ready/complete/halted) — skip step 5e-ii and 5e-iii entirely for this step, since it never runs. This is non-blocking and non-failing — it never halts the chain, and is not narrated to the user turn-by-turn (it is folded into the final completed/skipped summary reported at complete/halted, the same posture already given to individual passed steps, which also aren't separately narrated mid-chain).
      • {"skip": false, ...} — proceed to step 5e-ii and invoke the step normally. ii. Resolve forward_from and resolve, if the step declares either (E53_S03, runtime-wired by E53_S04_T02; resolve runtime behavior added by E53_S06_T01) — invoke skills/jenga/scripts/run-playbook-step.sh get-output <state_file> <named source step> before invoking this step. On {"status": "found", "value": "..."}, use that value as this step's actual invocation input (per the design's forwarding semantics). On {"status": "unavailable", "reason": "step_skipped"} or "reason": "not_captured" — the named source produced no usable value (it was itself skipped, or never captured one) — do not guess a fallback value; treat this exactly like a step failure: call advance <state_file> failed "forward_from source '<name>' unavailable (<reason>)" and follow the halted handling in 5e-vi below, without ever invoking this step. This is the documented, non-silent failure mode for a forward_from naming a skipped step (E53_S04_T06's fixture coverage).

      Then, if the step also declares a non-empty resolve field (E53_S06_T01) — this is a documented, named, scoped exception to CLAUDE.md's "Skill Implementation Principle — Scripts Over Inline Logic": open-ended reshaping/filtering/type-bridging genuinely needs the agent's own LLM judgment, which a deterministic script cannot provide, and resolve is never used for anything else in this codebase's playbook mechanism — in particular, never to pre-authorize a downstream confirmation, a combination load-playbooks.sh already rejects outright at load time (see project/documentation/skill-authoring.md's "The resolve / confirmation-gate rule"):

      • No-op without forward_from — a step carrying resolve but no forward_from (or one that resolved to no forwardable value) has nothing to reshape. This is a defined, non-crashing runtime behavior, not an error: the resolve field is simply ignored for that step, and invocation proceeds exactly as it would with no resolve field at all.
      • Apply the transform — when both forward_from (successfully resolved immediately above) and resolve are present, use your own LLM judgment to reshape/filter/type-bridge the forwarded value per resolve's natural-language instructions (e.g. "pick the first three items", "convert this file_list to a text summary"). The transformed value — never the raw forwarded value — becomes this step's actual invocation input.
      • Hard-fail, never silent pass-through — if the transform cannot cleanly produce a usable, type-compatible result (the instructions don't plausibly apply to the actual value, the value is empty/malformed for what's being asked, or the result would not plausibly satisfy the target step's expected input shape), do not invoke this step and do not guess or pass through a differently-shaped value. Instead call skills/jenga/scripts/run-playbook-step.sh advance <state_file> failed "<note>", where <note> follows the format resolve failed on step '<step name>': could not apply "<resolve text>" to raw value <raw pre-transform value> — <short reason> (the raw pre-transform value is always included, for debugging). Then follow the halted handling in 5e-vi below exactly as any other step failure — immediately stop executing further steps, report failed_step/failed_note/completed/skipped/never_run verbatim.

      Then invoke the step exactly as this section's Skill Matching & Invocation Contract's Invoke rule already does for a single matched skill: load agents/<prefered_agent>.md when that step's own SKILL.md specifies metadata.prefered_agent, otherwise execute its instructions directly. iii. After a normally-invoked step's execution concludes, call skills/jenga/scripts/run-playbook-step.sh advance <state_file> passed ["<typed-output-value>"] (the step completed successfully — supply the step's declared typed output, per its output_types, if it produced one) or ... advance <state_file> failed "<short failure note>" (the step failed). iv. On a step_ready result, repeat step 5e for the newly-named step. v. On a complete result, report the full lists of completed AND skipped steps to the user and stop — the playbook run is finished; do not continue into this /jenga invocation's Phase 1. vi. On a halted result, immediately stop executing any further steps — no silent skip-ahead. Report failed_step, failed_note, completed, skipped (steps that already finished or were skipped), and never_run (steps that never got a chance to run) to the user verbatim from the halt report. Do not continue into this /jenga invocation's Phase 1.

  6. Fall through to the Skill Matching & Invocation Contract's disambiguation — entered when step 4 found no playbook match (ambiguous or no_match). Surface the same disambiguation options this section's Skill Matching & Invocation Contract already defines for these cases (browse /help, create a new skill via /btw, or proceed with the raw prompt). Halt this /jenga invocation once the user picks an option; none of Phase 0.75's remaining steps or Phases 1-4 run for this branch.
Skill Matching & Invocation Contract

/jenga is the sole owner of this contract (E53_S13_T02). /route — which previously carried an independent, hand-synced copy of this same three-pass matching/tie-break/report logic in its own Step 2/6/7 — has been retired outright (hard break, no shim; see E53_S13's story). There is no other copy of this contract anywhere in the codebase to keep in sync with; if you change the matching logic here, this section is the only place that needs updating.

Matching (three passes, stop at first confident match):

  • Pass 1 — Keyword Match — check whether any phrase from a skill's keywords list appears verbatim (case-insensitive) in the prompt.
  • Pass 2 — Example Similarity — compare the prompt against each skill's examples list as a semantic similarity check; pick the skill whose examples most closely reflect the intent of the prompt.
  • Pass 3 — Description Match — if no clear winner has emerged, compare the prompt against each skill's description field; pick the skill whose description best captures what the user is trying to do.

Tie-break (two or more skills score equally) — present the top candidates and ask the user to choose:

More than one skill matches your prompt. Which should I apply?
1. /<skill-a> — <one-line description>
2. /<skill-b> — <one-line description>
3. Neither — describe what you need

No match — inform the user and offer:

No matching skill found for: "<prompt>"
Would you like to:
1. Browse all available skills (/help)
2. Create a new skill for this use case (/btw)
3. Proceed without a skill (raw prompt)

Invoke — deliver to the appropriate agent: if the matched skill specifies metadata.prefered_agent, load that agent's definition from agents/<prefered_agent>.md and pass it the full context; otherwise execute the skill's instructions directly. Do not summarise or restate the matched skill's instructions — deliver them as-is.

Report — before invoking, briefly inform the user:

Routing to: /<matched-skill-name>
Reason: <one sentence explaining why this skill was chosen>

Board items found/Docs found (opt-in, E53_S13_T01) — these two lines are appended to the Report above, in this order, only when the natural-language branch's enrichment_requested is true (i.e. the caller passed --enrich, per Phase 0.75's preamble):

Board items found: <count>
Docs found: <count>

<count> is enrich-nl-prompt.sh's board_items_found/docs_found field respectively (the total match count before the top-5/top-3 cap, not the number of items actually listed in the enriched prompt's Part B). When enrichment_requested is false — the default path, unchanged from before this flag existed — neither line is emitted; the Report is exactly the two lines above and nothing more.

Then proceed immediately — do not wait for user confirmation unless the match was ambiguous (the tie-break above already handled that).

Shared confirmation step (bare and scoped branches only)

  1. Invoke skills/jenga/scripts/render-confirmation.sh "<comma-separated resolved ids from whichever branch above>" (start mode). Relay STDOUT (the confirmation tree) to the user verbatim. Capture the STATE_FILE: path from STDERR.
  2. Wait for the user's chat reply, then invoke skills/jenga/scripts/render-confirmation.sh <state_file> "<raw_reply>" (continue mode).
    • Toggle or error turn (plain text on STDOUT, state file retained) — relay verbatim and return to step 2 for another reply.
    • Cancellation — relay the cancellation acknowledgement and halt the entire /jenga run; do not proceed to any later phase.
    • Confirmed (JSON object on STDOUT, state file removed) — this is the final scoped set. Take resolved_ids (or resolved_ids_csv) as the exact set of board IDs Phases 1-4 restrict themselves to for the rest of this run.
  3. Handoff to cascade resolution — do not invoke cascade-resolve.sh again here. render-confirmation.sh already invoked it internally to build the tree, and the CONFIRMED JSON's own undecomposed field is that same result already scoped down to the checked-only set. Use that undecomposed field directly to identify which epics/stories in the scoped set still need Phase 1/2 decomposition.

After this phase completes (bare and scoped branches via confirmation, wildcard branch immediately), proceed to Phase 1.


Phase 0.9 — Generate the Run's Shared orchestrator_session_id

This phase runs for every entry mode that reaches Phase 1 at all — bare, scoped, and wildcard (*) — immediately after Phase 0.75 completes and before Phase 1 begins. It does not run for the natural-language branch's playbook/single-skill-match outcomes (E53_S01/E53_S02), since those hand off entirely to another skill or playbook and never reach Phase 1-4 of this document.

Generate exactly one orchestrator_session_id for this entire /jenga run, here and only here. Both Phase 3.5's bundle dispatch and Phase 4's first (and every later) wave dispatch need this same value, which is why it is generated once, early, before either call site runs.

Format. Generate it as jenga-<UTC ISO 8601 basic-format timestamp> (e.g. jenga-20260917T162349Z) — a short prefix naming the orchestrating skill, plus a UTC timestamp, with no / or .. characters. This follows the same session-id string convention this repo already uses for other orchestrator-minted session ids (e.g. /do's own dispatch sessions), and stays compatible with scripts/acquire-concurrency-slot.sh's own validation, which refuses a <session_id> argument containing a path separator or traversal sequence, because the value is used verbatim in filenames such as project/queue/concurrency-slots-<session_id>.json.

Generate once, reuse everywhere in this run — never regenerate. This value MUST be reused, byte-for-byte unchanged, across:

  • Phase 3.5 step 7b's bundle /do <E##_S##> call.
  • Every wave dispatched by Phase 4 step 3, across every loop-back at Phase 4 step 5c. The loop-back never mints a new value — it only ever references the one value generated here.

This is what allows E32_S15's per-session max_concurrent_developers/max_concurrent_testers cap (skills/j-do/SKILL.md's ### 4.4) to actually contend across an entire /jenga run's dispatches, instead of every spawned /do sub-agent minting its own private orchestrator_session_id and getting its own private, uncontended project/queue/concurrency-slots-<id>.json counter file — the root-cause defect this story (E32_S16) fixes.


Phase 1 — Decompose Epics into Stories

If Phase 0.75 produced a scoped set, restrict this phase to epics that are members of that set (directly selected, or flagged in its undecomposed list). Under /jenga *, this phase is unrestricted, exactly as before.

Read all files in project/board/epics/. For each in-scope Epic that has no corresponding story files in project/board/stories/ (i.e. no files whose name starts with that Epic's ID), invoke /do via a scrum-master sub-agent to break it down into Stories.

Repeat until every in-scope Epic has at least one Story on the board.

Phase 2 — Decompose Stories into Tasks

If Phase 0.75 produced a scoped set, restrict this phase to stories that are members of that set (directly selected, expanded from an in-scope epic, or flagged in its undecomposed list). Under /jenga *, this phase is unrestricted, exactly as before.

Read all files in project/board/stories/. For each in-scope Story that has no corresponding task files in project/board/tasks/ (i.e. no files whose name starts with that Story's ID), invoke /do via a scrum-master sub-agent to break it down into Tasks.

Repeat until every in-scope Story has at least one Task on the board.

Phase 3 — Queue all Tasks into todo.md

If Phase 0.75 produced a scoped set, restrict this phase to tasks that are members of that set (directly selected, or expanded from an in-scope epic/story). Under /jenga *, this phase is unrestricted, exactly as before.

Read all files in project/board/tasks/. For every in-scope Task not already listed in project/todo.md, append its ID (and title as a comment) to project/todo.md.

After this phase, todo.md reflects the full set of in-scope work (or the full board, under *).

Phase 3.5 — Story-bundle detection

Before dispatching individual tasks in Phase 4, check each story for bundle eligibility. This phase runs once after Phase 3 completes.

If Phase 0.75 produced a scoped set, restrict this phase to stories that are members of that set (directly selected, or expanded from an in-scope epic) — a story with tasks sitting in todo.md from an earlier, differently-scoped run but that is not a member of the current run's scoped set is skipped entirely by this phase (not considered for bundling, and not dispatched via the bundle path) so that Phase 4's own scoped-set exclusion is never bypassed by a bundle call issued here. Under /jenga *, this phase is unrestricted, exactly as before.

For each in-scope story that has one or more tasks listed in todo.md:

  1. Read the story file — parse the tasks: frontmatter array to get the ordered list of task IDs.
  2. Guard: empty task list — if the tasks: list is empty (zero entries), this story is not eligible for the bundle path. Skip to per-task dispatch in Phase 4.
  3. Read each task file — for every task ID in the tasks: list, read the corresponding task file from project/board/tasks/.
  4. Collect execution_scope — extract the execution_scope field from each task's YAML frontmatter. If the field is absent or has any value other than story, treat that task as not story-scoped.
  5. Guard: locked-task disqualifier (defense-in-depth) — for each task file already read in step 3, also read crucial_level (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 any task in the story's tasks: list has crucial_level: locked, this story is not eligible for the bundle path — skip to per-task dispatch in Phase 4 for this story, regardless of that task's execution_scope value, even if it already reads inline. This check is defense-in-depth alongside Phase 0.5's Rule 4 (which forces a locked task's own execution_scope to inline when Rule 4 processes it): it exists for the race window where Rule 4 hasn't (yet) corrected the task — e.g. the task was added to the story's tasks: list after Rule 4 last ran, or the file was edited by hand after validation. It is not a replacement for Rule 4.
  6. Apply the all-or-nothing rule — a story qualifies for the bundle path only if every task in its tasks: list has execution_scope: story. A single task with a different scope (or a missing field) disqualifies the entire story.
  7. Route bundle candidates — if all tasks in the story are execution_scope: story and the list is non-empty: a. Emit:
    BUNDLE DETECTED: story <E##_S##> — <N> story-scoped tasks will execute as a bundle.
    where <E##_S##> is the story ID and <N> is the count of tasks in the list. b. Call /do <E##_S##> once (with the story ID, not individual task IDs), passing this run's shared orchestrator_session_id (generated once in Phase 0.9) into the bundle's sender object as its session_id field — a caller-supplied session id per skills/j-do/SKILL.md's standalone-vs-caller-supplied contract (### 4.4/### 5, E32_S16_T02), so the bundle path's own ### 4.4 slot acquire shares this run's one counter file too, rather than minting a private one. This invokes the bundle execution path in /do (implemented in E32_S05_T02), which runs all tasks sequentially in one shared worktree. c. Mark these tasks as bundled — record their task IDs so Phase 4 skips individual dispatch for them.
  8. Non-bundle stories — stories with a mixed scope, a zero-length task list, any task missing execution_scope: story, or any task with crucial_level: locked (step 5) use the normal per-task dispatch in Phase 4 without any change.

Phase 4 — Execute

Loop through todo.md and execute all eligible items, running independent ones in parallel. Use the threshold values loaded in Phase 0 (inline_max_files, inline_max_lines, story_max_files, bundle_lock_ttl_minutes) when applying execution-scope logic to each task. Skip any task that was bundled in Phase 3.5 — those tasks will be handled by the /do story-bundle call already issued.

Break entry check (E44_S02). A hard break set before this run started must stop it before the first wave; the check in step 5 only fires after a wave completes. Once, before the first pass of step 1, and 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

A mode=hard record (exit 10) → collect nothing, dispatch nothing, and print the break exit message from the Exit condition section with "nothing was started". Exit 0, a mode=soft record, or any other exit → continue. A soft break armed before the run is deliberately left in place and is honoured by the check in step 5 after the first wave. Like the rest of Phase 4 this prompts for nothing.

  1. Collect eligible items — from todo.md, find all items whose board file has status: Pending and no unresolved dependencies, excluding tasks already dispatched as part of a story bundle in Phase 3.5. If Phase 0.75 produced a scoped set, also exclude any item not a member of that set — execution never runs outside the confirmed/resolved scope. Under /jenga *, no such exclusion applies. A dependency is resolved if the blocking item's status is at least In Progress or Passed. Also exclude every item in this run's deferred set (built in step 5b; empty on the first wave) — a unit /do deferred at its pre-flight gate is back to Pending, so without this exclusion it would be re-collected and re-dispatched every wave. A deferred item_id may be a task id or, for a Story-Bundle, a story id: for a story id, exclude the story itself and every task belonging to that story (its member tasks must not be re-collected individually either). This exclusion is run-scoped: it applies on every wave of this run, not only the next one, and is never cleared mid-run.
  2. Group by parallelism — items with no shared dependencies and no overlapping output files can run concurrently. Items that depend on each other must be sequenced.
  3. Invoke /do in parallel — launch each independent item as a background sub-agent simultaneously. Do not wait for one to finish before starting another if they are independent. Pass this run's shared orchestrator_session_id (generated once in Phase 0.9, unchanged across every wave) into each sub-agent's sender object as its session_id field, per skills/j-do/assets/sender_template.json's existing shape and skills/j-do/SKILL.md's caller-supplied-session-id case (### 4.4/### 5, E32_S16_T02) — this is what lets E32_S15's per-session developer/tester concurrency cap actually contend across every sub-agent dispatched within (and across) this run's waves, instead of each one minting its own private session id and its own private, uncontended counter file.
  4. Mark In Progress — update status: In Progress in each launched item's board file (YAML front-matter) immediately after launch.
  5. Wait, drain, and loop — once all active background agents in the wave have completed: a. Drain the scrum triggers queue — invoke the ## Drain Scrum Triggers Queue procedure from agents/scrum-master.md against project/queue/scrum_triggers.jsonl. /jenga's orchestrating agent is the scrum-master, and this is the same session-start procedure applied mid-run: process any rapport_review, status_review, and story_rollup triggers written by the tester sub-sessions that just completed, then clear the file. This ensures rollups become visible on the board (story/epic status updates) before the next wave is collected, instead of sitting unprocessed until some future scrum-master session start. a.1. Task-completion cleanup (E37_S05_T02) — Phase 4 dispatches its background agents directly and never passes through /do's own step 7, so nothing else removes a finished task's entry from project/todo.md. For each task dispatched in this wave by step 3, read its board file; if its status is terminal (Passed, Passed with remarks or Failed), run the Task-Completion Cleanup Procedure (skills/j-do/SKILL.md ### 7.1, E37_S05_T01) with that task's title exactly as its entry appears in todo.md, from this orchestrating checkout, immediately — the tester has already written the status by the time the wave completes, so do not defer this to a later wave or session. Rules:

    • Only terminal statuses. A task still Pending (for example a pre-flight deferral recorded in (b)), In Progress, Blocked or Rejected keeps its todo.md entry.
    • Tasks bundled in Phase 3.5 are out of scope here — they were not dispatched by step 3 (the bundle path is wired separately by E37_S05_T03).
    • "Already removed" is benign. todo_manager.sh remove exits non-zero when no line matches, and a wave sub-agent that ran /do end to end may already have run its own step 7. Treat that no-match as success and still run the procedure's teardown step. Any other non-zero exit is surfaced to the user as a warning; it does not stop the run.
    • This reuses scripts/todo_manager.sh through the shared procedure only — no new queue file, lock or script — and leaves /do's own standalone loop unchanged. b. Record pre-flight deferrals — before the loop-back in step c below, run skills/jenga/scripts/list-preflight-deferred.sh <orchestrator_session_id> with this run's shared orchestrator_session_id and add every id it prints to the run-scoped deferred set (a set, so re-adding is harmless; start the run with it empty). The script lists the item_id of each preflight_deferred event /do step 4.1.2 appended for this session — a unit whose pre-flight checklist needs a user decision that a background sub-agent cannot give. Do not retry such a unit and do not count waves: a deferral needs a human, and re-dispatching cannot resolve it, so step 1 simply stops collecting it for the rest of the run. Run this on every wave, immediately after (a) and before (c), not only the first, so a deferral that appears in a later wave is excluded from the wave after it. A non-zero exit from the script is a genuine error (for example a malformed events.json): surface its stderr message to the user and stop the run rather than looping on an unknown set. b.1. Break check (E44_S02) — the wave's agents have all completed, the queue drain in (a) has recorded its rollups, and this wave's deferrals are recorded, so the board reflects reality at the moment of any stop. Order is explicit: drain (a), then clean up todo.md (a.1), then record deferrals (b), then check (b.1), then loop (c). 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 — no break. Continue to (c), unchanged.
    • Exit 10 — a break was requested. 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 remains. Do not collect the next batch and do not return to step 1. Print the break exit message from the Exit condition section and stop.
    • Any other exit — the checker failed, which is not a stop request. Print its stderr as a warning and continue to (c).

    The hands-free contract holds. /jenga takes no user input by design; a break halts and reports. It does not prompt, offer options or wait for a decision. Never read the flag file here — branch on the exit code and use the record the script prints.

    c. Return to step 1 of this phase — the final sub-step of the wave, reached only after steps b and b.1 above have recorded this wave's deferrals and found no break — to pick up any newly unblocked items — including items unblocked by the rollups just processed in (a). This loop-back reuses the SAME orchestrator_session_id generated once in Phase 0.9 for every subsequent wave's /do dispatches — it is never regenerated here or anywhere else in this phase.

Exit condition

There are two distinct exits. Do not confuse them: the first means the board is exhausted, the second means work remains.

Break exit (E44_S02) — entered from Phase 4's entry check or step 5's b.1 when a break was requested. It must not claim completion. Print:

⏹ Jenga stopped at a requested break (<soft|hard>). Work remains.
   Completed in the last wave: <ids, or "none — nothing was started">
   Eligible, not started:      <ids, or "none">
   Resume: re-run /jenga            (soft break — already cleared)
           /break clear, then /jenga  (hard break — still in place)

"Completed in the last wave" is the items that finished in the wave that just ended; "Eligible, not started" is what step 1 would have collected next. When the record says with_status=true, append the scoped rapport by running 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 printing its output verbatim; if it fails, say it is unavailable and why — never improvise one from the board. If the run-scoped deferred set is non-empty, append the same ⏸ Deferred at the pre-flight gate block shown below. This exit prompts for nothing.

Completion exit — when no eligible candidates remain in Phase 4, exit and output:

✅ Jenga complete. All eligible tasks have been started.

First run Phase 4 step 5b's script and add every id it prints to the run-scoped deferred set — once, even if no Phase 4 wave ever ran — so a deferral from the final wave, or from Phase 3.5's bundle /do call (a Story-Bundle deferred at Phase 3.5 step 7b, with Phase 4 dispatching nothing), is included. If the run-scoped deferred set (Phase 4 step 5b) is then empty, that is the whole output, unchanged. If it is non-empty, append one block after that line listing each deferred unit (a task id, or a story id for a Story-Bundle) and why:

⏸ Deferred at the pre-flight gate (not started): <id>[, <id>...]
   Each needs a user decision on a pre-task checklist item. Resolve it, then re-run /jenga to pick it up again.

Edge Cases

  • Epic with no stories after breakdown — log a warning and continue to the next Epic; do not block the pipeline.
  • Story with no tasks after breakdown — log a warning and continue to the next Story.
  • Task already in todo.md — skip; do not duplicate.
  • All tasks in todo.md already In Progress/Passed — exits cleanly with the completion message.
  • Unresolved dependencies — item is skipped in Phase 4 until its blockers are at least In Progress.
  • /do defers a unit at its pre-flight gate (E67_S03_T06) — the unit is back to Pending with a preflight_deferred event logged. Phase 4 step 5b adds its item_id (a story id for a Story-Bundle, which also excludes that story's member tasks) to the run-scoped deferred set, and step 1 excludes it on every later wave, so it is dispatched at most once per run. The Exit condition lists it; it is not an error and does not trigger the /do failure handling below.
  • /do failure (background agent) — treated as a skip; mark the item's status back to Pending and continue the loop with remaining candidates.
  • Story with zero tasks (empty tasks: list) — does not enter the bundle path in Phase 3.5; tasks (if any appear in todo.md independently) are dispatched normally in Phase 4.
  • Story with mixed execution_scope values — falls back entirely to per-task dispatch in Phase 4; no partial bundling occurs.
  • Task file missing execution_scope field — treated as not story-scoped; the containing story is disqualified from the bundle path.
  • Story containing a crucial_level: locked task — disqualified from the bundle path at Phase 3.5 step 5, independent of that task's execution_scope; falls back to per-task dispatch in Phase 4, where /do Section 4.2's locked-task dispatch guard (E39_S03_T04) provides the second enforcement layer before any worktree or subagent is created.
  • Bundle /do call failure — treated as a skip for the entire bundle; mark all bundled tasks' status back to Pending and continue Phase 4 with remaining non-bundled candidates.
  • Picker cancelled (bare branch) — the entire /jenga run halts immediately after relaying the cancellation acknowledgement; no phase past 0.75 runs, and nothing on the board is modified.
  • Confirmation cancelled (bare or scoped branch) — same as picker cancellation: the entire /jenga run halts immediately; no scoped set is produced and no later phase runs.
  • detect-nl-intent.sh classifies the argument as mixed (scoped branch) — the whole invocation halts at Phase 0.75 with each rejected segment's input/reason reported verbatim, per detect-nl-intent.sh's own classification contract (E53_S01_T01); no partial scope is assembled from the segments that did resolve, and no fallback guess is made for the rejected ones. The user must re-invoke /jenga <ids> with corrected input.
  • detect-nl-intent.sh classifies the argument as nl_intent, no confident single-skill match, and match-playbook.sh (E53_S02) also finds no playbook match — the natural-language branch's step 4 attempts the playbook fallback first (see the Natural-language branch's step 4/6), and only THEN surfaces the Skill Matching & Invocation Contract's no-match disambiguation options (browse /help, create a new skill via /btw, proceed with the raw prompt) instead of guessing; no phase past 0.75 runs until the user picks one.
  • detect-nl-intent.sh classifies the argument as nl_intent, no confident single-skill match, and match-playbook.sh returns an ambiguous multi-way tie between playbooks — treated the same as the no-playbook-match case above: falls through to the Skill Matching & Invocation Contract's tie-break prompt (top candidates + a "neither, describe what you need" option) instead of guessing; no phase past 0.75 runs until the user picks one. (match-playbook.sh's own ambiguous result — a tie between playbooks — is intentionally not given its own separate disambiguation UI; it is treated identically to no_match and routed to the same fallback prose, which already has its own tie-break handling.)
  • match-playbook.sh returns playbook_match and the user confirms the full chain, and every step succeeds — the Natural-language branch's step 5e reports the full completed AND skipped steps lists to the user and stops; /jenga's own Phase 1 never runs for this invocation (execution was already fully handled by the playbook's own steps, e.g. j.do/j.dev-done).
  • match-playbook.sh returns playbook_match but the user cancels at the chain confirmation step (step 5c) — identical posture to the existing picker/confirmation cancellation cases above: the entire /jenga run halts immediately after relaying the cancellation acknowledgement, with NO step of the chain executed; nothing on the board is modified by this invocation.
  • match-playbook.sh returns playbook_match, the user confirms, and a step mid-chain fails — the Natural-language branch's step 5e(vi) halts immediately on run-playbook-step.sh's halted result: no step after the failed one runs (no silent skip-ahead), and the user is shown exactly which steps already completed or were skipped, which step failed (with its note), and which steps never ran.
  • A step's conditional predicate evaluates false (E53_S04_T02) — the Natural-language branch's step 5e(i) never invokes that step at all; it calls advance <state_file> skipped directly, the step is recorded under the run's skipped list (never completed, never failed), and the chain continues to the next step exactly as it would after a passed step — a skipped step never halts the chain and is not narrated to the user as a separate turn, only reflected in the final completed/skipped summary (or the halted report's skipped field, if a later step fails).
  • A skipped step is later named by a forward_from — the Natural-language branch's step 5e(ii) calls get-output for the named source before invoking the dependent step; a skipped source returns {"status": "unavailable", "reason": "step_skipped"}. This is the documented, non-silent failure mode (E53_S04_T06's fixture coverage): the calling agent does NOT guess a fallback value or silently forward an empty string — it calls advance <state_file> failed "forward_from source '<name>' unavailable (<reason>)" and the chain halts with a halted report naming the dependent step as failed_step, exactly as any other step failure would.
  • A resolve step carries no forward_from (E53_S06_T01) — the Natural-language branch's step 5e(ii) treats this as a defined no-op: there is no forwarded value to reshape, so the resolve field is simply ignored and the step is invoked normally with whatever input it would otherwise have received. This is never surfaced to the user as an error or a warning.
  • A resolve step's transform cannot cleanly produce a usable result (E53_S06_T01) — the Natural-language branch's step 5e(ii) never invokes the target step and never guesses or passes through a differently-shaped value; it calls advance <state_file> failed "<note>" with a note that includes the raw pre-transform value, and the chain halts exactly as any other step failure would (5e-vi) — no silent skip-ahead, no fallback value.
  • A playbook step carries a conditional, shown at confirmation (E53_S04_T04) — the Natural-language branch's step 5b relays render-playbook-confirmation.sh's rendered chain, which visibly marks that step's line with "(may be skipped depending on step N's result)" — confirming a chain with a conditional step never hides its real conditional structure from the user, even though checking/unchecking that step still works exactly like any other step (the marker is display-only; the actual runtime skip decision belongs entirely to should-skip, independent of what the user checks or unchecks here).
  • The user unchecks, at confirmation, the specific step a later step's conditional depends on — the Natural-language branch's step 5d drops that conditional before calling init (its depends_on step is no longer in the confirmed list, and init would otherwise reject the conditional outright as naming a nonexistent earlier step). The dependent step becomes unconditional for this run and always executes — a deliberate, documented fallback rather than a hard failure over the user's own edit.
  • A playbook contains a cyclic {"playbook": "<id>"} reference (E53_S05_T01) — rejected entirely at load-playbooks.sh load time, before /jenga ever runs: the whole cyclic playbook is dropped from the catalog with a stderr warning naming the cycle. It is never surfaced as a match-playbook.sh candidate at all (a dropped playbook simply doesn't exist in the catalog match-playbook.sh matches against) — there is no runtime-visible error for this case, only a load-time one a human reviewing stderr output would see.
  • A playbook's composition nests deeper than the configured max_composition_depth (E53_S05_T01, default 3) — same posture as the cyclic-reference case above: dropped at load-playbooks.sh load time with a stderr warning, never surfaced as a match-playbook.sh candidate, no runtime-visible error.
  • A forward_from crosses a composition boundary (E53_S05_T02) — transparent in both directions with no special handling required anywhere in this SKILL.md: load-playbooks.sh's composition resolution runs before its forward_from/conditional validation, so by the time a playbook reaches match-playbook.sh's catalog, its steps array is already fully flattened — a step from a composed/nested playbook forwarding from (or being forwarded into by) a step outside it behaves exactly like any other forward_from relationship in step 5e(ii); the Natural-language branch never needs to know or care which originating playbook a step came from.
  • A playbook step carries composition origin metadata (depth > 1), shown at confirmation (E53_S05_T03) — the Natural-language branch's step 5b relays render-playbook-confirmation.sh's rendered chain, which now visibly indents and labels that step's line with "(from playbook: <id>, depth N)" — composing another playbook's steps into a chain never hides where one playbook ends and another begins from the user, even though the whole chain is still ONE numbered, editable, confirmable list (never a separate confirmation per nested playbook) and checking/unchecking a composed step still works exactly like any other step.
  • /jenga * (wildcard branch) — never produces a scoped set; Phases 1-4 run fully unrestricted over the entire board, identical to /jenga's behavior before Phase 0.75 existed.
  • Stale out-of-scope story queued in todo.md from an earlier run (scoped run only) — Phase 3.5's scoped-set guard skips it entirely (not considered for bundling), so it cannot be dispatched via a bundle /do <E##_S##> call that would otherwise bypass Phase 4's own scoped-set exclusion; it remains untouched in todo.md until a future run's scope includes it.
  • orchestrator_session_id reuse across waves and phases (E32_S16_T01) — the value generated once in Phase 0.9 is the exact same value threaded into Phase 3.5's bundle /do call and into every wave's /do sub-agent sender objects dispatched by Phase 4, for the entire duration of a single /jenga run; Phase 4 step 5c's loop-back never mints a new one. A run that halts before Phase 0.9 completes (e.g. cancelled at Phase 0.75's picker/confirmation) never reaches Phase 3.5 or Phase 4, so the value it would have generated is simply never used.

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.