@jenga-ai/agent 3.6.0 → 4.0.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 (79) hide show
  1. package/lib/generate-skill-allow-list.js +42 -5
  2. package/lib/skill-allow-list.json +1 -1
  3. package/package.json +1 -2
  4. package/project/app/api/lib/resolve-project-root.js +1 -1
  5. package/project/app/package.json +4 -0
  6. package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
  7. package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
  8. package/project/app/ui/dist/index.html +2 -2
  9. package/scripts/acquire-concurrency-slot.sh +34 -4
  10. package/scripts/apply-j-prefix.sh +1 -1
  11. package/scripts/delete-bare-skill-dirs.sh +1 -2
  12. package/scripts/idea_manager.sh +16 -1
  13. package/scripts/release-concurrency-slot.sh +34 -4
  14. package/scripts/render-ranked-list.sh +270 -0
  15. package/scripts/repoint-dead-bare-path-prose.py +81 -0
  16. package/scripts/rewrite-stale-skill-preambles.py +188 -0
  17. package/scripts/strip-polyfill-frontmatter.py +166 -0
  18. package/scripts/todo_manager.sh +16 -1
  19. package/scripts/validate-typed-object.sh +750 -0
  20. package/skills/j-brainstorm/SKILL.md +3 -4
  21. package/skills/j-btw/SKILL.md +3 -4
  22. package/skills/j-clearify/SKILL.md +3 -4
  23. package/skills/j-close-story/SKILL.md +11 -12
  24. package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
  25. package/skills/j-commit/SKILL.md +3 -4
  26. package/skills/j-continue/SKILL.md +5 -6
  27. package/skills/j-deep-dive/SKILL.md +3 -4
  28. package/skills/j-distribute/SKILL.md +3 -4
  29. package/skills/j-do/SKILL.md +13 -15
  30. package/skills/j-doc/SKILL.md +3 -4
  31. package/skills/j-doc-sync/SKILL.md +3 -4
  32. package/skills/j-dooo/SKILL.md +6 -15
  33. package/skills/j-error/SKILL.md +3 -4
  34. package/skills/j-evaluate/SKILL.md +3 -4
  35. package/skills/j-examplify/SKILL.md +3 -4
  36. package/skills/j-help/SKILL.md +3 -4
  37. package/skills/j-idea/SKILL.md +3 -4
  38. package/skills/j-improve/SKILL.md +3 -4
  39. package/skills/j-init/SKILL.md +20 -11
  40. package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
  41. package/skills/j-jbp/SKILL.md +3 -4
  42. package/skills/j-lgtm/SKILL.md +3 -4
  43. package/skills/j-pi-plan/SKILL.md +3 -4
  44. package/skills/j-proceed/SKILL.md +3 -4
  45. package/skills/j-publish/SKILL.md +3 -4
  46. package/skills/j-publish/scripts/run_gates.sh +1 -1
  47. package/skills/j-reconcile/SKILL.md +38 -5
  48. package/skills/j-reconcile/assets/report_format.md +11 -0
  49. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
  50. package/skills/j-reconcile-origin/SKILL.md +3 -4
  51. package/skills/j-redo/SKILL.md +3 -4
  52. package/skills/j-skillify/SKILL.md +3 -4
  53. package/skills/j-spinoff/SKILL.md +3 -4
  54. package/skills/j-status/SKILL.md +4 -5
  55. package/skills/j-todo/SKILL.md +42 -5
  56. package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
  57. package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
  58. package/skills/j-uncharted/SKILL.md +251 -12
  59. package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
  60. package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
  61. package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
  62. package/skills/j-uncharted/scripts/run-engine.sh +36 -2
  63. package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
  64. package/skills/j-wtf/SKILL.md +3 -4
  65. package/skills/jenga/SKILL.md +69 -8
  66. package/skills/jenga/playbooks/schema.json +4 -4
  67. package/skills/jenga/scripts/load-nl-catalog.js +5 -2
  68. package/skills/jenga/scripts/load-playbooks.sh +289 -4
  69. package/skills/jenga/scripts/match-playbook.sh +4 -4
  70. package/skills/jenga/scripts/run-playbook-step.sh +267 -1
  71. package/templates/permission-levels/level-1-locked.json +1 -1
  72. package/templates/permission-levels/level-2-guarded.json +1 -1
  73. package/templates/permission-levels/level-3-standard.json +1 -1
  74. package/templates/permission-levels/level-4-elevated.json +1 -1
  75. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  76. package/templates/playbook-types.json +6 -0
  77. package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
  78. package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
  79. 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 only so the `/wtf` slash command 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
 
@@ -169,11 +169,11 @@ 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/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:
172
+ 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.
173
+ 3. **Confident single match** — report the routing decision using the **Skill Matching & Invocation Contract**'s **Report** format, then invoke the matched skill exactly as its **Invoke** rule 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 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
175
  - `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.
176
+ - `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
177
  5. **Playbook proposal and execution** — entered only on a `playbook_match` result from step 4. A proposed playbook is an ordered chain of skills (e.g. the canonical `brainstorm -> j.todo -> j.do -> j.dev-done -> j.mirror-public` chain defined in `skills/jenga/playbooks/brainstorm-to-mirror.json`) that must be confirmed, editable, and confirmable per `CLAUDE.md`'s Interaction Pattern before any step executes — the same confirm-before-execute posture `/jenga` already applies to the bare/scoped branches via `render-confirmation.sh`.
178
178
  a. **Resolve conditional metadata** (`E53_S04_T02`/`T04`) — before rendering, inspect the
179
179
  matched playbook's own `steps` array (as returned by `load-playbooks.sh`'s catalog, not the
@@ -208,12 +208,73 @@ 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/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.
211
+ 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
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/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.
216
+ 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.
217
+
218
+ ##### Skill Matching & Invocation Contract
219
+
220
+ This contract is inlined here — rather than referenced by path to `skills/j-route/SKILL.md` — because
221
+ `j.route` is one of six skills that never ship publicly in either naming form (see
222
+ `docs/public-mirror-content-parity.md`'s "Which skills are never public"), while `/jenga` itself ships
223
+ publicly. A path reference from a shipped file to an unshipped one is a dead reference in the public
224
+ package even though it resolves fine in this private repo (filed via `j.error` 2026-09-26).
225
+ `skills/j-route/SKILL.md`'s own Step 2/6/7 carry the authoritative copy of this same contract for
226
+ `/route`'s own use — the two are intentionally duplicated for public-mirror reasons; keep them in sync
227
+ by hand if either changes.
228
+
229
+ **Matching** (three passes, stop at first confident match):
230
+
231
+ - *Pass 1 — Keyword Match* — check whether any phrase from a skill's `keywords` list appears verbatim
232
+ (case-insensitive) in the prompt.
233
+ - *Pass 2 — Example Similarity* — compare the prompt against each skill's `examples` list as a
234
+ semantic similarity check; pick the skill whose examples most closely reflect the intent of the
235
+ prompt.
236
+ - *Pass 3 — Description Match* — if no clear winner has emerged, compare the prompt against each
237
+ skill's `description` field; pick the skill whose description best captures what the user is trying
238
+ to do.
239
+
240
+ **Tie-break** (two or more skills score equally) — present the top candidates and ask the user to
241
+ choose:
242
+
243
+ ```
244
+ More than one skill matches your prompt. Which should I apply?
245
+ 1. /<skill-a> — <one-line description>
246
+ 2. /<skill-b> — <one-line description>
247
+ 3. Neither — describe what you need
248
+ ```
249
+
250
+ **No match** — inform the user and offer:
251
+
252
+ ```
253
+ No matching skill found for: "<prompt>"
254
+ Would you like to:
255
+ 1. Browse all available skills (/help)
256
+ 2. Create a new skill for this use case (/btw)
257
+ 3. Proceed without a skill (raw prompt)
258
+ ```
259
+
260
+ **Invoke** — deliver to the appropriate agent: if the matched skill specifies
261
+ `metadata.prefered_agent`, load that agent's definition from `agents/<prefered_agent>.md` and pass it
262
+ the full context; otherwise execute the skill's instructions directly. Do not summarise or restate the
263
+ matched skill's instructions — deliver them as-is.
264
+
265
+ **Report** — before invoking, briefly inform the user:
266
+
267
+ ```
268
+ Routing to: /<matched-skill-name>
269
+ Reason: <one sentence explaining why this skill was chosen>
270
+ ```
271
+
272
+ (`skills/j-route/SKILL.md`'s own Step 7 additionally reports `Board items found`/`Docs found` counts
273
+ from its Steps 3-5 board/doc enrichment — this `/jenga` natural-language branch never performs that
274
+ enrichment, so those two fields do not apply here and are intentionally omitted.)
275
+
276
+ Then proceed immediately — do not wait for user confirmation unless the match was ambiguous (the
277
+ tie-break above already handled that).
217
278
 
218
279
  #### Shared confirmation step (bare and scoped branches only)
219
280
 
@@ -345,8 +406,8 @@ When no eligible candidates remain in Phase 4, exit and output:
345
406
  - **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
407
  - **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
408
  - **`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.)
409
+ - **`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.
410
+ - **`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
411
  - **`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
412
  - **`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
413
  - **`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
  },
@@ -68,8 +68,11 @@ import { join } from "path";
68
68
  import { pathToFileURL } from "url";
69
69
 
70
70
  // The three permanent exceptions to the `j-<name>` canonical directory convention — see
71
- // docs/skill-authoring.md's Canonical Naming Contract and scripts/audit-twin-divergence.sh's
72
- // NEVER_TWINNED list, which this mirrors.
71
+ // docs/skill-authoring.md's Canonical Naming Contract, and scripts/repoint-skill-refs.sh's
72
+ // REPOINT_SKILL_REFS_EXCEPTIONS list, which this mirrors. (It previously mirrored the
73
+ // twin-divergence audit script's NEVER_TWINNED tuple; that script was deleted when
74
+ // E42_S07 retired the twin-parity gate, so both this comment and the cross-check in
75
+ // tests/load-nl-catalog-twin-resolution.bats were repointed at the surviving list.)
73
76
  const NEVER_TWINNED = new Set(["jenga", "jenga-permission-level", "index"]);
74
77
 
75
78
  function canonicalSkillDir(name) {