@jenga-ai/agent 3.4.0 → 3.6.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 +37 -3
- 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 +6 -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/ui/dist/assets/{index-CdK3Qrep.css → index-BVR_7Owg.css} +1 -1
- package/project/app/ui/dist/assets/index-CtU2xLQm.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 +1 -1
- package/scripts/apply-j-prefix.sh +46 -5
- package/scripts/audit-twin-divergence.sh +73 -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 +330 -0
- package/scripts/generate-legacy-shipped-paths.js +2 -2
- package/scripts/idea_manager.sh +258 -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/repoint-skill-refs.sh +539 -0
- package/scripts/todo_manager.sh +1 -1
- 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-clearify/SKILL.md +2 -2
- package/skills/j-close-story/scripts/check-privatized.sh +4 -4
- package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
- package/skills/j-do/SKILL.md +101 -17
- package/skills/j-doc-sync/SKILL.md +1 -0
- 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-idea/SKILL.md +78 -6
- package/skills/j-idea/assets/idea_template.md +1 -1
- package/skills/j-improve/SKILL.md +1 -1
- package/skills/j-init/SKILL.md +53 -14
- package/skills/j-init/assets/.gitignore_template +1 -2
- package/skills/j-init/assets/scope-thresholds_template.json +5 -2
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
- package/skills/j-init/scripts/init.sh +22 -8
- package/skills/j-playbook/SKILL.md +1 -1
- package/skills/j-publish/SKILL.md +1 -1
- 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-reconcile/SKILL.md +2 -2
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +11 -11
- package/skills/j-redo/SKILL.md +1 -1
- package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
- package/skills/j-spinoff/SKILL.md +1 -1
- package/skills/j-status/SKILL.md +15 -0
- package/skills/j-todo/SKILL.md +3 -1
- package/skills/j-uncharted/SKILL.md +55 -8
- package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
- package/skills/j-uncharted/scripts/detect-dependencies.sh +1 -1
- package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
- package/skills/j-uncharted/scripts/elicitation-state.sh +46 -8
- package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
- package/skills/j-wtf/SKILL.md +1 -1
- package/skills/jenga/SKILL.md +43 -9
- 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.sh +1 -1
- package/skills/jenga/scripts/load-playbooks.sh +23 -11
- package/skills/jenga/scripts/match-playbook.sh +1 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
- package/templates/SKILL_TEMPLATE.md +12 -0
- 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 +34 -6
- package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
- 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
|
package/scripts/todo_manager.sh
CHANGED
|
@@ -83,8 +83,8 @@ make_pkg() {
|
|
|
83
83
|
cat > "$dir/package.json" <<'JSON'
|
|
84
84
|
{ "name": "@jenga-ai/agent", "version": "3.0.0", "type": "module" }
|
|
85
85
|
JSON
|
|
86
|
-
mkdir -p "$dir/skills/do" "$dir/agents"
|
|
87
|
-
printf '# do skill (still shipped)\n' > "$dir/skills/do/SKILL.md"
|
|
86
|
+
mkdir -p "$dir/skills/j-do" "$dir/agents"
|
|
87
|
+
printf '# do skill (still shipped)\n' > "$dir/skills/j-do/SKILL.md"
|
|
88
88
|
printf '# developer agent (still shipped)\n' > "$dir/agents/developer.md"
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -116,7 +116,7 @@ PKG="$FIXTURE/pkg"
|
|
|
116
116
|
make_pkg "$PKG"
|
|
117
117
|
write_legacy_paths "$PKG" \
|
|
118
118
|
"agents/developer.md" \
|
|
119
|
-
"skills/do/SKILL.md" \
|
|
119
|
+
"skills/j-do/SKILL.md" \
|
|
120
120
|
"skills/j-legacy-twin/SKILL.md" \
|
|
121
121
|
"../OUTSIDE-THE-FIXTURE.txt" \
|
|
122
122
|
"skills/trap/LINK.md"
|
|
@@ -146,7 +146,7 @@ assert_dir "$CONSUMER_A/.agents/skills/j-legacy-twin" \
|
|
|
146
146
|
"directory survives because it still holds a consumer file (.agents)"
|
|
147
147
|
assert_grep "seeded from known-shipped legacy paths" "$FIXTURE/a-install.log" \
|
|
148
148
|
"install log attributes the cleanup to legacy-path seeding"
|
|
149
|
-
assert_file "$CONSUMER_A/.agents/skills/do/SKILL.md" \
|
|
149
|
+
assert_file "$CONSUMER_A/.agents/skills/j-do/SKILL.md" \
|
|
150
150
|
"currently-shipped skill untouched"
|
|
151
151
|
assert_file "$CONSUMER_A/.agents/agents/developer.md" \
|
|
152
152
|
"currently-shipped agent file untouched"
|
|
@@ -158,7 +158,7 @@ import json, sys
|
|
|
158
158
|
paths = json.load(open(sys.argv[1]))["paths"]
|
|
159
159
|
assert "skills/j-legacy-twin/SKILL.md" not in paths, "deleted orphan must not be in the fresh manifest"
|
|
160
160
|
assert "skills/j-legacy-twin/my-notes.md" not in paths, "consumer file must never be in the manifest"
|
|
161
|
-
assert "skills/do/SKILL.md" in paths and "agents/developer.md" in paths, "current package paths must be present"
|
|
161
|
+
assert "skills/j-do/SKILL.md" in paths and "agents/developer.md" in paths, "current package paths must be present"
|
|
162
162
|
PYEOF
|
|
163
163
|
if [ $? -eq 0 ]; then pass "fresh manifest describes only what is actually on disk post-cleanup"; else fail "fresh manifest is inaccurate"; fi
|
|
164
164
|
|
|
@@ -174,13 +174,13 @@ assert_nogrep_re "[1-9][0-9]* stale file" "$FIXTURE/b-install.log" \
|
|
|
174
174
|
"first-ever install deletes nothing despite a non-empty legacy path list"
|
|
175
175
|
assert_grep "no previous install manifest" "$FIXTURE/b-install.log" \
|
|
176
176
|
"first-ever install reports plain additive-only, not a seeded outcome"
|
|
177
|
-
assert_file "$CONSUMER_B/.agents/skills/do/SKILL.md" \
|
|
177
|
+
assert_file "$CONSUMER_B/.agents/skills/j-do/SKILL.md" \
|
|
178
178
|
"first-ever install still copies current package files"
|
|
179
179
|
|
|
180
180
|
# ═══ Scenario C — a legacy path still currently shipped is never touched ═════
|
|
181
|
-
# (Implicitly exercised by Scenario A's "do"/"developer.md" already surviving, but
|
|
181
|
+
# (Implicitly exercised by Scenario A's "j-do"/"developer.md" already surviving, but
|
|
182
182
|
# asserted here as its own named check for clarity.)
|
|
183
|
-
assert_file "$CONSUMER_A/.claude/skills/do/SKILL.md" \
|
|
183
|
+
assert_file "$CONSUMER_A/.claude/skills/j-do/SKILL.md" \
|
|
184
184
|
"a legacy-listed path that is STILL shipped survives untouched (not stale)"
|
|
185
185
|
|
|
186
186
|
# ═══ Scenario D — a legacy path absent from disk is never phantom-seeded ═════
|
|
@@ -219,7 +219,7 @@ PKG_F1="$FIXTURE/pkg-f1"
|
|
|
219
219
|
make_pkg "$PKG_F1"
|
|
220
220
|
mkdir -p "$PKG_F1/skills/renamed-old"
|
|
221
221
|
printf '# renamed-old — removed in the next version\n' > "$PKG_F1/skills/renamed-old/SKILL.md"
|
|
222
|
-
write_legacy_paths "$PKG_F1" "agents/developer.md" "skills/do/SKILL.md"
|
|
222
|
+
write_legacy_paths "$PKG_F1" "agents/developer.md" "skills/j-do/SKILL.md"
|
|
223
223
|
|
|
224
224
|
PKG_F2="$FIXTURE/pkg-f2"
|
|
225
225
|
make_pkg "$PKG_F2"
|
|
@@ -228,7 +228,7 @@ import json
|
|
|
228
228
|
p = '$PKG_F2/package.json'
|
|
229
229
|
d = json.load(open(p)); d['version'] = '3.0.1'; json.dump(d, open(p, 'w'))
|
|
230
230
|
"
|
|
231
|
-
write_legacy_paths "$PKG_F2" "agents/developer.md" "skills/do/SKILL.md" "skills/renamed-old/SKILL.md"
|
|
231
|
+
write_legacy_paths "$PKG_F2" "agents/developer.md" "skills/j-do/SKILL.md" "skills/renamed-old/SKILL.md"
|
|
232
232
|
|
|
233
233
|
CONSUMER_F="$FIXTURE/consumer-f"
|
|
234
234
|
mkdir -p "$CONSUMER_F"
|
|
@@ -90,7 +90,7 @@ echo " fixture: $FIXTURE"
|
|
|
90
90
|
# ── package v1.0.0 ───────────────────────────────────────────────────────────
|
|
91
91
|
PKG1="$FIXTURE/pkg-v1"
|
|
92
92
|
make_pkg "$PKG1" "1.0.0"
|
|
93
|
-
add_skill "$PKG1" "do"
|
|
93
|
+
add_skill "$PKG1" "j-do" "# j-do — stable across both versions"
|
|
94
94
|
add_skill "$PKG1" "renamed-old" "# renamed-old — becomes renamed-new in v2"
|
|
95
95
|
add_skill "$PKG1" "j-legacy-twin" "# j-legacy-twin — excluded in v2 (E50_S06 style)"
|
|
96
96
|
printf '# developer agent v1\n' > "$PKG1/agents/developer.md"
|
|
@@ -98,7 +98,7 @@ printf '# developer agent v1\n' > "$PKG1/agents/developer.md"
|
|
|
98
98
|
# ── package v2.0.0 ───────────────────────────────────────────────────────────
|
|
99
99
|
PKG2="$FIXTURE/pkg-v2"
|
|
100
100
|
make_pkg "$PKG2" "2.0.0"
|
|
101
|
-
add_skill "$PKG2" "do"
|
|
101
|
+
add_skill "$PKG2" "j-do" "# j-do — stable across both versions" # byte-identical
|
|
102
102
|
add_skill "$PKG2" "renamed-new" "# renamed-new — replaces renamed-old"
|
|
103
103
|
# j-legacy-twin intentionally absent (excluded), renamed-old intentionally absent
|
|
104
104
|
printf '# developer agent v2 (changed)\n' > "$PKG2/agents/developer.md"
|
|
@@ -136,7 +136,7 @@ assert_file "$CONSUMER/.claude/.jenga-postinstall-manifest.json" \
|
|
|
136
136
|
"manifest written to .claude on first install"
|
|
137
137
|
assert_nogrep "preexisting/JUNK.md" "$CONSUMER/.agents/.jenga-postinstall-manifest.json" \
|
|
138
138
|
"consumer file is NOT recorded in the manifest"
|
|
139
|
-
assert_grep "skills/do/SKILL.md" "$CONSUMER/.agents/.jenga-postinstall-manifest.json" \
|
|
139
|
+
assert_grep "skills/j-do/SKILL.md" "$CONSUMER/.agents/.jenga-postinstall-manifest.json" \
|
|
140
140
|
"manifest records mirrored package paths"
|
|
141
141
|
|
|
142
142
|
# ═══ Scenario B — upgrade with renames / removals / exclusions ════════════════
|
|
@@ -153,7 +153,7 @@ done
|
|
|
153
153
|
|
|
154
154
|
# Record identity of a file common to both versions, to prove it is not needlessly
|
|
155
155
|
# deleted and recopied (mirror() should classify it as `skipped`).
|
|
156
|
-
DO_BEFORE="$(ls -li "$CONSUMER/.agents/skills/do/SKILL.md" | awk '{print $1, $6, $7, $8}')"
|
|
156
|
+
DO_BEFORE="$(ls -li "$CONSUMER/.agents/skills/j-do/SKILL.md" | awk '{print $1, $6, $7, $8}')"
|
|
157
157
|
|
|
158
158
|
run_install "$PKG2" "$CONSUMER" "$FIXTURE/install-v2.log"
|
|
159
159
|
|
|
@@ -196,7 +196,7 @@ assert_file "$CONSUMER/.claude/skills/renamed-new/SKILL.md" \
|
|
|
196
196
|
"renamed-to skill installed (.claude)"
|
|
197
197
|
|
|
198
198
|
# 5. Unchanged common file was neither deleted nor recopied.
|
|
199
|
-
DO_AFTER="$(ls -li "$CONSUMER/.agents/skills/do/SKILL.md" | awk '{print $1, $6, $7, $8}')"
|
|
199
|
+
DO_AFTER="$(ls -li "$CONSUMER/.agents/skills/j-do/SKILL.md" | awk '{print $1, $6, $7, $8}')"
|
|
200
200
|
if [ "$DO_BEFORE" = "$DO_AFTER" ]; then
|
|
201
201
|
pass "file common to both versions untouched (same inode+mtime)"
|
|
202
202
|
else
|
|
@@ -375,9 +375,9 @@ mkdir -p "$CONSUMER_M"
|
|
|
375
375
|
run_install "$PKG1" "$CONSUMER_M" "$FIXTURE/missing-v1.log"
|
|
376
376
|
run_install "$PKG2M" "$CONSUMER_M" "$FIXTURE/missing-v2.log"
|
|
377
377
|
|
|
378
|
-
assert_file "$CONSUMER_M/.agents/skills/do/SKILL.md" \
|
|
378
|
+
assert_file "$CONSUMER_M/.agents/skills/j-do/SKILL.md" \
|
|
379
379
|
"missing copySet entry does not wipe the mirrored subtree (.agents)"
|
|
380
|
-
assert_file "$CONSUMER_M/.claude/skills/do/SKILL.md" \
|
|
380
|
+
assert_file "$CONSUMER_M/.claude/skills/j-do/SKILL.md" \
|
|
381
381
|
"missing copySet entry does not wipe the mirrored subtree (.claude)"
|
|
382
382
|
assert_grep "Upgrade cleanup skipped" "$FIXTURE/missing-v2.log" \
|
|
383
383
|
"packaging regression reported, cleanup skipped"
|