@jenga-ai/agent 3.5.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/README.md +85 -78
- package/agents/developer.md +1 -1
- package/agents/scrum-master.md +20 -2
- package/agents/tester.md +3 -3
- package/hooks/on_session_end.sh +5 -5
- package/lib/generate-agent-context.js +2 -2
- package/lib/generate-copilot-hooks.js +1 -1
- package/lib/generate-skill-allow-list.js +79 -8
- package/lib/mirror.js +1 -1
- package/lib/postinstall-manifest.js +1 -1
- package/lib/skill-allow-list.json +2 -2
- package/mcp/help/index.js +8 -17
- package/mcp/help/scan.js +73 -0
- package/package.json +5 -1
- package/project/app/api/lib/resolve-project-root.js +1 -1
- package/project/app/api/parsers/knowledge-graph.js +100 -9
- package/project/app/api/routes/health.js +36 -0
- package/project/app/api/scripts/capture-snapshot.js +9 -6
- 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/project/app/ui/package.json +4 -0
- package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
- package/scripts/acquire-concurrency-slot.sh +35 -5
- package/scripts/apply-j-prefix.sh +46 -5
- package/scripts/build-pages-site.sh +1 -1
- package/scripts/check-public-playbook-steps.sh +158 -52
- package/scripts/check-publicignore-match.sh +2 -2
- package/scripts/compute-deploy-reconcile.sh +5 -5
- package/scripts/delete-bare-skill-dirs.sh +329 -0
- package/scripts/generate-legacy-shipped-paths.js +2 -2
- package/scripts/idea_manager.sh +273 -3
- package/scripts/mark-deployed.sh +2 -2
- package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
- package/scripts/populate-knowledge-graph.js +213 -5
- package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
- package/scripts/postinstall.js +1 -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/repoint-skill-refs.sh +539 -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-legacy-seed-reconcile.sh +10 -10
- package/scripts/verify-postinstall-reconcile.sh +7 -7
- package/scripts/write-context-digest.sh +1 -1
- package/skills/j-brainstorm/SKILL.md +3 -4
- package/skills/j-btw/SKILL.md +3 -4
- package/skills/j-clearify/SKILL.md +5 -6
- package/skills/j-close-story/SKILL.md +11 -12
- package/skills/j-close-story/scripts/check-privatized.sh +4 -4
- 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/CONFIG_SCHEMA.md +82 -5
- package/skills/j-distribute/SKILL.md +3 -4
- package/skills/j-do/SKILL.md +100 -18
- package/skills/j-doc/SKILL.md +3 -4
- package/skills/j-doc-sync/SKILL.md +4 -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-gitignore/SKILL.md +157 -0
- package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
- package/skills/j-gitignore/scripts/_catalog.sh +105 -0
- package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
- package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
- package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
- package/skills/j-help/SKILL.md +3 -4
- package/skills/j-idea/SKILL.md +80 -9
- package/skills/j-idea/assets/idea_template.md +1 -1
- package/skills/j-improve/SKILL.md +4 -5
- package/skills/j-init/SKILL.md +23 -14
- package/skills/j-init/assets/scope-thresholds_template.json +5 -2
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +9 -7
- package/skills/j-init/scripts/init.sh +4 -4
- 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-playbook/SKILL.md +1 -1
- package/skills/j-proceed/SKILL.md +3 -4
- package/skills/j-publish/SKILL.md +4 -5
- package/skills/j-publish/adapters/npm-ci.md +6 -1
- package/skills/j-publish/adapters/npm.md +1 -1
- package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
- package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
- package/skills/j-publish/scripts/run_gates.sh +1 -1
- package/skills/j-reconcile/SKILL.md +40 -7
- package/skills/j-reconcile/assets/report_format.md +11 -0
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +13 -13
- package/skills/j-reconcile-origin/SKILL.md +3 -4
- package/skills/j-redo/SKILL.md +4 -5
- package/skills/j-skillify/SKILL.md +3 -4
- package/skills/j-spinoff/SKILL.md +4 -5
- package/skills/j-status/SKILL.md +18 -4
- package/skills/j-todo/SKILL.md +44 -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 +252 -13
- package/skills/j-uncharted/scripts/detect-dependencies.sh +80 -22
- package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
- package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
- package/skills/j-uncharted/scripts/elicitation-state.sh +1 -1
- 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/validate-proposed-items.sh +1 -1
- package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
- package/skills/j-wtf/SKILL.md +4 -5
- package/skills/jenga/SKILL.md +106 -11
- package/skills/jenga/playbooks/board-hygiene.json +32 -0
- package/skills/jenga/playbooks/schema.json +73 -6
- package/skills/jenga/playbooks/understand-then-commit.json +19 -0
- package/skills/jenga/scripts/load-nl-catalog.js +5 -2
- package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
- package/skills/jenga/scripts/load-playbooks.sh +290 -5
- package/skills/jenga/scripts/match-playbook.sh +4 -4
- package/skills/jenga/scripts/run-playbook-step.sh +267 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
- package/templates/SKILL_TEMPLATE.md +12 -0
- 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 +2 -2
- package/templates/permission-levels/level-5-unrestricted.json +2 -2
- package/templates/playbook-types.json +40 -6
- package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
- package/project/app/ui/dist/assets/index-CdK3Qrep.css +0 -1
- package/scripts/audit-twin-divergence.sh +0 -625
- package/scripts/generate-j-alias.sh +0 -333
- package/skills/j-dev-done/SKILL.md +0 -53
- package/skills/j-dev-done/scripts/classify-commit-outcome.sh +0 -114
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/repoint-skill-refs.sh — repoint bare skills/<name>/ path references to their
|
|
3
|
+
# canonical skills/j-<name>/ twin (E50_S11).
|
|
4
|
+
#
|
|
5
|
+
# Purpose
|
|
6
|
+
# -------
|
|
7
|
+
# `skills/j-<name>/` was promoted to the sole canonical skill directory by the E50
|
|
8
|
+
# reopening of 2026-09-09; the old bare `skills/<name>/` directories are deleted later
|
|
9
|
+
# in the sequence (E50_S15). This script performs the ordering-safety half of that
|
|
10
|
+
# cutover: rewriting every functional (non-prose) reference of the form
|
|
11
|
+
# `skills/<bare-name>/...` to `skills/j-<bare-name>/...` WHILE both directories still
|
|
12
|
+
# exist. The twin is already byte-identical to its bare source, so applying this script
|
|
13
|
+
# is a behaviour-neutral no-op at runtime — it only changes which (already-working)
|
|
14
|
+
# path a reference resolves through, in preparation for the bare directory's later
|
|
15
|
+
# deletion.
|
|
16
|
+
#
|
|
17
|
+
# Per CLAUDE.md's "Skill Implementation Principle — Scripts Over Inline Logic", this is
|
|
18
|
+
# a deterministic, re-runnable, idempotent script — not inline agent judgment applied
|
|
19
|
+
# file-by-file.
|
|
20
|
+
#
|
|
21
|
+
# Modes
|
|
22
|
+
# -----
|
|
23
|
+
# --dry-run Report every candidate file, line number, and the before/after string.
|
|
24
|
+
# Nothing is written.
|
|
25
|
+
# --apply Perform the rewrite in place.
|
|
26
|
+
#
|
|
27
|
+
# Exactly one of --dry-run/--apply is required. Either mode always runs the
|
|
28
|
+
# bare-vs-twin delta check FIRST (see below) before touching or reporting on any file.
|
|
29
|
+
#
|
|
30
|
+
# Usage
|
|
31
|
+
# -----
|
|
32
|
+
# scripts/repoint-skill-refs.sh (--dry-run|--apply) [PATH...]
|
|
33
|
+
#
|
|
34
|
+
# PATH may be a file or a directory (searched recursively); it is resolved relative to
|
|
35
|
+
# the caller's cwd if relative, or used as-is if absolute. If no PATH is given, the
|
|
36
|
+
# script defaults to this repo's whole canonical source scope: skills/, agents/,
|
|
37
|
+
# hooks/, scripts/, lib/, and .github/agents/ — the exact set of "canonical, hand-edited
|
|
38
|
+
# source" locations named by CLAUDE.md's Source-of-Truth rule and this story's Context.
|
|
39
|
+
#
|
|
40
|
+
# Examples
|
|
41
|
+
# --------
|
|
42
|
+
# scripts/repoint-skill-refs.sh --dry-run skills/j-do
|
|
43
|
+
# scripts/repoint-skill-refs.sh --apply agents/developer.md .github/agents/developer.md
|
|
44
|
+
# scripts/repoint-skill-refs.sh --apply # full default canonical scope
|
|
45
|
+
#
|
|
46
|
+
# Exception list — single named source
|
|
47
|
+
# --------------------------------------
|
|
48
|
+
# REPOINT_SKILL_REFS_EXCEPTIONS below is the ONLY place this script's exception list is
|
|
49
|
+
# defined; every other check in this script calls is_exception() rather than
|
|
50
|
+
# re-listing the names. These three directories keep their bare names permanently
|
|
51
|
+
# (E50_S06/E50's "three permanent exceptions") and are never rewritten to a `j-` form,
|
|
52
|
+
# and never required to have a `j-` twin:
|
|
53
|
+
# - skills/jenga/ (root orchestrator command)
|
|
54
|
+
# - skills/jenga-permission-level/ (root orchestrator command)
|
|
55
|
+
# - skills/index/ (not a skill — no SKILL.md, not part of routing)
|
|
56
|
+
#
|
|
57
|
+
# Double-prefix safety
|
|
58
|
+
# ---------------------
|
|
59
|
+
# The rewrite rule's source pattern is always `skills/<bare-name>/`, where <bare-name>
|
|
60
|
+
# is drawn from the LIVE list of non-"j-"-prefixed directory names under skills/ (see
|
|
61
|
+
# the delta check below), minus the exceptions above. Because every eligible name is
|
|
62
|
+
# itself bare (never starts with "j-") and the pattern requires the literal trailing
|
|
63
|
+
# "/" delimiter, the string `skills/<bare-name>/` can never occur as a substring of
|
|
64
|
+
# `skills/j-<bare-name>/` or of any other twin path — right after "skills/" a twin path
|
|
65
|
+
# always reads "j-", never the bare name directly. Double-prefixing
|
|
66
|
+
# (`skills/j-j-<name>/`) is therefore structurally impossible by construction, not
|
|
67
|
+
# merely avoided by a runtime guard.
|
|
68
|
+
#
|
|
69
|
+
# Bare-vs-twin delta check
|
|
70
|
+
# --------------------------
|
|
71
|
+
# Computed fresh from disk on every invocation (never a hardcoded list): every
|
|
72
|
+
# directory directly under skills/ is classified as either a twin (name starts with
|
|
73
|
+
# "j-") or bare (does not). Every bare, non-excepted directory MUST have a
|
|
74
|
+
# `skills/j-<name>/` twin on disk; if not, that directory is UNPAIRED. The script
|
|
75
|
+
# prints the bare count, the twin count, and every unpaired directory's name, then —
|
|
76
|
+
# if any unpaired directory exists — exits non-zero (naming the directory) BEFORE
|
|
77
|
+
# performing or reporting any rewrite. This does not require twin_count == bare_count:
|
|
78
|
+
# a twin-only skill authored after the E50 reopening (e.g. skills/j-playbook/ before it
|
|
79
|
+
# gained a jenga/scripts/load-playbooks.sh reference, or skills/j-cloud-connect/, which
|
|
80
|
+
# never had a bare form at all) is expected and not flagged — only a BARE directory
|
|
81
|
+
# lacking its twin is an error condition.
|
|
82
|
+
#
|
|
83
|
+
# Path excludes — single named source
|
|
84
|
+
# --------------------------------------
|
|
85
|
+
# PATH_EXCLUDE_PATTERNS below is the only place file/directory exclusions are defined.
|
|
86
|
+
# Always excluded, regardless of what PATHs are passed on the command line:
|
|
87
|
+
# - .git/, node_modules/ (VCS / dependency internals)
|
|
88
|
+
# - .claude/, .agents/ (generated mirrors — CLAUDE.md: never edit directly)
|
|
89
|
+
# - docs/ (prose/docs rewrite is E50_S17's scope, not this story's)
|
|
90
|
+
# - .publicignore (owned by E50_S16)
|
|
91
|
+
# - lib/legacy-shipped-paths.json (deliberately retained bare-path list — see E50_S16)
|
|
92
|
+
#
|
|
93
|
+
# Idempotency
|
|
94
|
+
# -----------
|
|
95
|
+
# Because the rewrite eliminates every occurrence of `skills/<bare-name>/` (bare) that
|
|
96
|
+
# it can see, and never introduces a new one (only ever `skills/j-<bare-name>/`), a
|
|
97
|
+
# second --apply run over the same tree finds nothing left to change and reports zero
|
|
98
|
+
# files rewritten.
|
|
99
|
+
|
|
100
|
+
set -euo pipefail
|
|
101
|
+
|
|
102
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
103
|
+
# shellcheck source=lib/resolve-project-dir.sh
|
|
104
|
+
source "$SCRIPT_DIR/../lib/resolve-project-dir.sh"
|
|
105
|
+
|
|
106
|
+
PROJECT_DIR="$JENGA_PROJECT_DIR"
|
|
107
|
+
SKILLS_DIR="$PROJECT_DIR/skills"
|
|
108
|
+
|
|
109
|
+
# ---- Single named source: directory exception list ----
|
|
110
|
+
REPOINT_SKILL_REFS_EXCEPTIONS=(jenga jenga-permission-level index)
|
|
111
|
+
|
|
112
|
+
is_exception() {
|
|
113
|
+
local name="$1" ex
|
|
114
|
+
for ex in "${REPOINT_SKILL_REFS_EXCEPTIONS[@]}"; do
|
|
115
|
+
if [ "$name" = "$ex" ]; then
|
|
116
|
+
return 0
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
return 1
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# ---- Single named source: path excludes ----
|
|
123
|
+
# Each entry is a pattern matched against the path relative to $PROJECT_DIR (POSIX
|
|
124
|
+
# separators). A path is excluded if it equals, or is nested under, any entry.
|
|
125
|
+
PATH_EXCLUDE_PATTERNS=(
|
|
126
|
+
".git"
|
|
127
|
+
"node_modules"
|
|
128
|
+
".claude"
|
|
129
|
+
".agents"
|
|
130
|
+
"docs"
|
|
131
|
+
".publicignore"
|
|
132
|
+
"lib/legacy-shipped-paths.json"
|
|
133
|
+
# This script's own source. Its header comments quote real, deliberately-unrewritten
|
|
134
|
+
# bare-path content from other files as illustrative examples (e.g. "skills/wtf/"
|
|
135
|
+
# inside a sentence explaining why skills/j-wtf/SKILL.md's own self-reference is
|
|
136
|
+
# left alone) and one historical bug description (the original, broken
|
|
137
|
+
# `mkdir -p ".../skills/do"` line this script's own fix commit corrected). Running
|
|
138
|
+
# the sweep against itself would rewrite those intentional quotes, making them
|
|
139
|
+
# inaccurate descriptions of the content/history they document. Not a "the tool
|
|
140
|
+
# can't sweep its own kind" restriction — every other *.sh file is swept normally.
|
|
141
|
+
"scripts/repoint-skill-refs.sh"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
is_path_excluded() {
|
|
145
|
+
local rel="$1" pattern
|
|
146
|
+
for pattern in "${PATH_EXCLUDE_PATTERNS[@]}"; do
|
|
147
|
+
if [ "$rel" = "$pattern" ] || [[ "$rel" == "$pattern"/* ]]; then
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
done
|
|
151
|
+
return 1
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# ---- Self-reference guard ----
|
|
155
|
+
# A file living under skills/j-<X>/... never has its OWN "skills/<X>/" (same name)
|
|
156
|
+
# occurrences rewritten, even though every other eligible name is still rewritten
|
|
157
|
+
# normally in that same file. This mirrors this story's own scope boundary
|
|
158
|
+
# (E50_S11_T02's title: "repoint CROSS-skill bare references inside skills/j-*/
|
|
159
|
+
# twins") — self-referential mentions of a twin's own bare source are out of scope
|
|
160
|
+
# for two concrete, evidenced reasons, not a hypothetical:
|
|
161
|
+
#
|
|
162
|
+
# 1. `scripts/generate-j-alias.sh` step 2 already rewrites self-referential
|
|
163
|
+
# `skills/<X>/` occurrences to `skills/j-<X>/` for ordinary body content at
|
|
164
|
+
# generation time — a REMAINING bare self-reference in a `skills/j-<X>/` file is
|
|
165
|
+
# therefore either (a) inside the generator's own OWN appended note (L294-304),
|
|
166
|
+
# whose bare self-mentions are inserted AFTER that rewrite pass and so were never
|
|
167
|
+
# caught by it, and which is accurate AS WRITTEN (describes real generation
|
|
168
|
+
# provenance) — see e.g. `skills/j-wtf/SKILL.md`'s "This skill is a
|
|
169
|
+
# literal-directory-name duplicate of `skills/wtf/`" / "generated/synced by
|
|
170
|
+
# ... from `skills/wtf/SKILL.md`" — or (b) deliberate, hand-written prose a later
|
|
171
|
+
# task substituted in place of that note, e.g. `skills/j-do/SKILL.md`'s
|
|
172
|
+
# "this file was previously generated from `skills/do/SKILL.md` ... `skills/do/`
|
|
173
|
+
# is the copy awaiting deletion by E50_S15" and `skills/j-dev-done/SKILL.md`'s
|
|
174
|
+
# equivalent — both intentionally keep referencing the BARE directory (as a
|
|
175
|
+
# concept: its history, or its pending-deletion fate), and rewriting either
|
|
176
|
+
# mention corrupts the sentence (turns "generated from X" into "generated from
|
|
177
|
+
# itself", or turns "X is the copy awaiting deletion" into a false claim about
|
|
178
|
+
# the twin itself).
|
|
179
|
+
# 2. `skills/j-init/` is the one hand-maintained (non-generator) pair predating this
|
|
180
|
+
# mechanism entirely — `scripts/generate-j-alias.sh` explicitly refuses to touch
|
|
181
|
+
# it. Its own prose ("Keep this file's instructions in lockstep with
|
|
182
|
+
# `skills/init/SKILL.md`") and its `scripts/apply-scaffold-visibility.sh` ("Keep
|
|
183
|
+
# this file's logic in lockstep with `skills/init/`'s copy") are legitimate,
|
|
184
|
+
# current, hand-authored statements about a live sibling relationship between two
|
|
185
|
+
# directories that both still exist and are both still hand-edited — rewriting
|
|
186
|
+
# either turns "in lockstep with skills/init/" into "in lockstep with itself".
|
|
187
|
+
#
|
|
188
|
+
# In every case found, retiring/rewriting this content (once the generator itself is
|
|
189
|
+
# retired/inverted, or once E50_S15 deletes the bare directories these notes describe)
|
|
190
|
+
# is E50_S14's scope ("Retire or Invert the Twin-Generation Scripts"), not this
|
|
191
|
+
# story's — E50_S11 rewrites CROSS-skill path strings only.
|
|
192
|
+
file_self_reference_name() {
|
|
193
|
+
local file="$1" rel
|
|
194
|
+
rel="${file#"$PROJECT_DIR"/}"
|
|
195
|
+
case "$rel" in
|
|
196
|
+
skills/j-*)
|
|
197
|
+
rel="${rel#skills/j-}"
|
|
198
|
+
echo "${rel%%/*}"
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
usage() {
|
|
204
|
+
cat >&2 <<'EOF'
|
|
205
|
+
Usage: repoint-skill-refs.sh (--dry-run|--apply) [PATH...]
|
|
206
|
+
|
|
207
|
+
--dry-run Report every candidate file/line/before/after. Writes nothing.
|
|
208
|
+
--apply Perform the rewrite in place.
|
|
209
|
+
|
|
210
|
+
PATH may be a file or directory; repeatable. Defaults to this repo's canonical
|
|
211
|
+
source scope (skills/, agents/, hooks/, scripts/, lib/, .github/agents/) when
|
|
212
|
+
no PATH is given.
|
|
213
|
+
EOF
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
MODE=""
|
|
217
|
+
TARGET_ARGS=()
|
|
218
|
+
|
|
219
|
+
while [ "$#" -gt 0 ]; do
|
|
220
|
+
case "$1" in
|
|
221
|
+
--dry-run)
|
|
222
|
+
MODE="dry-run"
|
|
223
|
+
shift
|
|
224
|
+
;;
|
|
225
|
+
--apply)
|
|
226
|
+
MODE="apply"
|
|
227
|
+
shift
|
|
228
|
+
;;
|
|
229
|
+
-h|--help)
|
|
230
|
+
usage
|
|
231
|
+
exit 0
|
|
232
|
+
;;
|
|
233
|
+
--)
|
|
234
|
+
shift
|
|
235
|
+
while [ "$#" -gt 0 ]; do
|
|
236
|
+
TARGET_ARGS+=("$1")
|
|
237
|
+
shift
|
|
238
|
+
done
|
|
239
|
+
;;
|
|
240
|
+
-*)
|
|
241
|
+
echo "repoint-skill-refs.sh: error: unknown flag '$1'" >&2
|
|
242
|
+
usage
|
|
243
|
+
exit 1
|
|
244
|
+
;;
|
|
245
|
+
*)
|
|
246
|
+
TARGET_ARGS+=("$1")
|
|
247
|
+
shift
|
|
248
|
+
;;
|
|
249
|
+
esac
|
|
250
|
+
done
|
|
251
|
+
|
|
252
|
+
if [ -z "$MODE" ]; then
|
|
253
|
+
echo "repoint-skill-refs.sh: error: exactly one of --dry-run or --apply is required" >&2
|
|
254
|
+
usage
|
|
255
|
+
exit 1
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
# ============================================================================
|
|
259
|
+
# 1. Live bare-vs-twin delta check — ALWAYS runs first, before any file I/O.
|
|
260
|
+
# ============================================================================
|
|
261
|
+
BARE_DIRS=()
|
|
262
|
+
TWIN_DIRS=()
|
|
263
|
+
UNPAIRED=()
|
|
264
|
+
|
|
265
|
+
if [ -d "$SKILLS_DIR" ]; then
|
|
266
|
+
for d in "$SKILLS_DIR"/*/; do
|
|
267
|
+
[ -d "$d" ] || continue
|
|
268
|
+
name="$(basename "$d")"
|
|
269
|
+
case "$name" in
|
|
270
|
+
j-*)
|
|
271
|
+
TWIN_DIRS+=("$name")
|
|
272
|
+
continue
|
|
273
|
+
;;
|
|
274
|
+
esac
|
|
275
|
+
BARE_DIRS+=("$name")
|
|
276
|
+
if is_exception "$name"; then
|
|
277
|
+
continue
|
|
278
|
+
fi
|
|
279
|
+
if [ ! -d "$SKILLS_DIR/j-$name" ]; then
|
|
280
|
+
UNPAIRED+=("$name")
|
|
281
|
+
fi
|
|
282
|
+
done
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
echo "== repoint-skill-refs.sh: bare-vs-twin delta =="
|
|
286
|
+
echo " bare directories (skills/<name>/, incl. exceptions): ${#BARE_DIRS[@]}"
|
|
287
|
+
echo " twin directories (skills/j-<name>/): ${#TWIN_DIRS[@]}"
|
|
288
|
+
if [ "${#UNPAIRED[@]}" -gt 0 ]; then
|
|
289
|
+
echo " UNPAIRED bare directories (no twin, not on the exception list):"
|
|
290
|
+
for u in "${UNPAIRED[@]}"; do
|
|
291
|
+
echo " - $u"
|
|
292
|
+
done
|
|
293
|
+
echo "repoint-skill-refs.sh: error: the directory(ies) named above have no skills/j-<name>/ twin and are not on the exception list. Disposition each one (generate the missing twin, add it to REPOINT_SKILL_REFS_EXCEPTIONS, or record another explicit decision) before running this script." >&2
|
|
294
|
+
exit 1
|
|
295
|
+
fi
|
|
296
|
+
echo " delta check clean — every bare directory is either excepted or paired with a twin."
|
|
297
|
+
echo
|
|
298
|
+
|
|
299
|
+
# ============================================================================
|
|
300
|
+
# 2. Determine the rewrite name set: every bare directory minus exceptions.
|
|
301
|
+
# ============================================================================
|
|
302
|
+
REWRITE_NAMES=()
|
|
303
|
+
for name in "${BARE_DIRS[@]}"; do
|
|
304
|
+
if ! is_exception "$name"; then
|
|
305
|
+
REWRITE_NAMES+=("$name")
|
|
306
|
+
fi
|
|
307
|
+
done
|
|
308
|
+
|
|
309
|
+
# ============================================================================
|
|
310
|
+
# 3. Resolve targets -> a flat file list (excluding PATH_EXCLUDE_PATTERNS and
|
|
311
|
+
# binary files).
|
|
312
|
+
# ============================================================================
|
|
313
|
+
if [ "${#TARGET_ARGS[@]}" -eq 0 ]; then
|
|
314
|
+
DEFAULT_SCOPE=(skills agents hooks scripts lib .github/agents)
|
|
315
|
+
for d in "${DEFAULT_SCOPE[@]}"; do
|
|
316
|
+
if [ -e "$PROJECT_DIR/$d" ]; then
|
|
317
|
+
TARGET_ARGS+=("$PROJECT_DIR/$d")
|
|
318
|
+
fi
|
|
319
|
+
done
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
FILES=()
|
|
323
|
+
for target in "${TARGET_ARGS[@]}"; do
|
|
324
|
+
case "$target" in
|
|
325
|
+
/*) abs_target="$target" ;;
|
|
326
|
+
*) abs_target="$PWD/$target" ;;
|
|
327
|
+
esac
|
|
328
|
+
if [ ! -e "$abs_target" ]; then
|
|
329
|
+
echo "repoint-skill-refs.sh: error: target '$target' does not exist" >&2
|
|
330
|
+
exit 1
|
|
331
|
+
fi
|
|
332
|
+
if [ -f "$abs_target" ]; then
|
|
333
|
+
candidates=("$abs_target")
|
|
334
|
+
else
|
|
335
|
+
while IFS= read -r -d '' f; do
|
|
336
|
+
candidates+=("$f")
|
|
337
|
+
done < <(find "$abs_target" -type f -print0)
|
|
338
|
+
fi
|
|
339
|
+
for f in "${candidates[@]-}"; do
|
|
340
|
+
[ -n "${f:-}" ] || continue
|
|
341
|
+
rel="${f#"$PROJECT_DIR"/}"
|
|
342
|
+
if is_path_excluded "$rel"; then
|
|
343
|
+
continue
|
|
344
|
+
fi
|
|
345
|
+
# Skip binary files.
|
|
346
|
+
if ! grep -Iq . "$f" 2>/dev/null; then
|
|
347
|
+
continue
|
|
348
|
+
fi
|
|
349
|
+
FILES+=("$f")
|
|
350
|
+
done
|
|
351
|
+
candidates=()
|
|
352
|
+
done
|
|
353
|
+
|
|
354
|
+
# Deduplicate FILES (a file could be reachable via more than one target arg).
|
|
355
|
+
# Avoid `mapfile` (a bash 4+ builtin) — this repo must stay compatible with macOS's
|
|
356
|
+
# stock /bin/bash 3.2 (see PROJECT_SUMMARY.md's E47_S04 note on this exact class of
|
|
357
|
+
# portability defect), so dedup via a plain while-read loop instead.
|
|
358
|
+
if [ "${#FILES[@]}" -gt 0 ]; then
|
|
359
|
+
DEDUPED_FILES=()
|
|
360
|
+
while IFS= read -r f; do
|
|
361
|
+
[ -n "$f" ] || continue
|
|
362
|
+
DEDUPED_FILES+=("$f")
|
|
363
|
+
done < <(printf '%s\n' "${FILES[@]}" | sort -u)
|
|
364
|
+
FILES=("${DEDUPED_FILES[@]}")
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
# ============================================================================
|
|
368
|
+
# 4. Scan (and, in --apply mode, rewrite) every file for every eligible name.
|
|
369
|
+
#
|
|
370
|
+
# Line-at-a-time (not a whole-file sed pass), against a per-file EFFECTIVE name list
|
|
371
|
+
# that excludes the file's own self-reference name (see file_self_reference_name()
|
|
372
|
+
# above) — so a skills/j-<X>/... file never has its own "skills/<X>/" mentions
|
|
373
|
+
# rewritten, while every other eligible cross-skill name is still rewritten normally
|
|
374
|
+
# in that same file.
|
|
375
|
+
#
|
|
376
|
+
# Boundary rule (not just a trailing slash): a match requires `skills/<name>`
|
|
377
|
+
# followed by either end-of-line or a character that is NOT [A-Za-z0-9_-] — i.e. not
|
|
378
|
+
# something that could continue the same identifier. A bare directory REFERENCE
|
|
379
|
+
# almost always has a trailing "/" (`skills/do/SKILL.md`), but a small, real set of
|
|
380
|
+
# prose/code mentions the bare name with no trailing slash at all — e.g.
|
|
381
|
+
# `mkdir -p "$dir/skills/do" "$dir/agents"` (found in
|
|
382
|
+
# scripts/verify-legacy-seed-reconcile.sh: this exact line, unrewritten, left the
|
|
383
|
+
# fixture creating a "do" directory while a nearby line wrote into "j-do/SKILL.md",
|
|
384
|
+
# breaking the rehearsal), or a comment like "Consumed by skills/do (Step 0)". A
|
|
385
|
+
# trailing-slash-only pattern would silently miss both. The boundary class still
|
|
386
|
+
# blocks a match from firing on a mere PREFIX of a longer name (`do` inside `doc`,
|
|
387
|
+
# `reconcile` inside `reconcile-origin`) because the very next character in those
|
|
388
|
+
# cases (`c`, `-`) is itself in the blocked class.
|
|
389
|
+
# ============================================================================
|
|
390
|
+
CHANGED_FILES=()
|
|
391
|
+
REPORT_COUNT=0
|
|
392
|
+
SKIPPED_COUNT=0
|
|
393
|
+
|
|
394
|
+
# Extended-regex boundary suffix shared by every match/replace in this section: end
|
|
395
|
+
# of line, or any character that is not part of a bare skill-directory-name token.
|
|
396
|
+
BOUNDARY_SUFFIX='([^A-Za-z0-9_-]|$)'
|
|
397
|
+
|
|
398
|
+
join_by_pipe() {
|
|
399
|
+
local IFS='|'
|
|
400
|
+
echo "$*"
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
for f in "${FILES[@]-}"; do
|
|
404
|
+
[ -n "${f:-}" ] || continue
|
|
405
|
+
|
|
406
|
+
self_name="$(file_self_reference_name "$f")"
|
|
407
|
+
|
|
408
|
+
# Effective rewrite name list for THIS file: every eligible name except its own
|
|
409
|
+
# self-reference name (if any).
|
|
410
|
+
FILE_REWRITE_NAMES=()
|
|
411
|
+
for name in "${REWRITE_NAMES[@]-}"; do
|
|
412
|
+
[ -n "${name:-}" ] || continue
|
|
413
|
+
if [ -n "$self_name" ] && [ "$name" = "$self_name" ]; then
|
|
414
|
+
continue
|
|
415
|
+
fi
|
|
416
|
+
FILE_REWRITE_NAMES+=("$name")
|
|
417
|
+
done
|
|
418
|
+
|
|
419
|
+
self_ref_regex=""
|
|
420
|
+
if [ -n "$self_name" ]; then
|
|
421
|
+
self_ref_regex="skills/${self_name}${BOUNDARY_SUFFIX}"
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
# Cheap short-circuit: does this file contain any EFFECTIVE candidate at all? A
|
|
425
|
+
# plain substring pre-check ("skills/" anywhere) gates the more expensive
|
|
426
|
+
# boundary-regex grep below, since the overwhelming majority of files never
|
|
427
|
+
# mention "skills/" at all.
|
|
428
|
+
has_candidate=0
|
|
429
|
+
if [ "${#FILE_REWRITE_NAMES[@]}" -gt 0 ] && grep -qF -- "skills/" "$f" 2>/dev/null; then
|
|
430
|
+
file_rewrite_alt="$(join_by_pipe "${FILE_REWRITE_NAMES[@]}")"
|
|
431
|
+
if grep -Eq -- "skills/(${file_rewrite_alt})${BOUNDARY_SUFFIX}" "$f" 2>/dev/null; then
|
|
432
|
+
has_candidate=1
|
|
433
|
+
fi
|
|
434
|
+
fi
|
|
435
|
+
|
|
436
|
+
if [ "$has_candidate" -eq 0 ]; then
|
|
437
|
+
# Still report the self-reference skip, if this file has one, so the report stays
|
|
438
|
+
# transparent about why a visible "skills/<self_name>" mention was left alone.
|
|
439
|
+
if [ -n "$self_name" ] && grep -Eq -- "$self_ref_regex" "$f" 2>/dev/null; then
|
|
440
|
+
while IFS=: read -r lineno linetext; do
|
|
441
|
+
[ -n "$lineno" ] || continue
|
|
442
|
+
echo "$f:$lineno: SKIP (self-reference to skills/${self_name} — E50_S14 scope): $linetext"
|
|
443
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
444
|
+
done < <(grep -En -- "$self_ref_regex" "$f" 2>/dev/null)
|
|
445
|
+
fi
|
|
446
|
+
continue
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
file_changed=0
|
|
450
|
+
tmp_file=""
|
|
451
|
+
if [ "$MODE" = "apply" ]; then
|
|
452
|
+
tmp_file="$(mktemp "${f}.repoint.XXXXXX")"
|
|
453
|
+
# `cp -p` before truncating: mktemp's own file is created with fresh default
|
|
454
|
+
# permissions (typically 600), which would silently drop an executable bit (or
|
|
455
|
+
# any other mode/timestamp) when moved over the original. Copying the original
|
|
456
|
+
# onto the temp path first inherits its mode; the later truncate-and-rewrite
|
|
457
|
+
# (via `>`/`>>` redirection below) only replaces content, never permissions.
|
|
458
|
+
cp -p "$f" "$tmp_file"
|
|
459
|
+
: > "$tmp_file"
|
|
460
|
+
fi
|
|
461
|
+
|
|
462
|
+
lineno=0
|
|
463
|
+
while IFS= read -r linetext || [ -n "$linetext" ]; do
|
|
464
|
+
lineno=$((lineno + 1))
|
|
465
|
+
newtext="$linetext"
|
|
466
|
+
|
|
467
|
+
case "$linetext" in
|
|
468
|
+
*"skills/"*)
|
|
469
|
+
# Per-line effective name list: drop any name whose TWIN form already
|
|
470
|
+
# appears on this same line. Handles the "named pair" sentence shape —
|
|
471
|
+
# e.g. scripts/generate-j-alias.sh: "the skills/init/ <-> skills/j-init/
|
|
472
|
+
# pair" — where the bare mention and the twin mention are deliberately
|
|
473
|
+
# both present, describing the pair itself. Rewriting the bare half there
|
|
474
|
+
# would turn it into "skills/j-init/ <-> skills/j-init/", a duplicate that
|
|
475
|
+
# erases the sentence's actual point. This is a per-LINE check (not
|
|
476
|
+
# per-file, unlike the self-reference guard above) because a pair sentence
|
|
477
|
+
# like this can appear anywhere, including files with no self-reference
|
|
478
|
+
# name at all.
|
|
479
|
+
line_names=()
|
|
480
|
+
for name in "${FILE_REWRITE_NAMES[@]-}"; do
|
|
481
|
+
[ -n "${name:-}" ] || continue
|
|
482
|
+
case "$linetext" in
|
|
483
|
+
*"skills/j-${name}"*) continue ;;
|
|
484
|
+
esac
|
|
485
|
+
line_names+=("$name")
|
|
486
|
+
done
|
|
487
|
+
if [ "${#line_names[@]}" -gt 0 ]; then
|
|
488
|
+
line_rewrite_alt="$(join_by_pipe "${line_names[@]}")"
|
|
489
|
+
# sed -E (POSIX extended regex, portable across GNU and BSD sed) does the
|
|
490
|
+
# actual boundary-preserving substitution: \1 is the matched name, \2 is
|
|
491
|
+
# the boundary character (or empty at end-of-line), both re-emitted after
|
|
492
|
+
# the "j-" prefix so only the name itself changes.
|
|
493
|
+
newtext="$(printf '%s' "$linetext" | sed -E "s#skills/(${line_rewrite_alt})${BOUNDARY_SUFFIX}#skills/j-\\1\\2#g")"
|
|
494
|
+
fi
|
|
495
|
+
;;
|
|
496
|
+
esac
|
|
497
|
+
|
|
498
|
+
if [ -n "$self_name" ] && [[ "$linetext" =~ $self_ref_regex ]]; then
|
|
499
|
+
echo "$f:$lineno: SKIP (self-reference to skills/${self_name} — E50_S14 scope): $linetext"
|
|
500
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
if [ "$newtext" != "$linetext" ]; then
|
|
504
|
+
echo "$f:$lineno: $linetext"
|
|
505
|
+
echo "$f:$lineno: -> $newtext"
|
|
506
|
+
REPORT_COUNT=$((REPORT_COUNT + 1))
|
|
507
|
+
file_changed=1
|
|
508
|
+
fi
|
|
509
|
+
|
|
510
|
+
if [ "$MODE" = "apply" ]; then
|
|
511
|
+
printf '%s\n' "$newtext" >> "$tmp_file"
|
|
512
|
+
fi
|
|
513
|
+
done < "$f"
|
|
514
|
+
|
|
515
|
+
if [ "$MODE" = "apply" ]; then
|
|
516
|
+
if [ "$file_changed" -eq 1 ]; then
|
|
517
|
+
mv "$tmp_file" "$f"
|
|
518
|
+
else
|
|
519
|
+
rm -f "$tmp_file"
|
|
520
|
+
fi
|
|
521
|
+
fi
|
|
522
|
+
|
|
523
|
+
if [ "$file_changed" -eq 1 ]; then
|
|
524
|
+
CHANGED_FILES+=("$f")
|
|
525
|
+
fi
|
|
526
|
+
done
|
|
527
|
+
|
|
528
|
+
echo
|
|
529
|
+
if [ "$MODE" = "dry-run" ]; then
|
|
530
|
+
echo "== repoint-skill-refs.sh: dry-run complete — $REPORT_COUNT line(s) would change, $SKIPPED_COUNT line(s) skipped (self-reference) =="
|
|
531
|
+
else
|
|
532
|
+
echo "== repoint-skill-refs.sh: apply complete — ${#CHANGED_FILES[@]} file(s) changed, $REPORT_COUNT line(s) rewritten, $SKIPPED_COUNT line(s) skipped (self-reference) =="
|
|
533
|
+
for cf in "${CHANGED_FILES[@]-}"; do
|
|
534
|
+
[ -n "${cf:-}" ] || continue
|
|
535
|
+
echo " - ${cf#"$PROJECT_DIR"/}"
|
|
536
|
+
done
|
|
537
|
+
fi
|
|
538
|
+
|
|
539
|
+
exit 0
|