@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.
- package/README.md +32 -1
- package/lib/generate-skill-allow-list.js +42 -5
- package/lib/skill-allow-list.json +1 -1
- package/package.json +1 -2
- package/project/app/api/lib/resolve-project-root.js +1 -1
- package/project/app/api/scripts/capture-snapshot.js +12 -7
- package/project/app/package.json +4 -0
- package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
- package/project/app/ui/dist/assets/index-DX2pfTAW.js +104 -0
- package/project/app/ui/dist/index.html +2 -2
- package/scripts/acquire-concurrency-slot.sh +34 -4
- package/scripts/apply-j-prefix.sh +1 -1
- package/scripts/delete-bare-skill-dirs.sh +1 -2
- package/scripts/idea_manager.sh +16 -1
- package/scripts/release-concurrency-slot.sh +34 -4
- package/scripts/render-ranked-list.sh +270 -0
- package/scripts/repoint-dead-bare-path-prose.py +81 -0
- package/scripts/rewrite-stale-skill-preambles.py +188 -0
- package/scripts/strip-polyfill-frontmatter.py +166 -0
- package/scripts/todo_manager.sh +16 -1
- package/scripts/validate-typed-object.sh +750 -0
- package/scripts/verify-postinstall-reconcile.sh +33 -7
- package/skills/index/scripts/board_index.py +29 -1
- package/skills/j-brainstorm/SKILL.md +3 -4
- package/skills/j-btw/SKILL.md +3 -4
- package/skills/j-clearify/SKILL.md +3 -4
- package/skills/j-close-story/SKILL.md +11 -12
- package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
- package/skills/j-commit/SKILL.md +3 -4
- package/skills/j-continue/SKILL.md +5 -6
- package/skills/j-deep-dive/SKILL.md +3 -4
- package/skills/j-distribute/SKILL.md +3 -4
- package/skills/j-do/SKILL.md +13 -15
- package/skills/j-doc/SKILL.md +3 -4
- package/skills/j-doc/scripts/resolve_last_update.py +27 -1
- package/skills/j-doc-sync/SKILL.md +3 -4
- package/skills/j-dooo/SKILL.md +6 -15
- package/skills/j-error/SKILL.md +3 -4
- package/skills/j-evaluate/SKILL.md +3 -4
- package/skills/j-examplify/SKILL.md +3 -4
- package/skills/j-help/SKILL.md +3 -4
- package/skills/j-idea/SKILL.md +3 -4
- package/skills/j-improve/SKILL.md +3 -4
- package/skills/j-init/SKILL.md +20 -11
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
- package/skills/j-jbp/SKILL.md +3 -4
- package/skills/j-lgtm/SKILL.md +3 -4
- package/skills/j-pi-plan/SKILL.md +3 -4
- package/skills/j-proceed/SKILL.md +3 -4
- package/skills/j-publish/SKILL.md +3 -4
- package/skills/j-publish/scripts/run_gates.sh +1 -1
- package/skills/j-reconcile/SKILL.md +38 -5
- package/skills/j-reconcile/assets/report_format.md +11 -0
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
- package/skills/j-reconcile-origin/SKILL.md +3 -4
- package/skills/j-redo/SKILL.md +3 -4
- package/skills/j-skillify/SKILL.md +3 -4
- package/skills/j-spinoff/SKILL.md +3 -4
- package/skills/j-status/SKILL.md +4 -5
- package/skills/j-todo/SKILL.md +42 -5
- package/skills/j-todo/scripts/add_trivial_task.sh +12 -1
- package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
- package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
- package/skills/j-uncharted/SKILL.md +251 -12
- package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
- package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
- package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
- package/skills/j-uncharted/scripts/run-engine.sh +36 -2
- package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
- package/skills/j-wtf/SKILL.md +3 -4
- package/skills/jenga/SKILL.md +106 -10
- package/skills/jenga/playbooks/schema.json +4 -4
- package/skills/jenga/scripts/enrich-nl-prompt.sh +225 -0
- package/skills/jenga/scripts/load-nl-catalog.js +6 -3
- package/skills/jenga/scripts/load-playbooks.sh +289 -4
- package/skills/jenga/scripts/match-playbook.sh +6 -5
- package/skills/jenga/scripts/run-playbook-step.sh +270 -3
- package/templates/permission-levels/level-1-locked.json +1 -1
- package/templates/permission-levels/level-2-guarded.json +1 -1
- package/templates/permission-levels/level-3-standard.json +1 -1
- package/templates/permission-levels/level-4-elevated.json +1 -1
- package/templates/permission-levels/level-5-unrestricted.json +1 -1
- package/templates/playbook-types.json +6 -0
- package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
- package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
- 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"
|
package/skills/j-wtf/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: j.wtf
|
|
3
|
-
description:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/skills/jenga/SKILL.md
CHANGED
|
@@ -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
|
-
**
|
|
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 "<
|
|
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
|
|
173
|
-
3. **Confident single match** —
|
|
174
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
},
|