@jenga-ai/agent 3.6.0 → 4.1.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 (86) hide show
  1. package/README.md +32 -1
  2. package/lib/generate-skill-allow-list.js +42 -5
  3. package/lib/skill-allow-list.json +1 -1
  4. package/package.json +1 -2
  5. package/project/app/api/lib/resolve-project-root.js +1 -1
  6. package/project/app/api/scripts/capture-snapshot.js +12 -7
  7. package/project/app/package.json +4 -0
  8. package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
  9. package/project/app/ui/dist/assets/index-DX2pfTAW.js +104 -0
  10. package/project/app/ui/dist/index.html +2 -2
  11. package/scripts/acquire-concurrency-slot.sh +34 -4
  12. package/scripts/apply-j-prefix.sh +1 -1
  13. package/scripts/delete-bare-skill-dirs.sh +1 -2
  14. package/scripts/idea_manager.sh +16 -1
  15. package/scripts/release-concurrency-slot.sh +34 -4
  16. package/scripts/render-ranked-list.sh +270 -0
  17. package/scripts/repoint-dead-bare-path-prose.py +81 -0
  18. package/scripts/rewrite-stale-skill-preambles.py +188 -0
  19. package/scripts/strip-polyfill-frontmatter.py +166 -0
  20. package/scripts/todo_manager.sh +16 -1
  21. package/scripts/validate-typed-object.sh +750 -0
  22. package/scripts/verify-postinstall-reconcile.sh +33 -7
  23. package/skills/index/scripts/board_index.py +29 -1
  24. package/skills/j-brainstorm/SKILL.md +3 -4
  25. package/skills/j-btw/SKILL.md +3 -4
  26. package/skills/j-clearify/SKILL.md +3 -4
  27. package/skills/j-close-story/SKILL.md +11 -12
  28. package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
  29. package/skills/j-commit/SKILL.md +3 -4
  30. package/skills/j-continue/SKILL.md +5 -6
  31. package/skills/j-deep-dive/SKILL.md +3 -4
  32. package/skills/j-distribute/SKILL.md +3 -4
  33. package/skills/j-do/SKILL.md +13 -15
  34. package/skills/j-doc/SKILL.md +3 -4
  35. package/skills/j-doc/scripts/resolve_last_update.py +27 -1
  36. package/skills/j-doc-sync/SKILL.md +3 -4
  37. package/skills/j-dooo/SKILL.md +6 -15
  38. package/skills/j-error/SKILL.md +3 -4
  39. package/skills/j-evaluate/SKILL.md +3 -4
  40. package/skills/j-examplify/SKILL.md +3 -4
  41. package/skills/j-help/SKILL.md +3 -4
  42. package/skills/j-idea/SKILL.md +3 -4
  43. package/skills/j-improve/SKILL.md +3 -4
  44. package/skills/j-init/SKILL.md +20 -11
  45. package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
  46. package/skills/j-jbp/SKILL.md +3 -4
  47. package/skills/j-lgtm/SKILL.md +3 -4
  48. package/skills/j-pi-plan/SKILL.md +3 -4
  49. package/skills/j-proceed/SKILL.md +3 -4
  50. package/skills/j-publish/SKILL.md +3 -4
  51. package/skills/j-publish/scripts/run_gates.sh +1 -1
  52. package/skills/j-reconcile/SKILL.md +38 -5
  53. package/skills/j-reconcile/assets/report_format.md +11 -0
  54. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
  55. package/skills/j-reconcile-origin/SKILL.md +3 -4
  56. package/skills/j-redo/SKILL.md +3 -4
  57. package/skills/j-skillify/SKILL.md +3 -4
  58. package/skills/j-spinoff/SKILL.md +3 -4
  59. package/skills/j-status/SKILL.md +4 -5
  60. package/skills/j-todo/SKILL.md +42 -5
  61. package/skills/j-todo/scripts/add_trivial_task.sh +12 -1
  62. package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
  63. package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
  64. package/skills/j-uncharted/SKILL.md +251 -12
  65. package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
  66. package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
  67. package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
  68. package/skills/j-uncharted/scripts/run-engine.sh +36 -2
  69. package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
  70. package/skills/j-wtf/SKILL.md +3 -4
  71. package/skills/jenga/SKILL.md +106 -10
  72. package/skills/jenga/playbooks/schema.json +4 -4
  73. package/skills/jenga/scripts/enrich-nl-prompt.sh +225 -0
  74. package/skills/jenga/scripts/load-nl-catalog.js +6 -3
  75. package/skills/jenga/scripts/load-playbooks.sh +289 -4
  76. package/skills/jenga/scripts/match-playbook.sh +6 -5
  77. package/skills/jenga/scripts/run-playbook-step.sh +270 -3
  78. package/templates/permission-levels/level-1-locked.json +1 -1
  79. package/templates/permission-levels/level-2-guarded.json +1 -1
  80. package/templates/permission-levels/level-3-standard.json +1 -1
  81. package/templates/permission-levels/level-4-elevated.json +1 -1
  82. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  83. package/templates/playbook-types.json +6 -0
  84. package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
  85. package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
  86. package/scripts/audit-twin-divergence.sh +0 -693
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env bash
2
+ # write-scan-record.sh — write a COMMITTED baseline scan-record after a successful onboard/refresh run
3
+ #
4
+ # Usage: write-scan-record.sh [options] [<report.json>]
5
+ # discover-subsystems.sh <root> | write-scan-record.sh [options]
6
+ # write-scan-record.sh --help
7
+ #
8
+ # `onboard` mode (and, once E40_S07_T02/T03 land, `refresh` mode) needs a durable, DISKED record
9
+ # of what a scan actually found, so a later run can tell what changed since the last one. This
10
+ # script is that record's writer, and only that — baseline DISCOVERY (which scan-record is the
11
+ # authoritative one to diff against) and DIFF CLASSIFICATION (unchanged/changed/new/removed) are
12
+ # separate, not-yet-implemented scripts (E40_S07_T02, E40_S07_T03). This script only writes.
13
+ #
14
+ # This is deliberately NOT elicitation-state.sh's persistence mechanism. elicitation-state.sh's
15
+ # state file is explicit, transient SESSION SCRATCH — git-ignored, meant to survive a pause within
16
+ # one elicitation, not to persist meaning between separate onboard/refresh runs weeks or months
17
+ # apart. This record is the opposite: COMMITTED to the repository (never git-ignored), and is the
18
+ # only thing that gives a future `refresh` run something durable to compare against.
19
+ #
20
+ # This script performs NO discovery of its own. It consumes discover-subsystems.sh's ranked
21
+ # output (the same report apply-subsystem-cap.sh consumes) and contributes only the record write.
22
+ # It is read-only against the analysed codebase — its only write is the scan-record file itself.
23
+ #
24
+ # ---------------------------------------------------------------------------
25
+ # INPUT
26
+ # ---------------------------------------------------------------------------
27
+ # discover-subsystems.sh's JSON report, from a file argument or from stdin (`-`, or no argument) —
28
+ # the same input contract apply-subsystem-cap.sh uses, so both scripts can consume the identical
29
+ # discover-subsystems.sh invocation in one onboard run. The fields read are the report's `root`
30
+ # (repo-relative analysed root) plus each `candidates[]` entry's `path`, `files`, and `lines` —
31
+ # real fields discover-subsystems.sh actually emits (see that script's own OUTPUT CONTRACT
32
+ # comment), nothing invented. Everything else in the input report is ignored.
33
+ #
34
+ # ---------------------------------------------------------------------------
35
+ # OPTIONS
36
+ # ---------------------------------------------------------------------------
37
+ # --out-dir DIR Where the scan-record is written. Default <repo-root>/project/rapports/analysis
38
+ # — the same directory the Understanding Document convention uses, kept
39
+ # distinguishable from it by filename suffix (see OUTPUT below), never by a
40
+ # separate location.
41
+ # --json-out FILE Also write the same JSON to this exact path (in addition to the timestamped
42
+ # file under --out-dir). Not written unless requested.
43
+ # --label TEXT Human label for the analysed codebase, used only in the output filename's
44
+ # slug. Default: the report's own `root` (or its basename when `root` is "."
45
+ # or empty, the same fallback apply-subsystem-cap.sh uses).
46
+ # -h, --help Show this help and exit 0.
47
+ #
48
+ # ---------------------------------------------------------------------------
49
+ # OUTPUT
50
+ # ---------------------------------------------------------------------------
51
+ # stdout : the absolute path of the written scan-record file. One line, nothing else — so
52
+ # `PATH=$(write-scan-record.sh ...)` is safe, matching run-engine.sh's own contract.
53
+ # stderr : notices and diagnostics only, never document content.
54
+ #
55
+ # Filename: uncharted-scan-record-<slug>-<YYYYMMDDTHHMMSSZ>.scan-record.json, under
56
+ # project/rapports/analysis/ — the SAME directory the Understanding Document uses, but never the
57
+ # same file: the `.scan-record.json` suffix (as opposed to a document's `.md`) is what keeps a run
58
+ # that produces both from overloading one artifact with two purposes. A name collision gets a
59
+ # -2, -3... suffix, the same convention run-engine.sh uses, so repeated runs leave a diffable
60
+ # history rather than clobbering each other. This script always writes a FRESH, timestamped file
61
+ # per run rather than mutating a prior one in place — baseline DISCOVERY (deciding which prior
62
+ # scan-record is the one to diff against) is a separate, later concern (E40_S07_T02); this script's
63
+ # only job is to make sure a new one always exists to be found.
64
+ #
65
+ # ---------------------------------------------------------------------------
66
+ # RECORD SHAPE (the written JSON, and what --json-out duplicates)
67
+ # ---------------------------------------------------------------------------
68
+ # {
69
+ # "script": "write-scan-record.sh",
70
+ # "version": 1,
71
+ # "baseline_commit": "<short HEAD SHA>" | null, // null only when the repo has no commits yet
72
+ # "scanned_at": "<ISO 8601 UTC timestamp>",
73
+ # "root": "<repo-relative analysed root, from the input report>",
74
+ # "candidates": [
75
+ # { "path": "<path relative to root>", "files": <int>, "lines": <int> }, ...
76
+ # ],
77
+ # "candidate_count": <int>,
78
+ # "notices": [ "<non-fatal diagnostic>", ... ]
79
+ # }
80
+ #
81
+ # `baseline_commit` is `git -C <repo-root> rev-parse --short HEAD` at scan time — the repo that
82
+ # OWNS the engine (this repo), matching every sibling script's REPO_ROOT anchor, not the analysed
83
+ # root (which `import`-staged or out-of-repo targets may not even share a git history with).
84
+ #
85
+ # ---------------------------------------------------------------------------
86
+ # EXIT CODES
87
+ # ---------------------------------------------------------------------------
88
+ # 0 — success; scan-record written, its path printed on stdout
89
+ # 1 — usage error: unknown flag, missing value, more than one positional input
90
+ # 2 — input error: report missing/unreadable/unparseable JSON, or not a discover-subsystems.sh
91
+ # report (no "candidates" array)
92
+ # 4 — write failure: --out-dir (or --json-out's directory) could not be created or is not
93
+ # writable. Deliberately fatal: a "successful" run that silently failed to leave a durable
94
+ # record would defeat the entire point of this script, the same reasoning
95
+ # apply-subsystem-cap.sh applies to its own rapport write.
96
+ #
97
+ # Examples:
98
+ # discover-subsystems.sh . | write-scan-record.sh
99
+ # discover-subsystems.sh . > s.json && write-scan-record.sh --label "my-app" s.json
100
+ # write-scan-record.sh --json-out /tmp/latest-scan-record.json s.json
101
+ #
102
+ # Requires: bash, git, python3. jq is NOT required — JSON is parsed and emitted by python3.
103
+
104
+ set -euo pipefail
105
+
106
+ OUT_DIR=""
107
+ JSON_OUT=""
108
+ LABEL=""
109
+ INPUT=""
110
+
111
+ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
112
+
113
+ usage() {
114
+ cat <<EOF
115
+ Usage: $(basename "$0") [options] [<report.json>]
116
+
117
+ Write a COMMITTED baseline scan-record from discover-subsystems.sh output: baseline_commit
118
+ (repo HEAD short SHA), scanned_at (ISO 8601 UTC), root, and one candidates[] entry per subsystem
119
+ (path, files, lines). Unlike elicitation-state.sh's transient session scratch, this record is
120
+ never git-ignored and is meant to survive indefinitely between onboard/refresh runs.
121
+
122
+ Arguments:
123
+ <report.json> discover-subsystems.sh output. Omit, or pass "-", to read stdin.
124
+
125
+ Options:
126
+ --out-dir DIR Directory for the scan-record (default: <repo-root>/project/rapports/analysis)
127
+ --json-out FILE Also write the same JSON to this exact path
128
+ --label TEXT Human label used only in the output filename's slug (default: the report's root)
129
+ -h, --help Show this help and exit
130
+
131
+ Exit codes: 0 success, 1 usage error, 2 input error, 4 write failure.
132
+ EOF
133
+ }
134
+
135
+ die_usage() {
136
+ echo "Error: $1" >&2
137
+ echo >&2
138
+ usage >&2
139
+ exit 1
140
+ }
141
+
142
+ require_value() {
143
+ # require_value <flag> <remaining-arg-count>
144
+ [ "$2" -ge 2 ] || die_usage "$1 requires a value"
145
+ }
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Argument parsing
149
+ # ---------------------------------------------------------------------------
150
+
151
+ while [ "$#" -gt 0 ]; do
152
+ case "$1" in
153
+ --out-dir) require_value "--out-dir" "$#"; OUT_DIR="$2"; shift 2 ;;
154
+ --out-dir=*) OUT_DIR="${1#*=}"; shift ;;
155
+ --json-out) require_value "--json-out" "$#"; JSON_OUT="$2"; shift 2 ;;
156
+ --json-out=*) JSON_OUT="${1#*=}"; shift ;;
157
+ --label) require_value "--label" "$#"; LABEL="$2"; shift 2 ;;
158
+ --label=*) LABEL="${1#*=}"; shift ;;
159
+ -h|--help) usage; exit 0 ;;
160
+ --)
161
+ shift
162
+ [ "$#" -le 1 ] || die_usage "at most one input report is accepted"
163
+ [ "$#" -eq 0 ] || INPUT="$1"
164
+ break ;;
165
+ -)
166
+ INPUT="-"; shift ;;
167
+ -*)
168
+ die_usage "unknown option \"$1\"" ;;
169
+ *)
170
+ [ -z "$INPUT" ] || die_usage "at most one input report is accepted (got \"$INPUT\" and \"$1\")"
171
+ INPUT="$1"; shift ;;
172
+ esac
173
+ done
174
+
175
+ # ---------------------------------------------------------------------------
176
+ # Input resolution
177
+ # ---------------------------------------------------------------------------
178
+
179
+ if [ -z "$INPUT" ] || [ "$INPUT" = "-" ]; then
180
+ INPUT="-"
181
+ else
182
+ if [ ! -e "$INPUT" ]; then
183
+ echo "Error: input report does not exist: $INPUT" >&2
184
+ echo " Expected discover-subsystems.sh JSON output." >&2
185
+ exit 2
186
+ fi
187
+ if [ ! -f "$INPUT" ] || [ ! -r "$INPUT" ]; then
188
+ echo "Error: input report is not a readable file: $INPUT" >&2
189
+ exit 2
190
+ fi
191
+ INPUT=$(cd -- "$(dirname -- "$INPUT")" && pwd -P)/$(basename -- "$INPUT")
192
+ fi
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # Output destinations — pre-flighted BEFORE any work, same fail-fast contract as
196
+ # run-engine.sh / apply-subsystem-cap.sh: an unwritable destination must surface with nothing
197
+ # done, rather than after the record has already been computed.
198
+ #
199
+ # Anchored on THIS SCRIPT, not on the analysed root: the record belongs to the project that owns
200
+ # the engine, even when the analysed root lives elsewhere (import staging areas, out-of-repo
201
+ # targets).
202
+ # ---------------------------------------------------------------------------
203
+
204
+ REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
205
+ [ -n "$REPO_ROOT" ] || REPO_ROOT="$(pwd -P)"
206
+
207
+ [ -n "$OUT_DIR" ] || OUT_DIR="$REPO_ROOT/project/rapports/analysis"
208
+ mkdir -p "$OUT_DIR" 2>/dev/null || {
209
+ echo "Error: could not create output directory: $OUT_DIR" >&2
210
+ exit 4
211
+ }
212
+ [ -w "$OUT_DIR" ] || { echo "Error: output directory is not writable: $OUT_DIR" >&2; exit 4; }
213
+ OUT_DIR=$(cd -- "$OUT_DIR" && pwd -P)
214
+
215
+ if [ -n "$JSON_OUT" ]; then
216
+ JSON_OUT_DIR=$(dirname -- "$JSON_OUT")
217
+ [ -d "$JSON_OUT_DIR" ] || { echo "Error: --json-out directory does not exist: $JSON_OUT_DIR" >&2; exit 4; }
218
+ [ -w "$JSON_OUT_DIR" ] || { echo "Error: --json-out directory is not writable: $JSON_OUT_DIR" >&2; exit 4; }
219
+ JSON_OUT=$(cd -- "$JSON_OUT_DIR" && pwd -P)/$(basename -- "$JSON_OUT")
220
+ fi
221
+
222
+ # baseline_commit is the ENGINE REPO's HEAD, not the analysed root's — see header. null (not a
223
+ # usage error) when this repo has no commits yet; a notice records why.
224
+ BASELINE_COMMIT=$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || true)
225
+ SCANNED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
226
+ STAMP=$(date -u +%Y%m%dT%H%M%SZ)
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # Read the discoverer's report, build the record, write it
230
+ # ---------------------------------------------------------------------------
231
+ # Captured into a variable and run with `python3 -c`, exactly as run-engine.sh and
232
+ # apply-subsystem-cap.sh do: a `python3 - <<PY` heredoc would occupy python's stdin, and this
233
+ # script must itself be able to read stdin (discover-subsystems.sh . | write-scan-record.sh).
234
+
235
+ PY_SRC=$(cat <<'PY'
236
+ import json
237
+ import os
238
+ import re
239
+ import sys
240
+
241
+ (INPUT, OUT_DIR, JSON_OUT, LABEL, BASELINE_COMMIT, SCANNED_AT, STAMP) = sys.argv[1:8]
242
+
243
+ notices = []
244
+
245
+ # --- read the discoverer's report ---------------------------------------------------------------
246
+
247
+ try:
248
+ if INPUT == "-":
249
+ raw = sys.stdin.read()
250
+ origin = "stdin"
251
+ else:
252
+ with open(INPUT, "r", encoding="utf-8") as fh:
253
+ raw = fh.read()
254
+ origin = INPUT
255
+ except OSError as exc:
256
+ sys.stderr.write("Error: could not read input report: %s\n" % exc)
257
+ sys.exit(2)
258
+
259
+ if not raw.strip():
260
+ sys.stderr.write("Error: input report is empty (%s).\n" % origin)
261
+ sys.stderr.write(" Expected discover-subsystems.sh JSON output.\n")
262
+ sys.exit(2)
263
+
264
+ try:
265
+ report = json.loads(raw)
266
+ except ValueError as exc:
267
+ sys.stderr.write("Error: input report is not valid JSON (%s): %s\n" % (origin, exc))
268
+ sys.stderr.write(" Expected discover-subsystems.sh JSON output.\n")
269
+ sys.exit(2)
270
+
271
+ if not isinstance(report, dict) or not isinstance(report.get("candidates"), list):
272
+ sys.stderr.write("Error: input report has no \"candidates\" array (%s).\n" % origin)
273
+ sys.stderr.write(" This does not look like discover-subsystems.sh output.\n")
274
+ sys.exit(2)
275
+
276
+ produced_by = report.get("script")
277
+ if produced_by and produced_by != "discover-subsystems.sh":
278
+ notices.append(
279
+ "Input reports itself as \"%s\" rather than discover-subsystems.sh; proceeding on the "
280
+ "strength of its candidates array." % produced_by)
281
+
282
+ root = report.get("root") or report.get("root_absolute") or "(unknown root)"
283
+ root_absolute = report.get("root_absolute") or ""
284
+ # "." is what the discoverer reports when the analysed root IS the repo root -- a useless label
285
+ # for a filename slug. Fall back to the directory's real name, same convention
286
+ # apply-subsystem-cap.sh uses for its rapport label.
287
+ if LABEL:
288
+ label = LABEL
289
+ elif root not in (".", "", "./"):
290
+ label = root
291
+ else:
292
+ label = os.path.basename(root_absolute.rstrip("/")) or root
293
+
294
+ if not BASELINE_COMMIT:
295
+ notices.append(
296
+ "Could not resolve a HEAD commit for this repository (no commits yet, or not a git work "
297
+ "tree); \"baseline_commit\" was recorded as null."
298
+ )
299
+
300
+ # --- candidates[] — only real fields discover-subsystems.sh actually emits ----------------------
301
+
302
+ candidates = []
303
+ for c in report.get("candidates", []):
304
+ if not isinstance(c, dict):
305
+ continue
306
+ candidates.append({
307
+ "path": c.get("path"),
308
+ "files": c.get("files", 0),
309
+ "lines": c.get("lines", 0),
310
+ })
311
+
312
+ record = {
313
+ "script": "write-scan-record.sh",
314
+ "version": 1,
315
+ "baseline_commit": BASELINE_COMMIT or None,
316
+ "scanned_at": SCANNED_AT,
317
+ "root": root,
318
+ "candidates": candidates,
319
+ "candidate_count": len(candidates),
320
+ "notices": notices,
321
+ }
322
+
323
+ # --- filename: slugged label + timestamp, collision-safe --------------------------------------
324
+
325
+ slug = re.sub(r"[^a-z0-9]+", "-", label.lower()).strip("-")[:40]
326
+ if not slug:
327
+ slug = "target"
328
+
329
+ out_file = os.path.join(OUT_DIR, "uncharted-scan-record-%s-%s.scan-record.json" % (slug, STAMP))
330
+ n = 2
331
+ while os.path.exists(out_file):
332
+ out_file = os.path.join(
333
+ OUT_DIR, "uncharted-scan-record-%s-%s-%d.scan-record.json" % (slug, STAMP, n))
334
+ n += 1
335
+
336
+ body = json.dumps(record, indent=2, ensure_ascii=False) + "\n"
337
+
338
+ try:
339
+ with open(out_file, "w", encoding="utf-8") as fh:
340
+ fh.write(body)
341
+ except OSError as exc:
342
+ sys.stderr.write("Error: could not write scan-record: %s\n" % exc)
343
+ sys.exit(4)
344
+
345
+ if JSON_OUT:
346
+ try:
347
+ with open(JSON_OUT, "w", encoding="utf-8") as fh:
348
+ fh.write(body)
349
+ except OSError as exc:
350
+ sys.stderr.write("Error: could not write --json-out file: %s\n" % exc)
351
+ sys.exit(4)
352
+
353
+ for n_ in notices:
354
+ sys.stderr.write("Notice: %s\n" % n_)
355
+
356
+ print(out_file)
357
+ PY
358
+ )
359
+
360
+ python3 -c "$PY_SRC" \
361
+ "$INPUT" "$OUT_DIR" "$JSON_OUT" "$LABEL" "$BASELINE_COMMIT" "$SCANNED_AT" "$STAMP"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: j.wtf
3
- description: Polyfill alias of the wtf skill under a collision-safe directory name. Identical behavior to /wtf — Alias of /clearify — clarifies ambiguous, dense, or under-specified prompts and conversation on request. This folder exists only so the `/wtf` slash command resolves to a skill; behaviour is identical to `/clearify`. Use when the bare /wtf form is shadowed by another tool's own built-in command of the same name.
3
+ description: Alias of /clearify — clarifies ambiguous, dense, or under-specified prompts and conversation on request. This folder exists so `j.wtf` (and its `/j-wtf` directory form) resolves to a skill; behaviour is identical to `/clearify`.
4
4
  keywords:
5
5
  - wtf
6
6
  - confused
@@ -8,7 +8,6 @@ keywords:
8
8
  - what does this mean
9
9
  - I'm lost
10
10
  - j-wtf
11
- - polyfill
12
11
  examples:
13
12
  - "wtf"
14
13
  - "wtf does this mean"
@@ -18,9 +17,9 @@ examples:
18
17
 
19
18
  # WTF — Alias of /clearify
20
19
 
21
- This skill is a literal-directory-name duplicate of `skills/wtf/`. It exists so that `/j-wtf` (and `j.j-wtf`) give a guaranteed-unshadowed way to reach the same flow as `/wtf`, even if a host tool's own built-in command of the same name would otherwise shadow or override the bare `/wtf` alias (Claude Code's native skill resolution is a literal-string, directory-name-based match — see `docs/skill-authoring.md`'s "Invocation Convention").
20
+ `skills/j-wtf/` is the **canonical, hand-edited** directory for this skill, per CLAUDE.md's "The Canonical Naming Contract" (the `E50` reopening of 2026-09-09, which promoted `skills/j-wtf/` from generated twin to sole canonical form). The `j-` prefix is there for collision safety — a real directory under a distinct name, so a host tool shipping its own same-named built-in command cannot shadow it (Claude Code's native skill resolution is a literal-string, directory-name-based match; see `docs/skill-authoring.md`'s "Invocation Convention").
22
21
 
23
- This file is generated/synced by `scripts/generate-j-alias.sh wtf` from `skills/wtf/SKILL.md` — do not hand-edit it; re-run the generator instead to pick up source changes.
22
+ > ⚠️ **`scripts/generate-j-alias.sh` was retired by `E50_S14` and no longer exists — there is nothing to run.** This file was previously generated from a bare `skills/wtf/SKILL.md` source; `E50_S15` deleted that directory. This file is now the sole canonical, hand-edited source for this skill — edit it directly.
24
23
 
25
24
  ## Instructions
26
25
 
@@ -137,11 +137,33 @@ If all applicable rules pass (or the task is a legacy task), proceed to the next
137
137
 
138
138
  This phase determines **how `/jenga` was invoked** and, for two of the four entry modes, produces a **scoped set** — a confirmed list of board IDs (epics/stories/tasks) that Phases 1-4 must restrict themselves to. All board scanning, ID parsing, cascade expansion, and rendering used by this phase already live in `skills/jenga/scripts/` per this repo's "Scripts Over Inline Logic" principle — this phase never re-implements any of that logic inline. The executing agent's job here is limited to: invoking the right script with the right arguments, relaying its STDOUT verbatim to the user when the contract calls for that, capturing the `STATE_FILE:` line from STDERR for the next turn, and forwarding the user's raw reply back into the next invocation unmodified.
139
139
 
140
- **Determine the invocation form** from the raw argument (if any) passed to `/jenga`:
140
+ **First, check for the `--enrich` flag** (`E53_S13_T01`, ported from the now-retired `/route`'s board
141
+ + docs enrichment — see the Natural-language branch's step 3 and the Skill Matching & Invocation
142
+ Contract's Report format below for what it actually does). If the raw argument passed to `/jenga`
143
+ begins with the exact leading token `--enrich` followed by at least one space, strip that token
144
+ (and the single space after it) before anything else runs, and remember `enrichment_requested =
145
+ true` for the remainder of this invocation. The **remaining text** — never the original argument
146
+ with the flag still attached — is what every step below (including `detect-nl-intent.sh`) treats as
147
+ "the raw argument passed to `/jenga`"; this is what keeps the flag from ever being visible to
148
+ `detect-nl-intent.sh`'s `all_resolved`/`nl_intent`/`mixed` classification. If `--enrich` is not
149
+ present as a leading token, `enrichment_requested = false` and the argument is used as-is —
150
+ byte-for-byte the same behavior as before this flag existed.
151
+
152
+ `--enrich` alone (nothing after it, once whitespace is stripped) reduces to an empty remaining
153
+ argument — treat this exactly like no argument at all (**bare branch**); the flag has nothing to
154
+ enrich without free-form text and bare `/jenga` never does skill matching. `--enrich *` reduces to
155
+ the **wildcard branch** the same way (remaining text is the literal `*`); the wildcard branch also
156
+ never does skill matching, so `enrichment_requested` is simply never consulted there. Concretely,
157
+ `enrichment_requested` is only ever read in the **natural-language branch** below, and only once
158
+ that branch's own step 3 confirms a single-skill match — it is inert everywhere else. Passing
159
+ `--enrich` on a scoped (`<ids>`) argument is likewise a no-op: the scoped branch does not perform
160
+ skill matching either, so there is nothing for the flag to attach to.
161
+
162
+ **Then, determine the invocation form** from the remaining argument (if any) passed to `/jenga`:
141
163
 
142
164
  - No argument at all → **bare branch**.
143
165
  - The argument is the literal string `*` → **wildcard branch**.
144
- - Any other non-empty argument → invoke `skills/jenga/scripts/detect-nl-intent.sh "<raw argument>"` (E53_S01_T01) and branch on its `classification` field:
166
+ - Any other non-empty argument → invoke `skills/jenga/scripts/detect-nl-intent.sh "<remaining argument>"` (E53_S01_T01) and branch on its `classification` field:
145
167
  - `all_resolved` or `mixed` → **scoped branch** (below) — this is the same branch as before; only its internal mechanics changed (see below).
146
168
  - `nl_intent` → **natural-language branch** (below) — new for E53_S01, no new sigil or entry point, purely a new outcome of this same argument-shape detection.
147
169
 
@@ -169,11 +191,16 @@ This branch is entered when `detect-nl-intent.sh` (invoked above) classifies the
169
191
  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
192
 
171
193
  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/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
- 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:
194
+ 2. **Match** — run this section's own **Skill Matching & Invocation Contract** (below) — the three-pass keyword → example-similarity → description match, including its tie-break and no-match handling — against this catalog, treating `detect-nl-intent.sh`'s `raw_argument` field as the prompt.
195
+ 3. **Confident single match** — if `enrichment_requested` is `true` (the `--enrich` flag was passed, per Phase 0.75's preamble above), first invoke `skills/jenga/scripts/enrich-nl-prompt.sh "<raw_argument>"` (`E53_S13_T01`, ported from `/route`'s Steps 3-5) and assemble the enriched composite message from its JSON output, in this exact order:
196
+ - **Part A — Matched skill (full content)** — the matched skill's full `SKILL.md` body (everything after its YAML front-matter), wrapped as `<!-- SKILL: /<matched-skill-name> --> ... <!-- END SKILL -->`.
197
+ - **Part B — Board & documentation context** — a `## 📋 Relevant Board Context` list (one line per `board_items` entry: `- [<status>] **<id>** — <title> (\`<file>\`)`) followed by a `## 📄 Relevant Documentation` list (one line per `docs` entry: `` - `<path>` — <summary> ``). Omit either sub-list entirely (not an empty heading) when its array is empty.
198
+ - **Part C — Original prompt** — `## 🗣 Original Prompt` followed by the raw prompt, verbatim, in a blockquote.
199
+
200
+ Then report the routing decision using the **Skill Matching & Invocation Contract**'s **Report** format (including its `Board items found`/`Docs found` lines, populated from `enrich-nl-prompt.sh`'s `board_items_found`/`docs_found` fields, since `enrichment_requested` is `true` here), and invoke the matched skill exactly as the **Invoke** rule already does — but deliver the enriched composite message as the working input instead of the raw prompt when enrichment ran. When `enrichment_requested` is `false` (the default, unflagged path — unchanged from before this flag existed), skip `enrich-nl-prompt.sh` entirely: report using the **Report** format's default (no `Board items found`/`Docs found` lines) and invoke the matched skill directly with the raw prompt, exactly as before. Either way: load `agents/<prefered_agent>.md` when the matched skill specifies `metadata.prefered_agent`, otherwise execute the skill instructions directly. The matched skill's own execution takes over from here — do not continue into this `/jenga` invocation's Phase 1.
201
+ 4. **No match, or an ambiguous multi-way tie (single-skill match)** — before surfacing the **Skill Matching & Invocation Contract**'s generic disambiguation options, attempt a **playbook fallback** (E53_S02): invoke `skills/jenga/scripts/match-playbook.sh "<raw_argument>"`. This step only ever runs when step 3 above did NOT already commit to a confident single-skill match — a confident single-skill match always wins outright and this playbook fallback is never even invoked in that case. Branch on `match-playbook.sh`'s `classification` field:
175
202
  - `playbook_match` → continue to **step 5 (Playbook proposal and execution)** below.
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.
203
+ - `ambiguous` or `no_match` → continue to **step 6 (Fall through to the Skill Matching & Invocation Contract's disambiguation)** below — the exact behavior this branch already had before E53_S02, unchanged.
177
204
  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`.
178
205
  a. **Resolve conditional metadata** (`E53_S04_T02`/`T04`) — before rendering, inspect the
179
206
  matched playbook's own `steps` array (as returned by `load-playbooks.sh`'s catalog, not the
@@ -208,12 +235,81 @@ This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl
208
235
  - **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
236
  - **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
237
 
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.
238
+ Then invoke the step exactly as this section's **Skill Matching & Invocation Contract**'s **Invoke** rule already does for a single matched skill: load `agents/<prefered_agent>.md` when that step's own `SKILL.md` specifies `metadata.prefered_agent`, otherwise execute its instructions directly.
212
239
  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
240
  iv. On a `step_ready` result, repeat step 5e for the newly-named step.
214
241
  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
242
  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/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.
243
+ 6. **Fall through to the Skill Matching & Invocation Contract's disambiguation** — entered when step 4 found no playbook match (`ambiguous` or `no_match`). Surface the same disambiguation options this section's **Skill Matching & Invocation Contract** already defines for these cases (browse `/help`, create a new skill via `/btw`, or proceed with the raw prompt). Halt this `/jenga` invocation once the user picks an option; none of Phase 0.75's remaining steps or Phases 1-4 run for this branch.
244
+
245
+ ##### Skill Matching & Invocation Contract
246
+
247
+ `/jenga` is the **sole owner** of this contract (`E53_S13_T02`). `/route` — which previously carried
248
+ an independent, hand-synced copy of this same three-pass matching/tie-break/report logic in its own
249
+ Step 2/6/7 — has been retired outright (hard break, no shim; see `E53_S13`'s story). There is no
250
+ other copy of this contract anywhere in the codebase to keep in sync with; if you change the matching
251
+ logic here, this section is the only place that needs updating.
252
+
253
+ **Matching** (three passes, stop at first confident match):
254
+
255
+ - *Pass 1 — Keyword Match* — check whether any phrase from a skill's `keywords` list appears verbatim
256
+ (case-insensitive) in the prompt.
257
+ - *Pass 2 — Example Similarity* — compare the prompt against each skill's `examples` list as a
258
+ semantic similarity check; pick the skill whose examples most closely reflect the intent of the
259
+ prompt.
260
+ - *Pass 3 — Description Match* — if no clear winner has emerged, compare the prompt against each
261
+ skill's `description` field; pick the skill whose description best captures what the user is trying
262
+ to do.
263
+
264
+ **Tie-break** (two or more skills score equally) — present the top candidates and ask the user to
265
+ choose:
266
+
267
+ ```
268
+ More than one skill matches your prompt. Which should I apply?
269
+ 1. /<skill-a> — <one-line description>
270
+ 2. /<skill-b> — <one-line description>
271
+ 3. Neither — describe what you need
272
+ ```
273
+
274
+ **No match** — inform the user and offer:
275
+
276
+ ```
277
+ No matching skill found for: "<prompt>"
278
+ Would you like to:
279
+ 1. Browse all available skills (/help)
280
+ 2. Create a new skill for this use case (/btw)
281
+ 3. Proceed without a skill (raw prompt)
282
+ ```
283
+
284
+ **Invoke** — deliver to the appropriate agent: if the matched skill specifies
285
+ `metadata.prefered_agent`, load that agent's definition from `agents/<prefered_agent>.md` and pass it
286
+ the full context; otherwise execute the skill's instructions directly. Do not summarise or restate the
287
+ matched skill's instructions — deliver them as-is.
288
+
289
+ **Report** — before invoking, briefly inform the user:
290
+
291
+ ```
292
+ Routing to: /<matched-skill-name>
293
+ Reason: <one sentence explaining why this skill was chosen>
294
+ ```
295
+
296
+ **`Board items found`/`Docs found` (opt-in, `E53_S13_T01`)** — these two lines are appended to the
297
+ Report above, in this order, **only** when the natural-language branch's `enrichment_requested` is
298
+ `true` (i.e. the caller passed `--enrich`, per Phase 0.75's preamble):
299
+
300
+ ```
301
+ Board items found: <count>
302
+ Docs found: <count>
303
+ ```
304
+
305
+ `<count>` is `enrich-nl-prompt.sh`'s `board_items_found`/`docs_found` field respectively (the total
306
+ match count before the top-5/top-3 cap, not the number of items actually listed in the enriched
307
+ prompt's Part B). When `enrichment_requested` is `false` — the default path, unchanged from before
308
+ this flag existed — neither line is emitted; the Report is exactly the two lines above and nothing
309
+ more.
310
+
311
+ Then proceed immediately — do not wait for user confirmation unless the match was ambiguous (the
312
+ tie-break above already handled that).
217
313
 
218
314
  #### Shared confirmation step (bare and scoped branches only)
219
315
 
@@ -345,8 +441,8 @@ When no eligible candidates remain in Phase 4, exit and output:
345
441
  - **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.
346
442
  - **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.
347
443
  - **`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.
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.)
444
+ - **`detect-nl-intent.sh` classifies the argument as `nl_intent`, no confident single-skill match, and `match-playbook.sh` (E53_S02) also finds no playbook match** — the natural-language branch's step 4 attempts the playbook fallback first (see the Natural-language branch's step 4/6), and only THEN surfaces the **Skill Matching & Invocation Contract**'s no-match disambiguation options (browse `/help`, create a new skill via `/btw`, proceed with the raw prompt) instead of guessing; no phase past 0.75 runs until the user picks one.
445
+ - **`detect-nl-intent.sh` classifies the argument as `nl_intent`, no confident single-skill match, and `match-playbook.sh` returns an ambiguous multi-way tie between playbooks** — treated the same as the no-playbook-match case above: falls through to the **Skill Matching & Invocation Contract**'s tie-break prompt (top candidates + a "neither, describe what you need" option) instead of guessing; no phase past 0.75 runs until the user picks one. (`match-playbook.sh`'s own `ambiguous` result — a tie between playbooks — is intentionally not given its own separate disambiguation UI; it is treated identically to `no_match` and routed to the same fallback prose, which already has its own tie-break handling.)
350
446
  - **`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`).
351
447
  - **`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.
352
448
  - **`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.
@@ -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/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.",
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/jenga/SKILL.md's inlined Skill Matching & Invocation Contract. 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,17 +18,17 @@
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/j-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/jenga/SKILL.md's inlined Skill Matching & Invocation Contract 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/j-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/jenga/SKILL.md's inlined Skill Matching & Invocation Contract 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/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.",
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/jenga/SKILL.md's inlined Skill Matching & Invocation Contract 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
  },