@jenga-ai/agent 3.1.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +52 -12
  2. package/agents/developer.md +31 -16
  3. package/agents/scrum-master.md +18 -17
  4. package/agents/tester.md +25 -15
  5. package/bin/jenga.js +10 -0
  6. package/lib/commands/dashboard.js +92 -0
  7. package/lib/skill-allow-list.json +7 -2
  8. package/package.json +21 -2
  9. package/project/app/api/lib/resolve-project-root.js +120 -0
  10. package/project/app/api/package.json +16 -0
  11. package/project/app/api/parsers/architecture.js +72 -0
  12. package/project/app/api/parsers/board.js +141 -0
  13. package/project/app/api/parsers/documentation.js +125 -0
  14. package/project/app/api/parsers/git-log.js +52 -0
  15. package/project/app/api/parsers/ideas.js +62 -0
  16. package/project/app/api/parsers/knowledge-graph.js +73 -0
  17. package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
  18. package/project/app/api/parsers/rapports.js +148 -0
  19. package/project/app/api/parsers/todo.js +179 -0
  20. package/project/app/api/response.js +47 -0
  21. package/project/app/api/routes/architecture.js +23 -0
  22. package/project/app/api/routes/board.js +46 -0
  23. package/project/app/api/routes/documentation.js +24 -0
  24. package/project/app/api/routes/health.js +25 -0
  25. package/project/app/api/routes/history.js +55 -0
  26. package/project/app/api/routes/rapports.js +24 -0
  27. package/project/app/api/scripts/capture-snapshot.js +294 -0
  28. package/project/app/api/server.js +112 -0
  29. package/project/app/api/types.js +40 -0
  30. package/project/app/package.json +21 -0
  31. package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
  32. package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
  33. package/project/app/ui/dist/index.html +13 -0
  34. package/project/app/ui/package.json +23 -0
  35. package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
  36. package/project/app/ui/scripts/dashboard-open.cjs +88 -0
  37. package/project/app/ui/scripts/dashboard-start.cjs +87 -0
  38. package/scripts/acquire-concurrency-slot.sh +220 -0
  39. package/scripts/audit-twin-divergence.sh +625 -0
  40. package/scripts/check-public-playbook-steps.sh +136 -0
  41. package/scripts/compute-deploy-reconcile.sh +439 -0
  42. package/scripts/jenga-permission-level-switch.sh +19 -3
  43. package/scripts/mark-deployed.sh +532 -0
  44. package/scripts/populate-knowledge-graph.js +429 -0
  45. package/scripts/release-concurrency-slot.sh +129 -0
  46. package/scripts/validate-board.sh +60 -2
  47. package/scripts/verify-consumer-install.sh +470 -0
  48. package/skills/j-close-story/SKILL.md +1 -1
  49. package/skills/j-cloud-connect/SKILL.md +95 -0
  50. package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
  51. package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
  52. package/skills/j-dashboard/SKILL.md +144 -0
  53. package/skills/j-dashboard/scripts/launch.sh +121 -0
  54. package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
  55. package/skills/j-dashboard/scripts/snapshot.sh +267 -0
  56. package/skills/j-dashboard-share/SKILL.md +96 -0
  57. package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
  58. package/skills/j-do/SKILL.md +19 -19
  59. package/skills/j-doc-sync/SKILL.md +12 -1
  60. package/skills/j-idea/SKILL.md +1 -1
  61. package/skills/j-init/SKILL.md +5 -4
  62. package/skills/j-init/assets/directory_structure.txt +1 -0
  63. package/skills/j-init/scripts/detect-existing-codebase.sh +2 -2
  64. package/skills/j-init/scripts/init.sh +13 -2
  65. package/skills/j-playbook/SKILL.md +93 -0
  66. package/skills/j-playbook-new/SKILL.md +155 -0
  67. package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
  68. package/skills/j-proceed/SKILL.md +1 -1
  69. package/skills/j-publish/SKILL.md +1 -1
  70. package/skills/j-publish/adapters/npm-ci.md +29 -0
  71. package/skills/j-publish/scripts/npm_ci_pipeline.sh +9 -0
  72. package/skills/j-publish/scripts/npm_pipeline.sh +18 -0
  73. package/skills/j-publish/scripts/npm_stage_pipeline.sh +81 -41
  74. package/skills/j-reconcile/SKILL.md +1 -0
  75. package/skills/j-redo/SKILL.md +1 -1
  76. package/skills/j-status/SKILL.md +12 -0
  77. package/skills/j-todo/SKILL.md +2 -2
  78. package/skills/j-uncharted/SKILL.md +8 -7
  79. package/skills/j-uncharted/scripts/validate-proposed-items.sh +18 -2
  80. package/skills/jenga/SKILL.md +55 -16
  81. package/skills/jenga/playbooks/idea-to-committed.json +20 -0
  82. package/skills/jenga/playbooks/schema.json +1 -1
  83. package/skills/jenga/scripts/load-nl-catalog.js +22 -6
  84. package/skills/jenga/scripts/load-playbooks.sh +968 -41
  85. package/skills/jenga/scripts/match-playbook.sh +1 -1
  86. package/skills/jenga/scripts/render-playbook-confirmation.sh +162 -8
  87. package/skills/jenga/scripts/run-playbook-step.sh +535 -42
  88. package/skills/jenga-permission-level/SKILL.md +4 -4
  89. package/templates/KNOWLEDGE_GRAPH_STUB_SCHEMA_TEMPLATE.md +128 -0
  90. package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
  91. package/templates/playbook-types.json +8 -0
  92. package/skills/jenga/playbooks/brainstorm-to-mirror.json +0 -22
@@ -359,6 +359,32 @@ if (( DRY_RUN )); then
359
359
  exit 0
360
360
  fi
361
361
 
362
+ # ---------------------------------------------------------------------------
363
+ # Stage-id parsing helper — shared between the `npm` target's locally
364
+ # captured STAGE_OUTPUT and the `npm-ci` target's fetched CI run log. Tries
365
+ # direct-output regex parsing first, then treats the text as JSON. Echoes
366
+ # the recovered id, or nothing if neither matches.
367
+ # ---------------------------------------------------------------------------
368
+ parse_stage_id_from_text() {
369
+ local text="$1"
370
+ local id=""
371
+
372
+ # Direct-output parsing. Tolerant of label variants npm may use
373
+ # ("stage id:", "stageId:", "stage_id="), case-insensitive.
374
+ id="$(printf '%s\n' "${text}" \
375
+ | grep -Eio '\bstage[ _-]?id["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9._-]+' \
376
+ | head -n 1 \
377
+ | grep -Eo '[A-Za-z0-9._-]+$' || true)"
378
+
379
+ # The text is itself JSON (e.g. if npm's stage publish supports --json the
380
+ # way `npm publish --json` does, or a fenced JSON blob in a CI log).
381
+ if [[ -z "${id}" ]] && printf '%s' "${text}" | jq -e . >/dev/null 2>&1; then
382
+ id="$(printf '%s' "${text}" | jq -r '.id // .stageId // .stage_id // empty' 2>/dev/null || true)"
383
+ fi
384
+
385
+ printf '%s' "${id}"
386
+ }
387
+
362
388
  # ---------------------------------------------------------------------------
363
389
  # Phase 5: capture — parse the stage id
364
390
  # ---------------------------------------------------------------------------
@@ -366,52 +392,66 @@ log_info "[capture] parsing stage id from stage output..."
366
392
 
367
393
  STAGE_ID=""
368
394
 
369
- # Attempt 1: direct-output parsing. Tolerant of label variants npm may use
370
- # ("stage id:", "stageId:", "stage_id="), case-insensitive.
371
- STAGE_ID="$(printf '%s\n' "${STAGE_OUTPUT}" \
372
- | grep -Eio '\bstage[ _-]?id["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9._-]+' \
373
- | head -n 1 \
374
- | grep -Eo '[A-Za-z0-9._-]+$' || true)"
375
-
376
- # Attempt 2: the captured output is itself JSON (e.g. if npm's stage publish
377
- # supports --json the way `npm publish --json` does).
378
- if [[ -z "${STAGE_ID}" ]] && printf '%s' "${STAGE_OUTPUT}" | jq -e . >/dev/null 2>&1; then
379
- STAGE_ID="$(printf '%s' "${STAGE_OUTPUT}" | jq -r '.id // .stageId // .stage_id // empty' 2>/dev/null || true)"
380
- fi
381
-
382
- # Attempt 3: fall back to `npm stage list <package> --json` and extract the
383
- # most recent matching entry's id. `npm stage list` rejects a version-
384
- # qualified spec ("Version specifiers are not supported for listing staged
385
- # packages") — it only accepts a bare package name — so this must pass
386
- # PACKAGE_NAME, never PACKAGE_SPEC; the version match happens client-side via
387
- # jq below instead (confirmed live on jenga-npm during v1.3.0 staging on
388
- # 2026-09-01, project/todo.md).
389
- if [[ -z "${STAGE_ID}" ]]; then
390
- log_warn "could not parse a stage id directly from stage output; falling back to 'npm stage list --json'..."
391
-
392
- LIST_STATUS=0
393
- LIST_OUTPUT="$(npm stage list "${PACKAGE_NAME}" --json 2>&1)" || LIST_STATUS=$?
394
-
395
- if [[ ${LIST_STATUS} -ne 0 ]]; then
396
- printf '%s\n' "${LIST_OUTPUT}" >&2
397
- printf 'npm stage pipeline: staged successfully but stage id capture failed (npm stage list also exited %s).\n' "${LIST_STATUS}" >&2
395
+ if [[ "${TARGET_TYPE}" == "npm-ci" ]]; then
396
+ # STAGE_OUTPUT for npm-ci is the fixed one-line dispatch-summary string set
397
+ # in Phase 4 above ("staged via GitHub Actions workflow run: <url>") —
398
+ # never npm's real output — so Attempt 1/2 can never match against it. The
399
+ # actual `npm stage publish --provenance` output (including whatever
400
+ # "stage id: ..." line npm prints) lands in the workflow run's own log
401
+ # instead. Fetch it and apply the same parsing logic used for the `npm`
402
+ # target's local STAGE_OUTPUT. `npm stage list --json` (Attempt 3) is
403
+ # never invoked for npm-ci — there is no local npm auth for this target
404
+ # type, so it would only ever produce a misleading secondary failure.
405
+ log_info "[capture] fetching CI run log for run ${RUN_ID}..."
406
+ CI_LOG_OUTPUT="$(gh run view "${RUN_ID}" --repo "${GITHUB_REPO}" --log 2>&1)" || true
407
+ STAGE_ID="$(parse_stage_id_from_text "${CI_LOG_OUTPUT}")"
408
+
409
+ if [[ -z "${STAGE_ID}" ]]; then
410
+ {
411
+ printf 'npm stage pipeline: the GitHub Actions workflow run completed successfully, so staging on the registry likely SUCCEEDED — but the stage id could not be parsed from the run log.\n'
412
+ printf 'Run "gh run view %s --repo %s --log" (or open %s) to find the "stage id: ..." line by hand in the "npm stage publish" step output, then record it manually:\n' "${RUN_ID}" "${GITHUB_REPO}" "${RUN_URL}"
413
+ printf ' bash skills/j-publish/scripts/write_ledger_entry.sh %s %s staged "" --version %s --config %s --stage-id <recovered-id> --dist-tag %s\n' "${TARGET_NAME}" "${TARGET_TYPE}" "${PACKAGE_VERSION}" "${CONFIG_PATH}" "${DIST_TAG}"
414
+ } >&2
398
415
  exit "${EXIT_STAGE_FAILURE}"
399
416
  fi
417
+ else
418
+ STAGE_ID="$(parse_stage_id_from_text "${STAGE_OUTPUT}")"
419
+
420
+ # Attempt 3: fall back to `npm stage list <package> --json` and extract the
421
+ # most recent matching entry's id. `npm stage list` rejects a version-
422
+ # qualified spec ("Version specifiers are not supported for listing staged
423
+ # packages") — it only accepts a bare package name — so this must pass
424
+ # PACKAGE_NAME, never PACKAGE_SPEC; the version match happens client-side
425
+ # via jq below instead (confirmed live on jenga-npm during v1.3.0 staging
426
+ # on 2026-09-01, project/todo.md). `npm` target only — npm-ci never
427
+ # reaches here (see the branch above).
428
+ if [[ -z "${STAGE_ID}" ]]; then
429
+ log_warn "could not parse a stage id directly from stage output; falling back to 'npm stage list --json'..."
430
+
431
+ LIST_STATUS=0
432
+ LIST_OUTPUT="$(npm stage list "${PACKAGE_NAME}" --json 2>&1)" || LIST_STATUS=$?
433
+
434
+ if [[ ${LIST_STATUS} -ne 0 ]]; then
435
+ printf '%s\n' "${LIST_OUTPUT}" >&2
436
+ printf 'npm stage pipeline: staged successfully but stage id capture failed (npm stage list also exited %s).\n' "${LIST_STATUS}" >&2
437
+ exit "${EXIT_STAGE_FAILURE}"
438
+ fi
400
439
 
401
- if printf '%s' "${LIST_OUTPUT}" | jq -e . >/dev/null 2>&1; then
402
- STAGE_ID="$(printf '%s' "${LIST_OUTPUT}" | jq -r --arg pkg "${PACKAGE_NAME}" --arg ver "${PACKAGE_VERSION}" '
403
- ( if (type == "array") then . else (.stages? // .items? // []) end ) as $entries
404
- | [ $entries[]? | select(((.name // .package // "") == $pkg) and ((.version // "") == $ver)) ]
405
- | sort_by(.stagedAt // .staged_at // .created // .createdAt // "")
406
- | last
407
- | (.id // .stageId // .stage_id // empty)
408
- ' 2>/dev/null || true)"
440
+ if printf '%s' "${LIST_OUTPUT}" | jq -e . >/dev/null 2>&1; then
441
+ STAGE_ID="$(printf '%s' "${LIST_OUTPUT}" | jq -r --arg pkg "${PACKAGE_NAME}" --arg ver "${PACKAGE_VERSION}" '
442
+ ( if (type == "array") then . else (.stages? // .items? // []) end ) as $entries
443
+ | [ $entries[]? | select(((.name // .package // "") == $pkg) and ((.version // "") == $ver)) ]
444
+ | sort_by(.stagedAt // .staged_at // .created // .createdAt // "")
445
+ | last
446
+ | (.id // .stageId // .stage_id // empty)
447
+ ' 2>/dev/null || true)"
448
+ fi
409
449
  fi
410
- fi
411
450
 
412
- if [[ -z "${STAGE_ID}" ]]; then
413
- printf 'npm stage pipeline: staged successfully but the stage id could not be captured from either the direct output or "npm stage list --json". Run "npm stage list %s --json" manually to recover it (bare package name — a version-qualified spec is rejected by npm).\n' "${PACKAGE_NAME}" >&2
414
- exit "${EXIT_STAGE_FAILURE}"
451
+ if [[ -z "${STAGE_ID}" ]]; then
452
+ printf 'npm stage pipeline: staged successfully but the stage id could not be captured from either the direct output or "npm stage list --json". Run "npm stage list %s --json" manually to recover it (bare package name — a version-qualified spec is rejected by npm).\n' "${PACKAGE_NAME}" >&2
453
+ exit "${EXIT_STAGE_FAILURE}"
454
+ fi
415
455
  fi
416
456
 
417
457
  echo ""
@@ -3,6 +3,7 @@ name: j.reconcile
3
3
  description: Polyfill alias of the reconcile skill under a collision-safe directory name. Identical behavior to /reconcile — Reconcile the scrum board with actual implementation state. Cross-checks every task's board status against git history and worktrees, merges orphaned worktree branches, demotes unimplemented "Done" items, promotes secretly-implemented items, flags code with no board provenance and offers /uncharted segment for it, and cleans stale entries from todo.md. Use when the board feels out of sync, after a big merge session, when tasks were completed outside the normal workflow, or when todo.md has grown stale. Trigger on phrases like "sync the board", "clean up the board", "reconcile", "board is out of date", "todo is stale", or "check what's really done". Use when the bare /reconcile form is shadowed by another tool's own built-in command of the same name.
4
4
  metadata:
5
5
  prefered_agent: scrum-master
6
+ output_types: text
6
7
  keywords:
7
8
  - j-reconcile
8
9
  - polyfill
@@ -45,7 +45,7 @@ If either part is missing, ask the user to provide it before proceeding.
45
45
  - Run `git --no-pager show <SHA>` to inspect the full diff.
46
46
 
47
47
  **Epic / Story number:**
48
- - Read the matching file under `$(bash scripts/board_resolver.sh)epics/` or `$(bash scripts/board_resolver.sh)stories/` to understand the scope.
48
+ - Read the matching file under `$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")epics/` or `$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")stories/` to understand the scope.
49
49
  - Use `git --no-pager log --all --oneline --grep="<epic or story title>"` to locate related commits and their diffs.
50
50
 
51
51
  Collect the list of **files originally changed** and the **original intent** of the implementation.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: j.status
3
3
  description: Polyfill alias of the status skill under a collision-safe directory name. Identical behavior to /status — Print a human-readable summary of the entire scrum board — all epics, stories, and tasks with their statuses — plus any open rapports and unprocessed queue triggers. Use when you want a quick overview of project state without reading raw files. Use when the bare /status form is shadowed by another tool's own built-in command of the same name.
4
+ output_types: text
4
5
  keywords:
5
6
  - status
6
7
  - board summary
@@ -38,3 +39,14 @@ This file is generated/synced by `scripts/generate-j-alias.sh status` from `skil
38
39
  7. **Print the summary** following the layout and icon conventions in `assets/output_format.md`.
39
40
 
40
41
  8. If no epics exist, print: `No board items found. Run /pi-plan to define epics or /todo to add items.`
42
+
43
+ ## Scope note: playbook step status (E53_S04_T03 audit)
44
+
45
+ This skill reports board-level status only (steps 2-4 above: epic/story/task `status` from
46
+ `project/board/`). It never reads or reports `/jenga` playbook run state
47
+ (`skills/jenga/scripts/run-playbook-step.sh`'s temp state file, its `step_ready`/`complete`/
48
+ `halted` reports, or per-step `passed`/`failed`/`skipped` outcomes) — a playbook run is ephemeral,
49
+ session-local execution state, not a board item, and has no representation in
50
+ `project/board/`. This is confirmed as intentional, not a gap: `skipped` is scoped strictly to
51
+ playbook-step context and is never written to a task/story's board-level `status` field (see
52
+ `templates/SCRUM_BOARD_SCHEMA.md`'s Status Values table, unmodified by `E53_S04`).
@@ -53,7 +53,7 @@ When `--trivial` is present, the mission is written as a **fully-formed task boa
53
53
 
54
54
  4. **Update project documentation** — Add the mission to the appropriate files under `project/board/epics/` and `project/board/stories/` if applicable.
55
55
  - If the mission involves implementing or modifying a skill, apply the **Skill Implementation Principle — Scripts Over Inline Logic** (see `CLAUDE.md` / `AGENTS.md`): note in the story/task's acceptance criteria that deterministic, repeatable steps must be offloaded to scripts under `skills/<name>/scripts/` (or `scripts/`) rather than encoded as inline agent instructions in `SKILL.md`.
56
- - If the user indicates the mission is high-risk, or explicitly asks to flag it, set an elevated caution tier directly on the story or task frontmatter: `crucial_level` (one of `advisory`, `gated`, `locked` — see `templates/SCRUM_BOARD_SCHEMA.md` for valid values and their meaning), `crucial_set_by: user`, and `crucial_note` capturing the user's stated reason. This user-initiated flag is written immediately — it does not require the confirm-before-write gate, which applies only to scrum-master-*proposed* caution tiers (a separate, heuristic-driven path).
56
+ - If the user indicates the mission is high-risk, or explicitly asks to flag it, set an elevated caution tier directly on the story or task frontmatter: `crucial_level` (one of `advisory`, `gated`, `locked` — see `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)` for valid values and their meaning), `crucial_set_by: user`, and `crucial_note` capturing the user's stated reason. This user-initiated flag is written immediately — it does not require the confirm-before-write gate, which applies only to scrum-master-*proposed* caution tiers (a separate, heuristic-driven path).
57
57
 
58
58
  4.5. **If `--trivial` was passed, create the task now** (skip step 5 — this step writes the todo.md entry itself):
59
59
 
@@ -81,7 +81,7 @@ When `--trivial` is present, the mission is written as a **fully-formed task boa
81
81
 
82
82
  5. **Add to `project/todo.md`** by running (skip this step if step 4.5 already ran):
83
83
  ```
84
- bash scripts/todo_manager.sh add '<mission title>: <Epic no.>_<Story no.>'
84
+ bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" add '<mission title>: <Epic no.>_<Story no.>'
85
85
  ```
86
86
  The epic and story reference is only required if the mission is assigned to one.
87
87
 
@@ -3,6 +3,7 @@ name: j.uncharted
3
3
  description: Polyfill alias of the uncharted skill under a collision-safe directory name. Identical behavior to /uncharted — Investigate code that has no Jenga board provenance — a foreign file, an external source being pulled in, or an entire pre-existing codebase — and give it a consistent understanding document plus proper board representation. Use when the bare /uncharted form is shadowed by another tool's own built-in command of the same name.
4
4
  metadata:
5
5
  prefered_agent: scrum-master
6
+ output_types: text
6
7
  keywords:
7
8
  - "uncharted"
8
9
  - "foreign code"
@@ -117,7 +118,7 @@ Every mode produces exactly one document per target, rendered from `skills/j-unc
117
118
 
118
119
  **Where it goes:** `project/rapports/analysis/`, named `uncharted-<mode>-<slug>-<YYYYMMDDTHHMMSSZ>.md`. Nothing is ever overwritten — a name collision gets a `-2`, `-3`… suffix, so repeated runs against one target leave a diffable history.
119
120
 
120
- This reuses the **existing `analysis` rapport type** already defined in `templates/SCRUM_BOARD_SCHEMA.md`. `/uncharted` introduces **no new rapport type** — if a change to the rapport taxonomy ever seems necessary, that is a schema change to be raised with the scrum-master, not something this skill invents.
121
+ This reuses the **existing `analysis` rapport type** 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)`. `/uncharted` introduces **no new rapport type** — if a change to the rapport taxonomy ever seems necessary, that is a schema change to be raised with the scrum-master, not something this skill invents.
121
122
 
122
123
  **Fixed structure** — seven headings, never added to, removed, reordered, or renamed, because downstream steps read the document by heading:
123
124
 
@@ -157,7 +158,7 @@ A document whose judgement sections are generic enough to apply to any codebase
157
158
 
158
159
  Since **E20_S08_T03**, `onboard`'s default behavior and `segment --mode investigate` both run a human-in-the-loop **conversational elicitation** instead of (or, for `onboard`, in addition to keeping available) a one-shot deterministic pass. This section defines the mechanics shared by both; each mode's own subsection below only describes what is specific to it.
159
160
 
160
- **What conversational elicitation produces, and how that differs from the Understanding Document above:** the **primary** output is coarse-tier graph nodes/edges — written directly to `project/knowledge-graph/graph.json`, conforming to the stub schema at `project/knowledge-graph/STUB_SCHEMA.md` (E20_S08_T01; this is a throwaway pilot schema, swapped wholesale once E20_S01's real schema lands — do not extend it expecting stability). Every node this flow writes carries `source: "human"`, since it comes from a person confirming or correcting a proposed understanding, not from mechanical extraction. Board representation is a `[ARCH]`-tagged epic, story, or task at whichever level fits the investigated scope (see `templates/SCRUM_BOARD_SCHEMA.md`'s `[ARCH]` — Durable Architectural Inventory convention) — **not** the delivery-shaped epic/story/task proposal `segment --mode delivery` and legacy `onboard` produce, and not the fixed 7-heading Understanding Document either. A written summary is produced only when warranted, filed as the resulting board item's ordinary `-summary.md` — there is no new artifact type or separate "Understanding Document" for conversational output.
161
+ **What conversational elicitation produces, and how that differs from the Understanding Document above:** the **primary** output is coarse-tier graph nodes/edges — written directly to `project/knowledge-graph/graph.json`, conforming to the stub schema at `project/knowledge-graph/STUB_SCHEMA.md` (E20_S08_T01; this is a throwaway pilot schema, swapped wholesale once E20_S01's real schema lands — do not extend it expecting stability). Every node this flow writes carries `source: "human"`, since it comes from a person confirming or correcting a proposed understanding, not from mechanical extraction. Board representation is a `[ARCH]`-tagged epic, story, or task at whichever level fits the investigated scope (see `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`'s `[ARCH]` — Durable Architectural Inventory convention) — **not** the delivery-shaped epic/story/task proposal `segment --mode delivery` and legacy `onboard` produce, and not the fixed 7-heading Understanding Document either. A written summary is produced only when warranted, filed as the resulting board item's ordinary `-summary.md` — there is no new artifact type or separate "Understanding Document" for conversational output.
161
162
 
162
163
  ### Human-Oracle-Availability Limitation
163
164
 
@@ -252,7 +253,7 @@ A whole-codebase `onboard` conversation, or an investigation of a large director
252
253
 
253
254
  Idempotent — safe to call again on a resumed `<elicitation-id>` without resetting progress. Choose `<elicitation-id>` so it is stable and re-derivable across sessions (e.g. `onboard-<root-slug>-<date>`, or `segment-investigate-<target-slug>`), since a resuming session must be able to reconstruct it to call `init` again.
254
255
  - **`checkpoint` after every converged node and after the Directory Triage confirmation gate** — never only at the end. This is what makes a mid-run pause lossless: `checkpoint --id <id> --json <file>` merges arbitrary progress data (triage results, draft nodes not yet converged, anything else worth surviving a pause) into the state file.
255
- - **`pause` when a session must end before the elicitation has converged.** Immediately after calling `elicitation-state.sh pause --id <elicitation-id>`, write the scrum-master's own `SessionEnd` handoff (per `templates/SCRUM_BOARD_SCHEMA.md`'s `handoffs/` convention) with `status: "elicitation_paused"` and both `elicitation_id` and `state_file` set — `hooks/on_session_end.sh` routes that into an `elicitation_resume` trigger on `scrum_triggers.jsonl`, which the next scrum-master session's Drain Scrum Triggers Queue procedure picks up (`agents/scrum-master.md`).
256
+ - **`pause` when a session must end before the elicitation has converged.** Immediately after calling `elicitation-state.sh pause --id <elicitation-id>`, write the scrum-master's own `SessionEnd` handoff (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 `handoffs/` convention) with `status: "elicitation_paused"` and both `elicitation_id` and `state_file` set — `hooks/on_session_end.sh` routes that into an `elicitation_resume` trigger on `scrum_triggers.jsonl`, which the next scrum-master session's Drain Scrum Triggers Queue procedure picks up (`agents/scrum-master.md`).
256
257
  - **On resume**, read `state_file` directly — every converged node, every flagged node, and the checkpoint data (including the confirmed directory-triage lists) are already there. Do not re-run Directory Triage or re-ask about an already-converged node; resume the Convergence Loop only for nodes still `pending` or explicitly deferred.
257
258
  - **`complete` when every candidate has converged, been deferred, or been explicitly accepted past the cap.** The state file is left on disk afterward as an audit trail — nothing currently prunes a completed elicitation's state file.
258
259
 
@@ -341,7 +342,7 @@ A new epic is the last option, not the default, and needs a stated reason. Recor
341
342
 
342
343
  **Step 5 — Draft the proposal** using `skills/j-uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md`. Its six headings are fixed; fill every one of them. Two things it will not let you skip:
343
344
 
344
- - **Every proposed task carries `execution_scope` and a non-empty `scope_rationale`** containing a file-count or line-count claim, assigned against `project/configs/scope-thresholds.json` rather than by feel — `inline` at or under 1 file / 20 lines, `story` when tasks share files across the story (up to 5 files), `task` otherwise. Never self-assign `execution_scope: epic`: it requires `epic_scope_approval: true`, which only a human sets. Propose `story` and say the task looks bigger instead. Field semantics live in `templates/SCRUM_BOARD_SCHEMA.md` — read them there, do not paraphrase them here.
345
+ - **Every proposed task carries `execution_scope` and a non-empty `scope_rationale`** containing a file-count or line-count claim, assigned against `project/configs/scope-thresholds.json` rather than by feel — `inline` at or under 1 file / 20 lines, `story` when tasks share files across the story (up to 5 files), `task` otherwise. Never self-assign `execution_scope: epic`: it requires `epic_scope_approval: true`, which only a human sets. Propose `story` and say the task looks bigger instead. Field semantics live in `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)` — read them there, do not paraphrase them here.
345
346
  - **The document's `Open Questions` carry over verbatim**, not re-derived, each with who can answer it and whether it blocks confirmation. A blocking question is resolved *before* the proposal is accepted, because the breakdown depends on its answer.
346
347
  - **Propose everything the board file will contain**, not just titles. Each story's Acceptance Criteria and Definition of Done are proposed at the gate, and a new epic's Purpose and Definition of Done with it — `scripts/validate-story-format.sh` requires those sections, so anything missing here is text `E40_S02_T03` would have to invent after the user has already said yes.
347
348
 
@@ -367,7 +368,7 @@ Options 2 and 3 loop back to Step 5 (or to Step 4 for a re-parent) and re-presen
367
368
 
368
369
  The proposal is the input and the **only** source of content. Everything the board files contain — the epic's Purpose and Definition of Done, each story's `As a …, I want …, so that …` line, Acceptance Criteria, and Definition of Done, each task's `execution_scope` and `scope_rationale` — was proposed at the gate and is transcribed **verbatim**. Nothing is composed, expanded, or improved at write time. If a field looks wrong now, that is a revision (option 2), not an edit in passing: text the user did not read has no business on the board, which is the whole point of the gate.
369
370
 
370
- Shape, field names, and file naming come from `templates/SCRUM_BOARD_SCHEMA.md`. Read them there — do not paraphrase them here. Five things it will not let you skip:
371
+ Shape, field names, and file naming come from `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`. Read them there — do not paraphrase them here. Five things it will not let you skip:
371
372
 
372
373
  - **Parent before child.** Epic (if new), then stories, then tasks. The schema's Linking Convention makes the epic's `stories[]` and each story's `tasks[]` the authoritative index, so a child written before its parent leaves an index nobody has updated.
373
374
  - **IDs continue the sequence they belong to.** `E##` is the next free epic number board-wide, but `S##` is the next free story number **within its parent epic** and `T##` the next free task number **within its parent story** — that is what the `E##_S##_T##` shape means. Story numbers restarting per epic is normal and expected, not a collision. Nothing catches an error here: `validate-board.sh` checks an ID's *shape*, never its uniqueness or its sequence, so a well-formed wrong number passes every gate in Step 7 and quietly reuses an ID that already meant something else.
@@ -387,7 +388,7 @@ It runs `scripts/validate-board.sh` over every file and `scripts/validate-story-
387
388
 
388
389
  On success, tell the user exactly which files were created, with their IDs.
389
390
 
390
- **Step 8 — Hand off to the standard path.** Queue each new task with the canonical todo owner — `scripts/todo_manager.sh add "<entry>"`, one call per task, referencing the task ID — and the work then proceeds through the ordinary `/todo` → `/do` → developer → tester path, with no special casing anywhere along it. Point the tasks' Description at the understanding document from Step 2; it is the context the developer picking one up would otherwise lack.
391
+ **Step 8 — Hand off to the standard path.** Queue each new task with the canonical todo owner — `` bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" add "<entry>" ``, one call per task, referencing the task ID — and the work then proceeds through the ordinary `/todo` → `/do` → developer → tester path, with no special casing anywhere along it. Point the tasks' Description at the understanding document from Step 2; it is the context the developer picking one up would otherwise lack.
391
392
 
392
393
  **`/uncharted` writes board files and stops there.** It does not adapt the segment to project conventions, edit or move the code it just analysed, open a worktree, or write an execution plan. That is ordinary developer work, driven by ordinary task files, and it is the developer agent's job — the same as for a task that came from `/brainstorm` or `/pi-plan`. A segment that has reached the board is no longer a special case, and this skill growing its own integration path would be a second, divergent execution route for work the existing one already handles.
393
394
 
@@ -712,7 +713,7 @@ re-running `onboard` with a higher `--cap`.
712
713
  Live as of `E40_S04_T04`. `skills/j-uncharted/scripts/write-backfilled-epics.sh` is the only thing
713
714
  that actually writes backfilled epics to the board — it consumes `apply-subsystem-cap.sh`'s
714
715
  `kept` array and renders one epic file per entry, following the Epic format in
715
- `templates/SCRUM_BOARD_SCHEMA.md` exactly (`provenance: backfilled`, `status: Pending`, an empty
716
+ `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)` exactly (`provenance: backfilled`, `status: Pending`, an empty
716
717
  `stories` array, a Purpose section built from the discovery evidence, and a Definition of Done
717
718
  framed around understanding and integration, never original construction):
718
719
 
@@ -48,8 +48,24 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
48
48
  REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
49
49
  [ -n "$REPO_ROOT" ] || REPO_ROOT=$(cd -- "$SCRIPT_DIR/../../.." && pwd -P)
50
50
 
51
- BOARD_VALIDATOR="$REPO_ROOT/scripts/validate-board.sh"
52
- STORY_VALIDATOR="$REPO_ROOT/scripts/validate-story-format.sh"
51
+ # ─── Resolve the validators' package root ─────────────────────────────────
52
+ # postinstall.js mirrors only skills/ and agents/ into a consumer's .claude/
53
+ # and .agents/ — scripts/ (which owns both validators) is never copied there,
54
+ # so this script — itself shipped under skills/j-uncharted/scripts/ and mirrored
55
+ # alongside it — cannot assume "$REPO_ROOT/scripts/..." exists. Mirrors
56
+ # skills/init/scripts/init.sh's PKG_ROOT fallback (same pattern already
57
+ # applied to this skill's elicitation-state.sh WITH_LOCK resolution): prefer
58
+ # a monorepo checkout's sibling scripts/ dir, else fall back to the installed
59
+ # npm package under node_modules/@jenga-ai/agent.
60
+ if [ -f "$SCRIPT_DIR/../../../scripts/validate-board.sh" ]; then
61
+ VALIDATOR_ROOT="$SCRIPT_DIR/../../../scripts"
62
+ elif [ -f "$REPO_ROOT/node_modules/@jenga-ai/agent/scripts/validate-board.sh" ]; then
63
+ VALIDATOR_ROOT="$REPO_ROOT/node_modules/@jenga-ai/agent/scripts"
64
+ else
65
+ VALIDATOR_ROOT="$REPO_ROOT/scripts"
66
+ fi
67
+ BOARD_VALIDATOR="$VALIDATOR_ROOT/validate-board.sh"
68
+ STORY_VALIDATOR="$VALIDATOR_ROOT/validate-story-format.sh"
53
69
 
54
70
  usage() {
55
71
  echo "Usage: $(basename "$0") <board-file> [more-files...]" >&2
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  name: j.jenga
3
3
  description: 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.
4
+ output_types:
5
+ - when: detect-nl-intent
6
+ type: id_list
4
7
  keywords:
5
8
  - jenga
6
9
  - orchestrate
@@ -96,7 +99,7 @@ Do not proceed with this task.
96
99
 
97
100
  #### Rule 4 — crucial_level: locked forces execution_scope: inline
98
101
 
99
- If the task frontmatter contains `crucial_level: locked` (per `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.
102
+ 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.
100
103
 
101
104
  This rule **auto-corrects and continues**; unlike Rules 1-3, it never halts.
102
105
 
@@ -172,18 +175,44 @@ This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl
172
175
  - `playbook_match` → continue to **step 5 (Playbook proposal and execution)** below.
173
176
  - `ambiguous` or `no_match` → continue to **step 6 (Fall through to `/route`'s disambiguation)** below — the exact behavior this branch already had before E53_S02, unchanged.
174
177
  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`.
175
- a. **Render and confirm the chain** — invoke `skills/jenga/scripts/render-playbook-confirmation.sh "<playbook_id>" "<name>" "<comma-separated steps>"` (start mode, using `match-playbook.sh`'s `playbook_id`/`name`/`steps` fields verbatim). Relay STDOUT (the numbered chain + instructions) to the user verbatim. Capture the `STATE_FILE:` path from STDERR.
176
- b. Wait for the user's chat reply, then invoke `skills/jenga/scripts/render-playbook-confirmation.sh <state_file> "<raw_reply>"` (continue mode).
177
- - **Toggle or error turn** (plain text on STDOUT, state file retained) — relay verbatim and return to step 5b for another reply. This loops exactly as the existing bare/scoped confirmation flow's own toggle/error turns already do.
178
+ a. **Resolve conditional metadata** (`E53_S04_T02`/`T04`) — before rendering, inspect the
179
+ matched playbook's own `steps` array (as returned by `load-playbooks.sh`'s catalog, not the
180
+ flattened string list) for any StepObject carrying a `conditional: {"depends_on": "<name>",
181
+ "predicate": "..."}` field. Build a JSON object mapping each such step's name to its
182
+ `depends_on` step's name only (e.g. `{"stepC": "stepA"}` — the confirmation display needs no
183
+ predicate detail, only which step to point at). If no step carries a `conditional`, this
184
+ object is empty/omitted.
185
+ **Also resolve composition origin metadata** (`E53_S05_T01`/`T03`) — inspect the SAME
186
+ `steps` array (`match-playbook.sh`'s `steps` field is already load-playbooks.sh's fully
187
+ FLATTENED, composition-resolved catalog output — composition is invisible to
188
+ `match-playbook.sh` itself; there is nothing further to "flatten" at this point, only to
189
+ read) for any StepObject carrying `_origin_playbook`/`_origin_depth` (present only on steps
190
+ whose `_origin_depth` is greater than 1 — see `load-playbooks.sh`'s header "COMPOSITION
191
+ RESOLUTION"). Build a JSON object mapping each such step's name to `{"playbook_id":
192
+ "<_origin_playbook>", "depth": <_origin_depth>}`. If no step in this playbook came from a
193
+ composed/nested playbook, this object is empty/omitted.
194
+ 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.
195
+ c. Wait for the user's chat reply, then invoke `skills/jenga/scripts/render-playbook-confirmation.sh <state_file> "<raw_reply>"` (continue mode).
196
+ - **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.
178
197
  - **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).
179
- - **Confirmed** (JSON object on STDOUT, state file removed) — take the confirmed `steps` array (checked-only, in original playbook order) and continue to step 5c.
180
- c. **Initialize the sequential runner** — invoke `skills/jenga/scripts/run-playbook-step.sh init "<playbook_id>" "<name>" "<comma-separated confirmed steps>"`. Its `step_ready` result names the first step to invoke.
181
- d. **Execute steps in a loop** — for the step named by the runner's most recent `step_ready` result:
182
- i. Invoke that step exactly as `skills/route/SKILL.md`'s **Step 6 — Invoke the Matched Skill** 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.
183
- ii. After that step's execution concludes, call `skills/jenga/scripts/run-playbook-step.sh advance <state_file> passed` (the step completed successfully) or `... advance <state_file> failed "<short failure note>"` (the step failed).
184
- iii. On a `step_ready` result, repeat step 5d for the newly-named step.
185
- iv. On a `complete` result, report the full list of completed steps to the user and stop — the playbook run is finished; do not continue into this `/jenga` invocation's Phase 1.
186
- v. On a `halted` result, **immediately stop executing any further steps** — no silent skip-ahead. Report `failed_step`, `failed_note`, `completed` (steps that already finished), 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.
198
+ - **Confirmed** (JSON object on STDOUT, state file removed) — take the confirmed `steps` array (checked-only, in original playbook order) and continue to step 5d.
199
+ 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.
200
+ e. **Execute steps in a loop** — for the step named by the runner's most recent `step_ready` result:
201
+ i. **Evaluate whether this step should run** (`E53_S04_T02`) — invoke `skills/jenga/scripts/run-playbook-step.sh should-skip <state_file>`.
202
+ - `{"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).
203
+ - `{"skip": false, ...}` — proceed to step 5e-ii and invoke the step normally.
204
+ 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).
205
+
206
+ **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 `docs/skill-authoring.md`'s "The `resolve` / confirmation-gate rule"):
207
+ - **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.
208
+ - **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.
209
+ - **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.
210
+
211
+ Then invoke the step exactly as `skills/route/SKILL.md`'s **Step 6 — Invoke the Matched Skill** 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.
212
+ 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).
213
+ iv. On a `step_ready` result, repeat step 5e for the newly-named step.
214
+ 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.
215
+ 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.
187
216
  6. **Fall through to `/route`'s disambiguation** — entered when step 4 found no playbook match (`ambiguous` or `no_match`). Surface the same disambiguation options `skills/route/SKILL.md`'s **Step 2** already defines for these cases (browse `/help`, create a new skill via `/btw`, or proceed with the raw prompt) by reference to that section — do not re-copy its prose. 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.
188
217
 
189
218
  #### Shared confirmation step (bare and scoped branches only)
@@ -235,7 +264,7 @@ For each in-scope story that has one or more tasks listed in `todo.md`:
235
264
  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.
236
265
  3. **Read each task file** — for every task ID in the `tasks:` list, read the corresponding task file from `project/board/tasks/`.
237
266
  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.
238
- 5. **Guard: locked-task disqualifier (defense-in-depth)** — for each task file already read in step 3, also read `crucial_level` (per `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.
267
+ 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.
239
268
  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.
240
269
  7. **Route bundle candidates** — if all tasks in the story are `execution_scope: story` and the list is non-empty:
241
270
  a. Emit:
@@ -285,8 +314,18 @@ When no eligible candidates remain in Phase 4, exit and output:
285
314
  - **`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.
286
315
  - **`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 `skills/route/SKILL.md`'s Step 2 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.
287
316
  - **`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 `skills/route/SKILL.md`'s Step 2 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 `/route` Step 2 fallback prose, which already has its own tie-break handling.)
288
- - **`match-playbook.sh` returns `playbook_match` and the user confirms the full chain, and every step succeeds** — the Natural-language branch's step 5d reports the full `completed` steps list 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`).
289
- - **`match-playbook.sh` returns `playbook_match` but the user cancels at the chain confirmation step (step 5b)** — 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.
290
- - **`match-playbook.sh` returns `playbook_match`, the user confirms, and a step mid-chain fails** — the Natural-language branch's step 5d(v) 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, which step failed (with its note), and which steps never ran.
317
+ - **`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`).
318
+ - **`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.
319
+ - **`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.
320
+ - **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).
321
+ - **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.
322
+ - **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.
323
+ - **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.
324
+ - **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).
325
+ - **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.
326
+ - **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.
327
+ - **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.
328
+ - **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.
329
+ - **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.
291
330
  - **`/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.
292
331
  - **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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "idea-to-committed",
3
+ "name": "Idea to Committed",
4
+ "description": "Takes a rough idea through planning, board capture, implementation, and a commit -- the end-to-end Jenga workflow chain, stopping at the commit rather than at a release.",
5
+ "keywords": [
6
+ "idea to commit",
7
+ "plan build commit",
8
+ "brainstorm to commit",
9
+ "capture and implement",
10
+ "start to commit"
11
+ ],
12
+ "examples": [
13
+ "I have an idea, help me plan it, build it, and commit it",
14
+ "take this feature from a rough idea through to a commit",
15
+ "plan this out, put it on the board, implement it, and commit the work",
16
+ "walk this through planning, implementation, and committing",
17
+ "help me think this through, break it down, build it, and commit"
18
+ ],
19
+ "steps": ["j-brainstorm", "j-todo", "j-do", "j-commit"]
20
+ }
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "steps": {
36
36
  "type": "array",
37
- "description": "Ordered list of bare skill names (the directory name under skills/<name>/SKILL.md, e.g. \"brainstorm\", not \"j.brainstorm\" or \"/brainstorm\") that make up this playbook's chain, in the exact execution order. Each entry MUST resolve to an existing skills/<name>/SKILL.md at load time -- load-playbooks.sh skips (with a stderr warning) any playbook referencing a nonexistent skill rather than silently including a broken chain in the catalog.",
37
+ "description": "Ordered list of canonical skill DIRECTORY names (the directory under skills/<dir>/SKILL.md, e.g. \"j-brainstorm\", not the \"j.brainstorm\" frontmatter/invocation form and not the retired bare \"brainstorm\") that make up this playbook's chain, in the exact execution order. Per E50_S10's canonical naming contract the canonical directory is skills/j-<name>/, so a step value carries the j- prefix; the three permanent exceptions (jenga, jenga-permission-level, index) keep their bare directory names and are written bare. Each entry MUST resolve to an existing skills/<dir>/SKILL.md at load time -- load-playbooks.sh skips (with a stderr warning) any playbook referencing a nonexistent skill rather than silently including a broken chain in the catalog.",
38
38
  "items": { "type": "string" },
39
39
  "minItems": 2
40
40
  }
@@ -11,10 +11,16 @@
11
11
  * inventory (`readSkillAllowList()`, which reads the committed `lib/skill-allow-list.json`
12
12
  * artifact) — this script does not independently re-scan `skills/` for a name list of its own,
13
13
  * per E53_S01_T02's acceptance criteria and the drift lesson E41_S04 already documented for that
14
- * generator. For each name in that inventory, this script reads exactly one file —
15
- * `skills/<name>/SKILL.md` — to populate the remaining catalog fields: `description`, `keywords`,
16
- * `examples`, and `metadata.prefered_agent`. These are the same fields `/route`'s Step 1
17
- * ("Discover Available Skills") collects.
14
+ * generator. That inventory holds bare identifiers (e.g. "brainstorm"), stripped of the `j.`
15
+ * frontmatter prefix — but per docs/skill-authoring.md's Canonical Naming Contract, the actual
16
+ * on-disk directory is `skills/j-<name>/`, except the three permanent exceptions (`jenga`,
17
+ * `jenga-permission-level`, `index`) which keep their bare directory name. For each name in the
18
+ * inventory, this script re-derives that canonical directory name, reads exactly one file —
19
+ * `skills/<dirName>/SKILL.md` — to populate the remaining catalog fields: `description`,
20
+ * `keywords`, `examples`, and `metadata.prefered_agent`, and emits `dirName` (not the bare
21
+ * identifier) as the catalog entry's `name` — callers like `/jenga`'s Skill invocation and
22
+ * `playbook-new.sh`'s `validate-skill` need the real, invokable directory name. These are the
23
+ * same fields `/route`'s Step 1 ("Discover Available Skills") collects.
18
24
  *
19
25
  * ---------------------------------------------------------------------------
20
26
  * USAGE
@@ -61,6 +67,15 @@ import { readFileSync, existsSync } from "fs";
61
67
  import { join } from "path";
62
68
  import { pathToFileURL } from "url";
63
69
 
70
+ // The three permanent exceptions to the `j-<name>` canonical directory convention — see
71
+ // docs/skill-authoring.md's Canonical Naming Contract and scripts/audit-twin-divergence.sh's
72
+ // NEVER_TWINNED list, which this mirrors.
73
+ const NEVER_TWINNED = new Set(["jenga", "jenga-permission-level", "index"]);
74
+
75
+ function canonicalSkillDir(name) {
76
+ return NEVER_TWINNED.has(name) ? name : `j-${name}`;
77
+ }
78
+
64
79
  /**
65
80
  * Parses YAML frontmatter from a SKILL.md's content into a plain object. This is a hand-rolled,
66
81
  * intentionally minimal parser scoped to the small set of shapes SKILL.md frontmatter actually
@@ -161,7 +176,8 @@ async function main() {
161
176
 
162
177
  const catalog = [];
163
178
  for (const name of names) {
164
- const skillMdPath = join(pkgRoot, "skills", name, "SKILL.md");
179
+ const dirName = canonicalSkillDir(name);
180
+ const skillMdPath = join(pkgRoot, "skills", dirName, "SKILL.md");
165
181
  if (!existsSync(skillMdPath)) {
166
182
  process.stderr.write(
167
183
  `Warning: ${skillMdPath} not found for allow-listed skill '${name}' — skipped\n`
@@ -186,7 +202,7 @@ async function main() {
186
202
  }
187
203
 
188
204
  catalog.push({
189
- name,
205
+ name: dirName,
190
206
  description: fm.description,
191
207
  keywords: Array.isArray(fm.keywords) ? fm.keywords : [],
192
208
  examples: Array.isArray(fm.examples) ? fm.examples : [],