@jenga-ai/agent 3.4.0 → 3.6.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 (97) hide show
  1. package/README.md +85 -78
  2. package/agents/developer.md +1 -1
  3. package/agents/scrum-master.md +20 -2
  4. package/agents/tester.md +3 -3
  5. package/hooks/on_session_end.sh +5 -5
  6. package/lib/generate-agent-context.js +2 -2
  7. package/lib/generate-copilot-hooks.js +1 -1
  8. package/lib/generate-skill-allow-list.js +37 -3
  9. package/lib/mirror.js +1 -1
  10. package/lib/postinstall-manifest.js +1 -1
  11. package/lib/skill-allow-list.json +2 -2
  12. package/mcp/help/index.js +8 -17
  13. package/mcp/help/scan.js +73 -0
  14. package/package.json +6 -1
  15. package/project/app/api/parsers/knowledge-graph.js +100 -9
  16. package/project/app/api/routes/health.js +36 -0
  17. package/project/app/api/scripts/capture-snapshot.js +9 -6
  18. package/project/app/ui/dist/assets/{index-CdK3Qrep.css → index-BVR_7Owg.css} +1 -1
  19. package/project/app/ui/dist/assets/index-CtU2xLQm.js +104 -0
  20. package/project/app/ui/dist/index.html +2 -2
  21. package/project/app/ui/package.json +4 -0
  22. package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
  23. package/scripts/acquire-concurrency-slot.sh +1 -1
  24. package/scripts/apply-j-prefix.sh +46 -5
  25. package/scripts/audit-twin-divergence.sh +73 -5
  26. package/scripts/build-pages-site.sh +1 -1
  27. package/scripts/check-public-playbook-steps.sh +158 -52
  28. package/scripts/check-publicignore-match.sh +2 -2
  29. package/scripts/compute-deploy-reconcile.sh +5 -5
  30. package/scripts/delete-bare-skill-dirs.sh +330 -0
  31. package/scripts/generate-legacy-shipped-paths.js +2 -2
  32. package/scripts/idea_manager.sh +258 -3
  33. package/scripts/mark-deployed.sh +2 -2
  34. package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
  35. package/scripts/populate-knowledge-graph.js +213 -5
  36. package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
  37. package/scripts/postinstall.js +1 -1
  38. package/scripts/repoint-skill-refs.sh +539 -0
  39. package/scripts/todo_manager.sh +1 -1
  40. package/scripts/verify-legacy-seed-reconcile.sh +10 -10
  41. package/scripts/verify-postinstall-reconcile.sh +7 -7
  42. package/scripts/write-context-digest.sh +1 -1
  43. package/skills/j-clearify/SKILL.md +2 -2
  44. package/skills/j-close-story/scripts/check-privatized.sh +4 -4
  45. package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
  46. package/skills/j-do/SKILL.md +101 -17
  47. package/skills/j-doc-sync/SKILL.md +1 -0
  48. package/skills/j-gitignore/SKILL.md +157 -0
  49. package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
  50. package/skills/j-gitignore/scripts/_catalog.sh +105 -0
  51. package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
  52. package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
  53. package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
  54. package/skills/j-idea/SKILL.md +78 -6
  55. package/skills/j-idea/assets/idea_template.md +1 -1
  56. package/skills/j-improve/SKILL.md +1 -1
  57. package/skills/j-init/SKILL.md +53 -14
  58. package/skills/j-init/assets/.gitignore_template +1 -2
  59. package/skills/j-init/assets/scope-thresholds_template.json +5 -2
  60. package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
  61. package/skills/j-init/scripts/init.sh +22 -8
  62. package/skills/j-playbook/SKILL.md +1 -1
  63. package/skills/j-publish/SKILL.md +1 -1
  64. package/skills/j-publish/adapters/npm-ci.md +6 -1
  65. package/skills/j-publish/adapters/npm.md +1 -1
  66. package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
  67. package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
  68. package/skills/j-reconcile/SKILL.md +2 -2
  69. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +11 -11
  70. package/skills/j-redo/SKILL.md +1 -1
  71. package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
  72. package/skills/j-spinoff/SKILL.md +1 -1
  73. package/skills/j-status/SKILL.md +15 -0
  74. package/skills/j-todo/SKILL.md +3 -1
  75. package/skills/j-uncharted/SKILL.md +55 -8
  76. package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
  77. package/skills/j-uncharted/scripts/detect-dependencies.sh +1 -1
  78. package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
  79. package/skills/j-uncharted/scripts/elicitation-state.sh +46 -8
  80. package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
  81. package/skills/j-wtf/SKILL.md +1 -1
  82. package/skills/jenga/SKILL.md +43 -9
  83. package/skills/jenga/playbooks/board-hygiene.json +32 -0
  84. package/skills/jenga/playbooks/schema.json +73 -6
  85. package/skills/jenga/playbooks/understand-then-commit.json +19 -0
  86. package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
  87. package/skills/jenga/scripts/load-playbooks.sh +23 -11
  88. package/skills/jenga/scripts/match-playbook.sh +1 -1
  89. package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
  90. package/templates/SKILL_TEMPLATE.md +12 -0
  91. package/templates/permission-levels/level-4-elevated.json +1 -1
  92. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  93. package/templates/playbook-types.json +34 -6
  94. package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
  95. package/scripts/generate-j-alias.sh +0 -333
  96. package/skills/j-dev-done/SKILL.md +0 -53
  97. package/skills/j-dev-done/scripts/classify-commit-outcome.sh +0 -114
@@ -53,7 +53,23 @@
53
53
  # "nodes": {
54
54
  # "<node-id>": { "turns": <int>, "status": "pending"|"converged"|"flagged", "note": "<text>" }
55
55
  # },
56
- # "checkpoint": { ...arbitrary, agent-defined fields, e.g. directory-triage results... }
56
+ # "checkpoint": {
57
+ # ...arbitrary, agent-defined fields, e.g. directory-triage results...
58
+ # "verification_depth": {
59
+ # // Recognized field (E40_S06_T01). Per-candidate Familiarity Check
60
+ # // answer from skills/j-uncharted/SKILL.md's Convergence Loop Step 1,
61
+ # // keyed by candidate/node id so a resumed elicitation can tell which
62
+ # // candidates already answered. Values are "shallow" | "moderate" |
63
+ # // "strict". Written and read by the agent driving /uncharted, same
64
+ # // as every other checkpoint field — this script assigns it no
65
+ # // special handling beyond the dict-merge behavior documented under
66
+ # // `checkpoint` below (which exists so that checking in one
67
+ # // candidate's depth never clobbers another's already recorded
68
+ # // here). Consumed by E40_S06_T02's risk-weighted gating; unread by
69
+ # // anything in this script or E40_S06_T01's own scope.
70
+ # "<candidate-id>": "shallow" | "moderate" | "strict"
71
+ # }
72
+ # }
57
73
  # }
58
74
  #
59
75
  # ---------------------------------------------------------------------------
@@ -79,11 +95,22 @@
79
95
  # the node's note (e.g. a one-line summary of what was confirmed).
80
96
  #
81
97
  # checkpoint --id ID --json FILE
82
- # Shallow-merge the JSON object in FILE (or stdin when FILE is "-")
83
- # into the state's top-level "checkpoint" field. New keys are added;
84
- # existing keys are overwritten by the new value. This is the generic
85
- # "save progress" primitive — directory-triage results, draft node
86
- # content, anything else the flow wants durable before it might pause.
98
+ # Merge the JSON object in FILE (or stdin when FILE is "-") into the
99
+ # state's top-level "checkpoint" field. New keys are added; existing
100
+ # keys are overwritten by the new value — EXCEPT when both the existing
101
+ # value and the new value for a given key are themselves JSON objects,
102
+ # in which case they are merged one level deep instead of one replacing
103
+ # the other (existing sub-keys are kept, new sub-keys are added,
104
+ # conflicting sub-keys take the new value). This one-level dict merge
105
+ # is what lets a map-shaped field addressed by its own sub-keys — e.g.
106
+ # "verification_depth", keyed per candidate id (E40_S06_T01) — accumulate
107
+ # entries across separate checkpoint calls instead of each call
108
+ # clobbering every entry a previous call wrote. Plain (non-dict)
109
+ # values — strings, numbers, lists, directory-triage's own arrays —
110
+ # still simply overwrite, exactly as before this addition. This is the
111
+ # generic "save progress" primitive — directory-triage results, draft
112
+ # node content, anything else the flow wants durable before it might
113
+ # pause.
87
114
  #
88
115
  # pause --id ID
89
116
  # Set status "paused" and update "updated_at". The caller (the agent
@@ -156,7 +183,7 @@ fi
156
183
  # and .agents/ — scripts/ (which owns with-lock.sh) is never copied there, so
157
184
  # this script — itself shipped under skills/j-uncharted/scripts/ and mirrored
158
185
  # alongside it — cannot assume "$REPO_ROOT/scripts/with-lock.sh" exists.
159
- # Mirrors skills/init/scripts/init.sh's PKG_ROOT fallback: prefer a monorepo
186
+ # Mirrors skills/j-init/scripts/init.sh's PKG_ROOT fallback: prefer a monorepo
160
187
  # checkout's sibling scripts/ dir, else fall back to the installed npm
161
188
  # package under node_modules/@jenga-ai/agent.
162
189
  if [ -f "$SCRIPT_DIR/../../../scripts/with-lock.sh" ]; then
@@ -422,7 +449,18 @@ elif subcommand == "checkpoint":
422
449
  state = load()
423
450
  payload = json.loads(checkpoint_json)
424
451
  cp = state.setdefault("checkpoint", {})
425
- cp.update(payload)
452
+ # One-level-deep merge when both sides are dicts (E40_S06_T01) — lets a
453
+ # map-shaped field keyed by its own sub-keys (e.g. verification_depth,
454
+ # keyed per candidate id) accumulate entries across separate checkpoint
455
+ # calls instead of each call replacing the whole field. Anything else
456
+ # (strings, numbers, lists, or a dict landing on a non-dict/absent key)
457
+ # keeps the prior plain overwrite behavior.
458
+ for key, value in payload.items():
459
+ existing = cp.get(key)
460
+ if isinstance(existing, dict) and isinstance(value, dict):
461
+ existing.update(value)
462
+ else:
463
+ cp[key] = value
426
464
  state["updated_at"] = now
427
465
  atomic_write(state)
428
466
  result = {"checkpoint_keys": list(payload.keys())}
@@ -53,7 +53,7 @@ REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
53
53
  # and .agents/ — scripts/ (which owns both validators) is never copied there,
54
54
  # so this script — itself shipped under skills/j-uncharted/scripts/ and mirrored
55
55
  # alongside it — cannot assume "$REPO_ROOT/scripts/..." exists. Mirrors
56
- # skills/init/scripts/init.sh's PKG_ROOT fallback (same pattern already
56
+ # skills/j-init/scripts/init.sh's PKG_ROOT fallback (same pattern already
57
57
  # applied to this skill's elicitation-state.sh WITH_LOCK resolution): prefer
58
58
  # a monorepo checkout's sibling scripts/ dir, else fall back to the installed
59
59
  # npm package under node_modules/@jenga-ai/agent.
@@ -24,4 +24,4 @@ This file is generated/synced by `scripts/generate-j-alias.sh wtf` from `skills/
24
24
 
25
25
  ## Instructions
26
26
 
27
- `/wtf` is an alias of `/clearify`. Follow `skills/clearify/SKILL.md` in full — do not duplicate or reimplement its ambiguity-detection logic here. Read that file's `## Instructions` section and execute it exactly as written, using whatever prompt or conversation context is attached to this `/wtf` invocation.
27
+ `/wtf` is an alias of `/clearify`. Follow `skills/j-clearify/SKILL.md` in full — do not duplicate or reimplement its ambiguity-detection logic here. Read that file's `## Instructions` section and execute it exactly as written, using whatever prompt or conversation context is attached to this `/wtf` invocation.
@@ -169,8 +169,8 @@ This branch is entered when `detect-nl-intent.sh` (invoked above) classifies the
169
169
  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.
170
170
 
171
171
  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.
172
- 2. **Match** — run `skills/route/SKILL.md`'s **Step 2 — Match the Prompt to a Skill** (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. Reuse that section's matching logic by reference — do not re-author its prose here.
173
- 3. **Confident single match** — report the routing decision using `skills/route/SKILL.md`'s **Step 7 — Report Routing Decision** format (substitute `/jenga` for `/route` as the invoking command named in the report), then invoke the matched skill exactly as `skills/route/SKILL.md`'s **Step 6 — Invoke the Matched Skill** already does: 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.
172
+ 2. **Match** — run `skills/j-route/SKILL.md`'s **Step 2 — Match the Prompt to a Skill** (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. Reuse that section's matching logic by reference — do not re-author its prose here.
173
+ 3. **Confident single match** — report the routing decision using `skills/j-route/SKILL.md`'s **Step 7 — Report Routing Decision** format (substitute `/jenga` for `/route` as the invoking command named in the report), then invoke the matched skill exactly as `skills/j-route/SKILL.md`'s **Step 6 — Invoke the Matched Skill** already does: 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.
174
174
  4. **No match, or an ambiguous multi-way tie (single-skill match)** — before surfacing `/route`'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:
175
175
  - `playbook_match` → continue to **step 5 (Playbook proposal and execution)** below.
176
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.
@@ -208,12 +208,12 @@ This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl
208
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
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
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.
211
+ Then invoke the step exactly as `skills/j-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
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
213
  iv. On a `step_ready` result, repeat step 5e for the newly-named step.
214
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
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.
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.
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/j-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.
217
217
 
218
218
  #### Shared confirmation step (bare and scoped branches only)
219
219
 
@@ -228,6 +228,39 @@ After this phase completes (bare and scoped branches via confirmation, wildcard
228
228
 
229
229
  ---
230
230
 
231
+ ### Phase 0.9 — Generate the Run's Shared `orchestrator_session_id`
232
+
233
+ This phase runs for every entry mode that reaches Phase 1 at all — bare, scoped, and wildcard (`*`) —
234
+ immediately after Phase 0.75 completes and before Phase 1 begins. It does not run for the
235
+ natural-language branch's playbook/single-skill-match outcomes (E53_S01/E53_S02), since those hand off
236
+ entirely to another skill or playbook and never reach Phase 1-4 of this document.
237
+
238
+ Generate exactly **one** `orchestrator_session_id` for this entire `/jenga` run, here and only here.
239
+ Both Phase 3.5's bundle dispatch and Phase 4's first (and every later) wave dispatch need this same
240
+ value, which is why it is generated once, early, before either call site runs.
241
+
242
+ **Format.** Generate it as `jenga-<UTC ISO 8601 basic-format timestamp>` (e.g.
243
+ `jenga-20260917T162349Z`) — a short prefix naming the orchestrating skill, plus a UTC timestamp, with no
244
+ `/` or `..` characters. This follows the same session-id string convention this repo already uses for
245
+ other orchestrator-minted session ids (e.g. `/do`'s own dispatch sessions), and stays compatible with
246
+ `scripts/acquire-concurrency-slot.sh`'s own validation, which refuses a `<session_id>` argument
247
+ containing a path separator or traversal sequence, because the value is used verbatim in filenames such
248
+ as `project/queue/concurrency-slots-<session_id>.json`.
249
+
250
+ **Generate once, reuse everywhere in this run — never regenerate.** This value MUST be reused,
251
+ byte-for-byte unchanged, across:
252
+ - Phase 3.5 step 7b's bundle `/do <E##_S##>` call.
253
+ - Every wave dispatched by Phase 4 step 3, across every loop-back at Phase 4 step 5b. The loop-back
254
+ never mints a new value — it only ever references the one value generated here.
255
+
256
+ This is what allows `E32_S15`'s per-session `max_concurrent_developers`/`max_concurrent_testers` cap
257
+ (`skills/j-do/SKILL.md`'s `### 4.4`) to actually contend across an entire `/jenga` run's dispatches,
258
+ instead of every spawned `/do` sub-agent minting its own private `orchestrator_session_id` and getting
259
+ its own private, uncontended `project/queue/concurrency-slots-<id>.json` counter file — the root-cause
260
+ defect this story (`E32_S16`) fixes.
261
+
262
+ ---
263
+
231
264
  ### Phase 1 — Decompose Epics into Stories
232
265
 
233
266
  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.
@@ -272,7 +305,7 @@ For each in-scope story that has one or more tasks listed in `todo.md`:
272
305
  BUNDLE DETECTED: story <E##_S##> — <N> story-scoped tasks will execute as a bundle.
273
306
  ```
274
307
  where `<E##_S##>` is the story ID and `<N>` is the count of tasks in the list.
275
- b. Call `/do <E##_S##>` once (with the story ID, not individual task IDs). This invokes the bundle execution path in `/do` (implemented in E32_S05_T02), which runs all tasks sequentially in one shared worktree.
308
+ 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.
276
309
  c. **Mark these tasks as bundled** — record their task IDs so Phase 4 skips individual dispatch for them.
277
310
  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.
278
311
 
@@ -282,11 +315,11 @@ Loop through `todo.md` and execute all eligible items, running independent ones
282
315
 
283
316
  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`.
284
317
  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.
285
- 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.
318
+ 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 `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.
286
319
  4. **Mark In Progress** — update `status: In Progress` in each launched item's board file (YAML front-matter) immediately after launch.
287
320
  5. **Wait, drain, and loop** — once all active background agents in the wave have completed:
288
321
  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.
289
- b. **Return to step 1** of this phase to pick up any newly unblocked items — including items unblocked by the rollups just processed in (a).
322
+ b. **Return to step 1** of this phase 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.
290
323
 
291
324
  ### Exit condition
292
325
 
@@ -312,8 +345,8 @@ When no eligible candidates remain in Phase 4, exit and output:
312
345
  - **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.
313
346
  - **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.
314
347
  - **`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.
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.
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.)
348
+ - **`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/j-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.
349
+ - **`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/j-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.)
317
350
  - **`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
351
  - **`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
352
  - **`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.
@@ -329,3 +362,4 @@ When no eligible candidates remain in Phase 4, exit and output:
329
362
  - **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.
330
363
  - **`/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.
331
364
  - **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.
365
+ - **`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 5b'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.
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "board-hygiene",
3
+ "name": "Board Hygiene",
4
+ "description": "Reconciles the scrum board against what is actually implemented, captures whatever drift that turns up as follow-up todos, and reports the resulting board state -- a read-only triage chain that writes no code and produces no commit of its own.",
5
+ "keywords": [
6
+ "board hygiene",
7
+ "board triage",
8
+ "board health",
9
+ "tidy the board",
10
+ "board audit"
11
+ ],
12
+ "examples": [
13
+ "reconcile the board, capture whatever drift you find as todos, then show me the status",
14
+ "do a full board hygiene pass -- sync it with what's really implemented, queue follow-ups for anything off, and report where we stand",
15
+ "check the board against reality, turn any discrepancies into todo items, and give me an overview at the end",
16
+ "the board feels out of sync -- triage it, capture the fixes, and summarise the state afterwards",
17
+ "audit the board end to end and only queue follow-up work if something is actually wrong"
18
+ ],
19
+ "steps": [
20
+ "j-reconcile",
21
+ {
22
+ "skill": "j-todo",
23
+ "forward_from": "j-reconcile",
24
+ "conditional": {
25
+ "depends_on": "j-reconcile",
26
+ "predicate": "non_empty"
27
+ },
28
+ "instruction": "capture each reconciliation finding as a follow-up todo"
29
+ },
30
+ "j-status"
31
+ ]
32
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://jenga.local/schemas/jenga-playbook.schema.json",
4
4
  "title": "Jenga Multi-Skill Playbook",
5
- "description": "Schema for a single multi-skill playbook definition consumed by skills/jenga/scripts/load-playbooks.sh (E53_S02_T01). A playbook is a dedicated, versionable data file describing an ORDERED chain of skills that /jenga's natural-language branch may propose (as an editable, confirmable numbered list -- see skills/jenga/scripts/render-playbook-confirmation.sh, E53_S02_T03) when free-text intent spans more than one skill and does not cleanly resolve to a single one via skills/route/SKILL.md's Step 2 matching. This file itself (schema.json) is never treated as a playbook -- load-playbooks.sh explicitly excludes it by filename when scanning skills/jenga/playbooks/*.json.",
5
+ "description": "Schema for a single multi-skill playbook definition consumed by skills/jenga/scripts/load-playbooks.sh (E53_S02_T01). A playbook is a dedicated, versionable data file describing an ORDERED chain of skills that /jenga's natural-language branch may propose (as an editable, confirmable numbered list -- see skills/jenga/scripts/render-playbook-confirmation.sh, E53_S02_T03) when free-text intent spans more than one skill and does not cleanly resolve to a single one via skills/j-route/SKILL.md's Step 2 matching. This file itself (schema.json) is never treated as a playbook -- load-playbooks.sh explicitly excludes it by filename when scanning skills/jenga/playbooks/*.json.",
6
6
  "type": "object",
7
7
  "required": ["id", "name", "description", "keywords", "examples", "steps"],
8
8
  "additionalProperties": false,
@@ -18,25 +18,92 @@
18
18
  },
19
19
  "description": {
20
20
  "type": "string",
21
- "description": "One-sentence explanation of what this playbook accomplishes end-to-end, used as the lowest-priority match signal (description match, same as skills/route/SKILL.md's Step 2 Pass 3) when keywords/examples don't produce a confident match."
21
+ "description": "One-sentence explanation of what this playbook accomplishes end-to-end, used as the lowest-priority match signal (description match, same as skills/j-route/SKILL.md's Step 2 Pass 3) when keywords/examples don't produce a confident match."
22
22
  },
23
23
  "keywords": {
24
24
  "type": "array",
25
- "description": "Short phrases (1-3 words) for verbatim, case-insensitive keyword matching against the raw natural-language prompt -- the highest-priority match signal (Pass 1), mirroring skills/route/SKILL.md's Step 2 Pass 1 semantics exactly, but scoped to this playbook's catalog rather than the single-skill catalog.",
25
+ "description": "Short phrases (1-3 words) for verbatim, case-insensitive keyword matching against the raw natural-language prompt -- the highest-priority match signal (Pass 1), mirroring skills/j-route/SKILL.md's Step 2 Pass 1 semantics exactly, but scoped to this playbook's catalog rather than the single-skill catalog.",
26
26
  "items": { "type": "string" },
27
27
  "minItems": 1
28
28
  },
29
29
  "examples": {
30
30
  "type": "array",
31
- "description": "Natural-language example prompts a user might type that should resolve to this playbook. Used for the semantic similarity match (Pass 2), mirroring skills/route/SKILL.md's Step 2 Pass 2 semantics. At least one example must plausibly span the full breadth of this playbook's steps (not just its first step) so it is distinguishable from a plain single-skill match.",
31
+ "description": "Natural-language example prompts a user might type that should resolve to this playbook. Used for the semantic similarity match (Pass 2), mirroring skills/j-route/SKILL.md's Step 2 Pass 2 semantics. At least one example must plausibly span the full breadth of this playbook's steps (not just its first step) so it is distinguishable from a plain single-skill match.",
32
32
  "items": { "type": "string" },
33
33
  "minItems": 1
34
34
  },
35
35
  "steps": {
36
36
  "type": "array",
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
- "items": { "type": "string" },
37
+ "description": "Ordered list of steps making up this playbook's chain, in the exact execution order. A step is either a BARE STRING or a STEPOBJECT (E53_S03; composition added by E53_S05). A bare string is shorthand for {\"skill\": \"<name>\"} -- \"bare\" describes the JSON shape, never a bare skill NAME. A skill name is always a canonical skill DIRECTORY name (the directory under skills/<dir>/SKILL.md, e.g. \"j-brainstorm\", not the \"j.brainstorm\" frontmatter/invocation form and not the retired bare \"brainstorm\"); per E50_S10's canonical naming contract the canonical directory is skills/j-<name>/, so a step value carries the j- prefix, while the three permanent exceptions (jenga, jenga-permission-level, index) keep their bare directory names and are written bare. Each skill name 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. NOTE: this schema documents SHAPE only. The cross-step rules -- forward_from naming an EARLIER step whose skill declares a non-empty output_types, conditional.depends_on naming an EARLIER step, composition cycle/depth limits -- are not expressible here and are enforced by load-playbooks.sh, which remains the single source of truth for validation.",
38
+ "items": {
39
+ "oneOf": [
40
+ {
41
+ "type": "string",
42
+ "description": "Bare-string step: shorthand for {\"skill\": \"<name>\"}, unchanged original behavior. An all-bare-string playbook loads byte-for-byte the same as it did before E53_S03, and a bare-string step is never rewritten into object form in the loader's output."
43
+ },
44
+ { "$ref": "#/definitions/stepObject" }
45
+ ]
46
+ },
39
47
  "minItems": 2
40
48
  }
49
+ },
50
+ "definitions": {
51
+ "stepObject": {
52
+ "type": "object",
53
+ "description": "A StepObject step (E53_S03_T01). Carries EXACTLY ONE of the mutually exclusive target fields `skill` or `playbook` -- a step naming both, or naming neither, is rejected by load-playbooks.sh.",
54
+ "additionalProperties": false,
55
+ "oneOf": [
56
+ { "required": ["skill"], "not": { "required": ["playbook"] } },
57
+ { "required": ["playbook"], "not": { "required": ["skill"] } }
58
+ ],
59
+ "properties": {
60
+ "skill": {
61
+ "type": "string",
62
+ "description": "Invokes a single skill, same as a bare string. Value is a canonical skill directory name (see the `steps` description above)."
63
+ },
64
+ "playbook": {
65
+ "type": "string",
66
+ "description": "Composes another playbook by ID (E53_S05). Resolved and flattened in place at load time, so the emitted catalog's `steps` array never contains a raw playbook-type entry. Existence validation, cycle detection, and a configurable nesting-depth limit (project/configs/playbook-config.json's max_composition_depth, default 3) all apply."
67
+ },
68
+ "instruction": {
69
+ "type": "string",
70
+ "description": "Static natural-language text appended to this step's own invocation message."
71
+ },
72
+ "forward_from": {
73
+ "type": "string",
74
+ "description": "Names an EARLIER step in the same playbook (by that step's skill name) whose typed output becomes this step's actual invocation input. The named source's SKILL.md must declare a non-empty output_types -- a skill declaring nothing can never be a forward source. Transparent across composition boundaries, since composition resolution runs before this validation."
75
+ },
76
+ "resolve": {
77
+ "type": "string",
78
+ "description": "Natural-language instructions for reshaping/filtering/type-bridging the value forwarded into this step (e.g. \"pick the first three items\"). A step carrying both `resolve` and `playbook` is rejected at load time: resolve may never pre-authorize a downstream confirmation gate (E53_S07's safety review, permanently rejected)."
79
+ },
80
+ "conditional": {
81
+ "type": "object",
82
+ "description": "Runs this step only when the predicate holds against a named earlier step's captured output; otherwise the step is marked `skipped` and the chain continues without halting (E53_S04_T02). Unlike forward_from, this does NOT require the depended-on step to declare output_types.",
83
+ "additionalProperties": false,
84
+ "required": ["depends_on", "predicate"],
85
+ "properties": {
86
+ "depends_on": {
87
+ "type": "string",
88
+ "description": "Names an EARLIER skill step in this playbook whose captured output the predicate is evaluated against.",
89
+ "minLength": 1
90
+ },
91
+ "predicate": {
92
+ "type": "string",
93
+ "description": "The recognized predicate grammar, whose single source of truth is skills/jenga/scripts/run-playbook-step.sh: non_empty | empty | equals:<value> | not_equals:<value>.",
94
+ "pattern": "^(non_empty|empty|equals:.+|not_equals:.+)$"
95
+ }
96
+ }
97
+ },
98
+ "version": {
99
+ "type": "string",
100
+ "description": "RESERVED, currently a no-op. Accepted and passed through unchanged; not acted upon by anything yet. Exists so a future schema revision has a place to declare itself without a retroactive migration of every existing playbook."
101
+ },
102
+ "schema_version": {
103
+ "type": "string",
104
+ "description": "Alternate key name for `version`. Same reserved no-op semantics."
105
+ }
106
+ }
107
+ }
41
108
  }
42
109
  }
@@ -0,0 +1,19 @@
1
+ {
2
+ "id": "understand-then-commit",
3
+ "name": "Understand Then Build",
4
+ "description": "Investigates existing or unfamiliar code first, then runs the full idea-to-commit pipeline on top of what was learned -- a composed chain built from the canonical idea-to-committed playbook, terminating at the commit.",
5
+ "keywords": [
6
+ "understand then build",
7
+ "investigate then build",
8
+ "explore then implement",
9
+ "onboard and build",
10
+ "learn the codebase first"
11
+ ],
12
+ "examples": [
13
+ "help me understand this codebase first, then plan a change here, build it, and commit it",
14
+ "investigate this existing code, then take an idea through planning, implementation, and a commit",
15
+ "look into what's already here, then brainstorm a fix, build it, and commit the work",
16
+ "onboard me to this project and then walk a new idea from planning through to a committed change"
17
+ ],
18
+ "steps": ["j-uncharted", {"playbook": "idea-to-committed"}]
19
+ }
@@ -46,7 +46,7 @@ fi
46
46
 
47
47
  # Resolve the jenga-agent PACKAGE root (where lib/generate-skill-allow-list.js and the canonical
48
48
  # skills/ tree actually live) — same monorepo-checkout vs. installed-npm-package detection used
49
- # by skills/init/scripts/init.sh's PKG_ROOT resolution.
49
+ # by skills/j-init/scripts/init.sh's PKG_ROOT resolution.
50
50
  if [ -d "$SCRIPT_DIR/../../../templates" ]; then
51
51
  PKG_ROOT="$SCRIPT_DIR/../../.."
52
52
  elif [ -d "$JENGA_PROJECT_DIR/node_modules/@jenga-ai/agent/templates" ]; then
@@ -461,18 +461,30 @@ if [ -n "${JENGA_PLAYBOOKS_TEST_ROOT:-}" ]; then
461
461
  # E53_S05_T01 to also cover playbook-config.json resolution). Never set in a real invocation.
462
462
  PKG_ROOT="$JENGA_PLAYBOOKS_TEST_ROOT"
463
463
  PROJECT_DIR="$JENGA_PLAYBOOKS_TEST_ROOT"
464
- # Resolve the jenga-agent PACKAGE root (where the canonical skills/ tree actually lives) — same
465
- # monorepo-checkout vs. installed-npm-package detection used by
466
- # skills/jenga/scripts/load-nl-catalog.sh's PKG_ROOT resolution and skills/init/scripts/init.sh.
467
- elif [ -d "$SCRIPT_DIR/../../../templates" ]; then
468
- PKG_ROOT="$SCRIPT_DIR/../../.."
469
- PROJECT_DIR="${JENGA_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || pwd)}}"
470
- elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}/node_modules/@jenga-ai/agent/templates" ]; then
471
- PKG_ROOT="${CLAUDE_PROJECT_DIR}/node_modules/@jenga-ai/agent"
472
- PROJECT_DIR="${JENGA_PROJECT_DIR:-$CLAUDE_PROJECT_DIR}"
473
464
  else
474
- echo "Error: could not locate the jenga-agent package root (templates/ not found via monorepo checkout or node_modules/@jenga-ai/agent)." >&2
475
- exit 2
465
+ # Resolve JENGA_PROJECT_DIR the same way every other script in skills/jenga/scripts/ does.
466
+ if [ -f "$SCRIPT_DIR/../../../lib/resolve-project-dir.sh" ]; then
467
+ # shellcheck source=/dev/null
468
+ source "$SCRIPT_DIR/../../../lib/resolve-project-dir.sh"
469
+ elif [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
470
+ JENGA_PROJECT_DIR="$CLAUDE_PROJECT_DIR"
471
+ else
472
+ JENGA_PROJECT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || pwd)"
473
+ fi
474
+
475
+ # Resolve the jenga-agent PACKAGE root (where the canonical skills/ tree actually lives) — same
476
+ # monorepo-checkout vs. installed-npm-package detection used by
477
+ # skills/jenga/scripts/load-nl-catalog.sh's PKG_ROOT resolution and skills/j-init/scripts/init.sh.
478
+ if [ -d "$SCRIPT_DIR/../../../templates" ]; then
479
+ PKG_ROOT="$SCRIPT_DIR/../../.."
480
+ PROJECT_DIR="$JENGA_PROJECT_DIR"
481
+ elif [ -d "$JENGA_PROJECT_DIR/node_modules/@jenga-ai/agent/templates" ]; then
482
+ PKG_ROOT="$JENGA_PROJECT_DIR/node_modules/@jenga-ai/agent"
483
+ PROJECT_DIR="$JENGA_PROJECT_DIR"
484
+ else
485
+ echo "Error: could not locate the jenga-agent package root (templates/ not found via monorepo checkout or node_modules/@jenga-ai/agent)." >&2
486
+ exit 2
487
+ fi
476
488
  fi
477
489
 
478
490
  PLAYBOOKS_DIR="$PKG_ROOT/skills/jenga/playbooks"
@@ -3,7 +3,7 @@
3
3
  # skills/jenga/scripts/match-playbook.sh
4
4
  #
5
5
  # Deterministic PLAYBOOK matcher for `/jenga`'s natural-language branch (E53_S02_T02). Runs the
6
- # same three-pass matching *philosophy* as `skills/route/SKILL.md`'s Step 2 (keyword ->
6
+ # same three-pass matching *philosophy* as `skills/j-route/SKILL.md`'s Step 2 (keyword ->
7
7
  # example similarity -> description), but scoped to the playbook catalog produced by
8
8
  # `load-playbooks.sh` (E53_S02_T01) instead of the single-skill catalog `load-nl-catalog.sh`
9
9
  # produces for `/route`/`/jenga`'s existing single-skill matching.
@@ -297,7 +297,7 @@ currently gates on its presence.
297
297
 
298
298
  ### `[SPIKE]` — Bounded Research
299
299
 
300
- **Scope:** story and task level only.
300
+ **Scope:** story and task level, **and rapports** (extended by `E35_S03_T01`, see below).
301
301
 
302
302
  **Meaning:** a time-boxed research or exploration effort whose output is a decision, a design note,
303
303
  or an answered question — **not** shippable implementation code. Existing usage (`E06_S03_spike-editable-board.md`,
@@ -312,6 +312,19 @@ or an answered question — **not** shippable implementation code. Existing usag
312
312
  `[SPIKE]` predates this document; this section formalizes an existing informal convention rather
313
313
  than introducing new behavior.
314
314
 
315
+ **Rapport scope extension (`E35_S03_T01`):** `[SPIKE]` also applies to a problem rapport (see
316
+ `project/rapports/problems/`) via the same title-text convention — a `[SPIKE] <Topic>` prefix on
317
+ the rapport's own title/header field, not a frontmatter key (rapports don't carry board
318
+ frontmatter at all). This marks a rapport as itself surfacing a bounded research question or a
319
+ distinct idea worth tracking, independent of whatever problem the rapport was originally filed
320
+ to report. In practice this happens as part of the idea-lifecycle outcome-record mechanism
321
+ (`E35_S03`): when a `project/ideas.md` entry that originated from a rapport (see
322
+ `skills/idea/SKILL.md`'s "Source-Rapport Link Convention") is later promoted or rejected, the
323
+ outcome-record step updates that rapport with the decision and prefixes its title `[SPIKE]` if
324
+ it isn't already tagged. As with the story/task usage above, this is advisory only — nothing
325
+ validates or enforces the prefix's presence, and a rapport's normal `Type:`/`Related
326
+ Epic/Story/Task` fields (per `templates/PROBLEM_RAPPORT_TEMPLATE.md`) are unaffected by it.
327
+
315
328
  ### `[ARCH]` — Durable Architectural Inventory
316
329
 
317
330
  **Scope:** epic, story, and task level. This is the **first tag extended to epic level** — `[SPIKE]`
@@ -3,8 +3,20 @@ name: <skill-name>
3
3
  description: <One-sentence description of what this skill does and when to use it.>
4
4
  metadata:
5
5
  prefered_agent: <agent-name> # optional — remove if not applicable
6
+ output_types: <type> # optional — forwardable output type(s) this skill produces; a single
7
+ # type string or a list of {when, type} entries. Declare only if this
8
+ # skill genuinely produces output a playbook step could consume
9
+ # (honesty over breadth, E53_S11). Remove if not applicable.
10
+ input_types: <type> # optional — forwarded input type(s) this skill accepts; same two
11
+ # shapes as output_types. May be broadly declared — honesty over
12
+ # breadth is scoped to output_types only (E62_S01_T03).
13
+ # Remove if not applicable.
6
14
  ---
7
15
 
16
+ > Type values come from the canonical vocabulary in `templates/playbook-types.json`. See
17
+ > `docs/skill-authoring.md`'s `output_types` and `input_types` sections for the full contract,
18
+ > including the `text` rule and the normalize-versus-convert boundary.
19
+
8
20
  # <Skill Title> — <Short tagline>
9
21
 
10
22
  ## Instructions
@@ -6,7 +6,7 @@
6
6
  "autoMode": {
7
7
  "allow": [
8
8
  "$defaults",
9
- "Bash(bash skills/mirror-public/scripts/mirror.sh*)"
9
+ "Bash(bash skills/j-mirror-public/scripts/mirror.sh*)"
10
10
  ]
11
11
  },
12
12
  "permissions": {
@@ -6,7 +6,7 @@
6
6
  "autoMode": {
7
7
  "allow": [
8
8
  "$defaults",
9
- "Bash(bash skills/mirror-public/scripts/mirror.sh*)"
9
+ "Bash(bash skills/j-mirror-public/scripts/mirror.sh*)"
10
10
  ]
11
11
  },
12
12
  "permissions": {
@@ -1,8 +1,36 @@
1
1
  {
2
- "_comment": "Canonical playbook output-type vocabulary for Epic E53's Playbooks v2 StepObject/output_types contract (E53_S03_T02). Extend this vocabulary by adding entries to the 'types' array below -- NEVER by code changes; skills/jenga/scripts/load-playbooks.sh references this file by name in its own header contract (see that script's 'TYPE REGISTRY' section). Governed by the Scrum Master: registry additions/changes route through the normal board-item process rather than an ungoverned edit to this file -- see docs/skill-authoring.md's 'Playbook Type Registry Governance' section for the full policy.",
3
- "types": [
4
- "text",
5
- "id_list",
6
- "file_list"
7
- ]
2
+ "_comment": "Canonical playbook type registry for Epic E53's Playbooks v2 StepObject/output_types contract (E53_S03_T02), converted from a bare list of type names into a map of type descriptors by E62_S01_T01. -- FORMAT: 'types' maps each type name to a descriptor object {\"verify\": <rule-object|null>, \"normalize\": [<transform-name>, ...]}. Both members are optional in the sense that 'verify' may be null and 'normalize' may be empty; a descriptor never carries any other key. -- VERIFY: null means the type has no checkable shape and runtime verification is a deliberate no-op. Otherwise it is an object whose keys are drawn ONLY from the enumerated 'verify_rules' below. The single rule kind defined today is 'per_line': a regex that every element of the normalized value must match. Regexes here are POSIX ERE, not PCRE -- write [0-9] and [^[:space:]], never \\d or \\S -- because the consuming validator is a shell script (E62_S01_T02). -- NORMALIZE: an ordered list of named transforms applied left to right BEFORE verification. The value is treated as a list of lines; 'split_on_comma' further splits each element on commas, 'trim' strips leading and trailing whitespace from each element, 'drop_empty' removes elements that are empty after trimming. Every entry MUST be a bare identifier matching ^[a-z][a-z0-9_]*$ AND a member of 'normalize_vocabulary' below. That closure is the single most important property of this format: because an entry cannot contain whitespace, a free-text or semantic-extraction instruction (e.g. 'extract the ids the skill meant to return') is STRUCTURALLY UNREPRESENTABLE here, not merely discouraged. LLM-driven auto-conversion and a type-repair skill were both rejected at epic level (E62); a transform name the validator does not implement is a hard error, never a string for anything to interpret. -- THE 'text' RULE: 'text' carries \"verify\": null because prose has no checkable shape. This is NOT a licence to treat 'text' as an 'any' in the load-time compatibility lattice. Unverifiable at runtime: yes. Universal acceptor at load time: no -- a universal acceptor would make every text-declaring skill compatible with everything, disarming exactly what E53_S11's honesty-over-breadth policy protects. -- EXTENDING: adding a TYPE is a DATA-ONLY edit and NEVER a code change -- add a key to 'types' whose verify/normalize are composed from the already-enumerated 'verify_rules' and 'normalize_vocabulary'. Adding a new TRANSFORM or a new RULE KIND is the one thing that does require code, since the validator has to implement it; extend the corresponding enumeration here in that same change. Removing or renaming an existing type is a breaking change: it invalidates every SKILL.md declaring it. skills/jenga/scripts/load-playbooks.sh references this file by name in its own header contract (see that script's 'TYPE REGISTRY' section). -- GOVERNANCE: owned by the Scrum Master. Registry additions or changes route through the normal board-item process rather than an ungoverned edit to this file -- see docs/skill-authoring.md's 'Playbook Type Registry Governance' section for the full policy.",
3
+ "verify_rules": [
4
+ "per_line"
5
+ ],
6
+ "normalize_vocabulary": [
7
+ "split_on_comma",
8
+ "trim",
9
+ "drop_empty"
10
+ ],
11
+ "types": {
12
+ "text": {
13
+ "verify": null,
14
+ "normalize": []
15
+ },
16
+ "id_list": {
17
+ "verify": {
18
+ "per_line": "^E[0-9]+(_S[0-9]+(_T[0-9]+)?)?$"
19
+ },
20
+ "normalize": [
21
+ "split_on_comma",
22
+ "trim",
23
+ "drop_empty"
24
+ ]
25
+ },
26
+ "file_list": {
27
+ "verify": {
28
+ "per_line": "^[^[:space:]]"
29
+ },
30
+ "normalize": [
31
+ "trim",
32
+ "drop_empty"
33
+ ]
34
+ }
35
+ }
8
36
  }