@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.
- 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/package.json +4 -0
- package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
- package/project/app/ui/dist/assets/index-C3oiuli_.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/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-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/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 +69 -8
- package/skills/jenga/playbooks/schema.json +4 -4
- package/skills/jenga/scripts/load-nl-catalog.js +5 -2
- package/skills/jenga/scripts/load-playbooks.sh +289 -4
- package/skills/jenga/scripts/match-playbook.sh +4 -4
- package/skills/jenga/scripts/run-playbook-step.sh +267 -1
- 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 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
|
-
|
|
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
|
@@ -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
|
|
173
|
-
3. **Confident single match** — report the routing decision using
|
|
174
|
-
4. **No match, or an ambiguous multi-way tie (single-skill match)** — before surfacing
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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
|
},
|
|
@@ -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/
|
|
72
|
-
//
|
|
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) {
|