@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
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# scripts/generate-j-alias.sh — generate/sync a j-<name> polyfill alias skill directory
|
|
3
|
-
#
|
|
4
|
-
# Mechanizes the hand-maintained pattern that produced skills/j-init/ (E50_S04): a
|
|
5
|
-
# literal-directory-name duplicate of skills/<name>/ that gives a guaranteed-unshadowed
|
|
6
|
-
# way to reach a skill even when a host tool's own built-in command of the same name
|
|
7
|
-
# would otherwise shadow the bare /<name> form. Per CLAUDE.md's Skill Implementation
|
|
8
|
-
# Principle, this must not be repeated by hand across every skill — this script performs
|
|
9
|
-
# both the initial generation and any later resync-after-source-change as a single
|
|
10
|
-
# deterministic command (E50_S05_T01).
|
|
11
|
-
#
|
|
12
|
-
# For the given <skill-name>, this script:
|
|
13
|
-
# 1. Copies the full skills/<skill-name>/ tree into skills/j-<skill-name>/, overwriting
|
|
14
|
-
# the destination on re-run (the sync case).
|
|
15
|
-
# 2. Rewrites every self-referential "skills/<skill-name>/" path reference inside the
|
|
16
|
-
# copied SKILL.md and any copied scripts to "skills/j-<skill-name>/" — including the
|
|
17
|
-
# .claude/skills/<skill-name>/ and .agents/skills/<skill-name>/ mirrored-install
|
|
18
|
-
# fallback paths, which fall out of the same literal substring replace.
|
|
19
|
-
# 3. Rewrites the copied SKILL.md's frontmatter: name: j.<skill-name> -> name:
|
|
20
|
-
# j.j-<skill-name>; description: reframed as a polyfill alias; keywords: gets
|
|
21
|
-
# j-<skill-name> and polyfill appended (existing keywords preserved, list created
|
|
22
|
-
# if absent); examples: gets a "j-<skill-name>" example appended if the list exists.
|
|
23
|
-
# 4. Inserts a short, programmatically generated lockstep/duplicate-alias note near the
|
|
24
|
-
# top of the body.
|
|
25
|
-
#
|
|
26
|
-
# Idempotent: the target directory is always fully rebuilt from the source on every run
|
|
27
|
-
# (never incrementally patched), so two runs against an unchanged source produce a
|
|
28
|
-
# byte-identical result, and a source change is picked up in full on the next run.
|
|
29
|
-
#
|
|
30
|
-
# Exclusions:
|
|
31
|
-
# - Only operates on directories containing a SKILL.md (skips e.g. skills/index/,
|
|
32
|
-
# which has no SKILL.md and is not part of skill routing) — enforced as a hard error,
|
|
33
|
-
# not a silent no-op, so a typo'd or non-skill argument fails clearly.
|
|
34
|
-
# - Refuses to touch the skills/init/ <-> skills/j-init/ pair — that pair already
|
|
35
|
-
# exists, is hand-maintained, and is explicitly out of scope for this story.
|
|
36
|
-
# - Refuses to touch skills/jenga/ or skills/jenga-permission-level/ (and their would-be
|
|
37
|
-
# j-jenga/j-jenga-permission-level twins) — hard error, non-zero exit (E50_S06_T01).
|
|
38
|
-
# These are root orchestrator commands; their invocation surface must stay exactly
|
|
39
|
-
# /jenga (j.jenga) and /jenga-permission-level (j.jenga-permission-level), never a
|
|
40
|
-
# doubled j-jenga alias. A prior generic run of this generator produced exactly that
|
|
41
|
-
# unreachable doubled twin by mistake — see docs/skill-authoring.md's "j-<name>
|
|
42
|
-
# directory twins" Exclusions paragraph.
|
|
43
|
-
#
|
|
44
|
-
# This is a single-skill generator only. Running it across every skill in the repo is a
|
|
45
|
-
# separate task (E50_S05_T02) — deliberately not built here.
|
|
46
|
-
#
|
|
47
|
-
# Usage:
|
|
48
|
-
# scripts/generate-j-alias.sh <skill-name>
|
|
49
|
-
#
|
|
50
|
-
# Examples:
|
|
51
|
-
# scripts/generate-j-alias.sh status # generates/syncs skills/j-status/
|
|
52
|
-
# scripts/generate-j-alias.sh close-story # generates/syncs skills/j-close-story/
|
|
53
|
-
|
|
54
|
-
set -euo pipefail
|
|
55
|
-
|
|
56
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
57
|
-
# shellcheck source=lib/resolve-project-dir.sh
|
|
58
|
-
source "$SCRIPT_DIR/../lib/resolve-project-dir.sh"
|
|
59
|
-
|
|
60
|
-
PROJECT_DIR="$JENGA_PROJECT_DIR"
|
|
61
|
-
SKILLS_DIR="$PROJECT_DIR/skills"
|
|
62
|
-
|
|
63
|
-
usage() {
|
|
64
|
-
echo "Usage: $(basename "$0") <skill-name>" >&2
|
|
65
|
-
echo " Generates/syncs skills/j-<skill-name>/ as a polyfill alias of skills/<skill-name>/." >&2
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if [ "$#" -ne 1 ]; then
|
|
69
|
-
usage
|
|
70
|
-
exit 1
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
case "$1" in
|
|
74
|
-
-h|--help)
|
|
75
|
-
usage
|
|
76
|
-
exit 0
|
|
77
|
-
;;
|
|
78
|
-
esac
|
|
79
|
-
|
|
80
|
-
SKILL_NAME="$1"
|
|
81
|
-
|
|
82
|
-
# Security guard (E50_S05_T01 rework — see
|
|
83
|
-
# project/rapports/problems/E50_S05_T01-generator-path-traversal.md): validate
|
|
84
|
-
# the skill-name argument BEFORE any path is constructed from it, let alone
|
|
85
|
-
# before anything destructive runs. An unsanitized argument containing '/' or
|
|
86
|
-
# '..' segments previously let SRC_DIR/TARGET_DIR (built by plain string
|
|
87
|
-
# concatenation below) resolve outside $SKILLS_DIR, and the destructive
|
|
88
|
-
# rmtree-then-copytree step ran before the one existing safety check
|
|
89
|
-
# (frontmatter name match) ever fired. This allowlist alone closes that vector
|
|
90
|
-
# — it forbids '/' and '.' entirely — and runs first, ahead of every other
|
|
91
|
-
# check including the init/j-init special-case below.
|
|
92
|
-
if [[ ! "$SKILL_NAME" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then
|
|
93
|
-
echo "generate-j-alias.sh: error: '$SKILL_NAME' is not a valid skill name — must match ^[a-z0-9][a-z0-9_-]*\$ (lowercase letters, digits, '-', '_' only, starting with a letter or digit). Refusing to construct a path from it." >&2
|
|
94
|
-
exit 1
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
if [[ "$SKILL_NAME" == "init" || "$SKILL_NAME" == "j-init" ]]; then
|
|
98
|
-
echo "generate-j-alias.sh: refusing to touch '$SKILL_NAME' — the skills/init/ <-> skills/j-init/ pair already exists and is hand-maintained (out of scope for this generator, E50_S05). No-op." >&2
|
|
99
|
-
exit 0
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
# Hard exclusion (E50_S06_T01): jenga and jenga-permission-level are root orchestrator
|
|
103
|
-
# commands whose invocation surface must stay exactly /jenga (j.jenga) and
|
|
104
|
-
# /jenga-permission-level (j.jenga-permission-level) — never a doubled j-jenga alias.
|
|
105
|
-
# Unlike the init/j-init case above (a silent no-op, exit 0, because that pair is
|
|
106
|
-
# legitimately hand-maintained and pre-dates this generator), this is a hard error with a
|
|
107
|
-
# non-zero exit: a j-jenga/j-jenga-permission-level twin should never be generated at all,
|
|
108
|
-
# so a future accidental invocation must fail loudly rather than silently succeed as a
|
|
109
|
-
# no-op. See docs/skill-authoring.md's "j-<name> directory twins" Exclusions paragraph.
|
|
110
|
-
if [[ "$SKILL_NAME" == "jenga" || "$SKILL_NAME" == "j-jenga" || "$SKILL_NAME" == "jenga-permission-level" || "$SKILL_NAME" == "j-jenga-permission-level" ]]; then
|
|
111
|
-
echo "generate-j-alias.sh: error: refusing to generate a j-<name> twin for '$SKILL_NAME' — jenga and jenga-permission-level are root orchestrator commands and are hard-excluded from this generator (E50_S06_T01). Their invocation surface must stay exactly /jenga (j.jenga) and /jenga-permission-level (j.jenga-permission-level), never a doubled j-jenga alias. This is not a no-op — it is an intentional hard failure." >&2
|
|
112
|
-
exit 1
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
SRC_DIR="$SKILLS_DIR/$SKILL_NAME"
|
|
116
|
-
SRC_SKILL_MD="$SRC_DIR/SKILL.md"
|
|
117
|
-
|
|
118
|
-
if [ ! -f "$SRC_SKILL_MD" ]; then
|
|
119
|
-
echo "generate-j-alias.sh: error: '$SRC_DIR' does not contain a SKILL.md — not a skill directory, refusing to generate skills/j-$SKILL_NAME/." >&2
|
|
120
|
-
exit 1
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
TARGET_DIR="$SKILLS_DIR/j-$SKILL_NAME"
|
|
124
|
-
|
|
125
|
-
# Defense in depth (belt and suspenders on top of the allowlist above):
|
|
126
|
-
# resolve SRC_DIR/TARGET_DIR to absolute canonical paths and hard-fail unless
|
|
127
|
-
# each is confined to a direct child of $SKILLS_DIR. This protects against the
|
|
128
|
-
# allowlist regex being weakened in a future edit, or $SKILLS_DIR itself
|
|
129
|
-
# containing a symlink that could redirect an otherwise-validated path outside
|
|
130
|
-
# skills/. Both checks below run before the python heredoc, i.e. strictly
|
|
131
|
-
# before any rmtree/copytree call.
|
|
132
|
-
REAL_SKILLS_DIR="$(realpath "$SKILLS_DIR")"
|
|
133
|
-
REAL_SRC_DIR="$(realpath "$SRC_DIR")"
|
|
134
|
-
if [ "$(dirname "$REAL_SRC_DIR")" != "$REAL_SKILLS_DIR" ]; then
|
|
135
|
-
echo "generate-j-alias.sh: error: resolved source path '$REAL_SRC_DIR' escapes '$REAL_SKILLS_DIR' — refusing to proceed." >&2
|
|
136
|
-
exit 1
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
# TARGET_DIR may not exist yet (first-run generation case), and this
|
|
140
|
-
# platform's realpath has no -m/--canonicalize-missing option, so it can't be
|
|
141
|
-
# realpath'd directly when absent. Its parent is always $SKILLS_DIR by
|
|
142
|
-
# construction (SKILL_NAME is a single path component, and the allowlist above
|
|
143
|
-
# already forbids '/' and '..' in it), so check the literal parent/basename
|
|
144
|
-
# shape here, and additionally realpath the target itself when it already
|
|
145
|
-
# exists (the resync case) to catch a symlink planted at the target that
|
|
146
|
-
# points outside skills/.
|
|
147
|
-
TARGET_BASENAME="$(basename "$TARGET_DIR")"
|
|
148
|
-
if [[ "$TARGET_BASENAME" == "." || "$TARGET_BASENAME" == ".." || "$TARGET_BASENAME" == */* ]]; then
|
|
149
|
-
echo "generate-j-alias.sh: error: computed target basename '$TARGET_BASENAME' is unsafe — refusing to proceed." >&2
|
|
150
|
-
exit 1
|
|
151
|
-
fi
|
|
152
|
-
if [ "$(dirname "$TARGET_DIR")" != "$SKILLS_DIR" ]; then
|
|
153
|
-
echo "generate-j-alias.sh: error: computed target path '$TARGET_DIR' does not resolve directly under '$SKILLS_DIR' — refusing to proceed." >&2
|
|
154
|
-
exit 1
|
|
155
|
-
fi
|
|
156
|
-
if [ -e "$TARGET_DIR" ] || [ -L "$TARGET_DIR" ]; then
|
|
157
|
-
REAL_TARGET_DIR="$(realpath "$TARGET_DIR")"
|
|
158
|
-
if [ "$(dirname "$REAL_TARGET_DIR")" != "$REAL_SKILLS_DIR" ]; then
|
|
159
|
-
echo "generate-j-alias.sh: error: resolved target path '$REAL_TARGET_DIR' escapes '$REAL_SKILLS_DIR' — refusing to proceed." >&2
|
|
160
|
-
exit 1
|
|
161
|
-
fi
|
|
162
|
-
fi
|
|
163
|
-
|
|
164
|
-
export SKILL_NAME SRC_DIR TARGET_DIR
|
|
165
|
-
|
|
166
|
-
python3 - <<'PY'
|
|
167
|
-
import os
|
|
168
|
-
import re
|
|
169
|
-
import shutil
|
|
170
|
-
import sys
|
|
171
|
-
|
|
172
|
-
skill_name = os.environ["SKILL_NAME"]
|
|
173
|
-
src_dir = os.environ["SRC_DIR"]
|
|
174
|
-
target_dir = os.environ["TARGET_DIR"]
|
|
175
|
-
|
|
176
|
-
old_ref = f"skills/{skill_name}/"
|
|
177
|
-
new_ref = f"skills/j-{skill_name}/"
|
|
178
|
-
|
|
179
|
-
# ---- 1. Copy the full tree, overwriting the destination (the sync case) ----
|
|
180
|
-
if os.path.lexists(target_dir):
|
|
181
|
-
shutil.rmtree(target_dir)
|
|
182
|
-
shutil.copytree(src_dir, target_dir, symlinks=True)
|
|
183
|
-
|
|
184
|
-
# ---- 2. Rewrite self-referential paths in every copied file ----
|
|
185
|
-
for dirpath, _dirnames, filenames in os.walk(target_dir):
|
|
186
|
-
for fn in filenames:
|
|
187
|
-
fpath = os.path.join(dirpath, fn)
|
|
188
|
-
try:
|
|
189
|
-
with open(fpath, "r", encoding="utf-8") as fh:
|
|
190
|
-
content = fh.read()
|
|
191
|
-
except (UnicodeDecodeError, OSError):
|
|
192
|
-
continue # binary or unreadable — leave untouched
|
|
193
|
-
if old_ref in content:
|
|
194
|
-
with open(fpath, "w", encoding="utf-8") as fh:
|
|
195
|
-
fh.write(content.replace(old_ref, new_ref))
|
|
196
|
-
|
|
197
|
-
# ---- 3. Rewrite SKILL.md frontmatter + insert the lockstep note ----
|
|
198
|
-
target_skill_md = os.path.join(target_dir, "SKILL.md")
|
|
199
|
-
with open(target_skill_md, "r", encoding="utf-8") as fh:
|
|
200
|
-
lines = fh.readlines()
|
|
201
|
-
|
|
202
|
-
if not lines or lines[0].strip() != "---":
|
|
203
|
-
sys.exit(f"generate-j-alias.sh: error: {target_skill_md} has no opening '---' frontmatter marker")
|
|
204
|
-
|
|
205
|
-
close_idx = None
|
|
206
|
-
for idx in range(1, len(lines)):
|
|
207
|
-
if lines[idx].strip() == "---":
|
|
208
|
-
close_idx = idx
|
|
209
|
-
break
|
|
210
|
-
if close_idx is None:
|
|
211
|
-
sys.exit(f"generate-j-alias.sh: error: {target_skill_md} has no closing '---' frontmatter marker")
|
|
212
|
-
|
|
213
|
-
# Parse the frontmatter into an ordered list of top-level fields. This is the same
|
|
214
|
-
# informal line-oriented approach scripts/apply-j-prefix.sh already uses elsewhere in
|
|
215
|
-
# this repo for SKILL.md frontmatter — not a full YAML parser, consistent with how
|
|
216
|
-
# frontmatter is already handled here.
|
|
217
|
-
key_re = re.compile(r'^([A-Za-z_][\w-]*):(.*)$')
|
|
218
|
-
fields = []
|
|
219
|
-
i = 1
|
|
220
|
-
while i < close_idx:
|
|
221
|
-
line = lines[i]
|
|
222
|
-
m = key_re.match(line)
|
|
223
|
-
if not m:
|
|
224
|
-
i += 1
|
|
225
|
-
continue
|
|
226
|
-
block = [line]
|
|
227
|
-
j = i + 1
|
|
228
|
-
if m.group(2).strip() == "":
|
|
229
|
-
# possible nested block (list or mapping) — consume indented continuation lines
|
|
230
|
-
while j < close_idx and lines[j].startswith(" "):
|
|
231
|
-
block.append(lines[j])
|
|
232
|
-
j += 1
|
|
233
|
-
fields.append({"key": m.group(1), "lines": block})
|
|
234
|
-
i = j
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def find_field(key):
|
|
238
|
-
for f in fields:
|
|
239
|
-
if f["key"] == key:
|
|
240
|
-
return f
|
|
241
|
-
return None
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
name_field = find_field("name")
|
|
245
|
-
desc_field = find_field("description")
|
|
246
|
-
|
|
247
|
-
if name_field is None or len(name_field["lines"]) != 1:
|
|
248
|
-
sys.exit(f"generate-j-alias.sh: error: {target_skill_md} has no single-line 'name:' field")
|
|
249
|
-
if desc_field is None or len(desc_field["lines"]) != 1:
|
|
250
|
-
sys.exit(f"generate-j-alias.sh: error: {target_skill_md} has no single-line 'description:' field")
|
|
251
|
-
|
|
252
|
-
current_name = key_re.match(name_field["lines"][0]).group(2).strip().strip('"\'')
|
|
253
|
-
# Accepts the current "j.<name>" form, the bare "<name>" form, and the legacy
|
|
254
|
-
# "j:<name>" form (pre-E50_S07_T01) so a twin can still be regenerated from a
|
|
255
|
-
# not-yet-migrated source instead of hard-aborting. Whatever the source carries,
|
|
256
|
-
# the twin is always emitted on the current "j." separator.
|
|
257
|
-
if current_name not in (f"j.{skill_name}", f"j:{skill_name}", skill_name):
|
|
258
|
-
sys.exit(
|
|
259
|
-
f"generate-j-alias.sh: error: frontmatter name '{current_name}' in {target_skill_md} "
|
|
260
|
-
f"does not match expected 'j.{skill_name}' (or bare '{skill_name}') — refusing to guess, aborting."
|
|
261
|
-
)
|
|
262
|
-
name_field["lines"][0] = f"name: j.j-{skill_name}\n"
|
|
263
|
-
|
|
264
|
-
original_desc = key_re.match(desc_field["lines"][0]).group(2).strip().strip('"\'').rstrip(".")
|
|
265
|
-
new_desc = (
|
|
266
|
-
f"Polyfill alias of the {skill_name} skill under a collision-safe directory name. "
|
|
267
|
-
f"Identical behavior to /{skill_name} — {original_desc}. "
|
|
268
|
-
f"Use when the bare /{skill_name} form is shadowed by another tool's own built-in "
|
|
269
|
-
f"command of the same name."
|
|
270
|
-
)
|
|
271
|
-
desc_field["lines"][0] = f"description: {new_desc}\n"
|
|
272
|
-
|
|
273
|
-
keywords_field = find_field("keywords")
|
|
274
|
-
if keywords_field is not None:
|
|
275
|
-
keywords_field["lines"].append(f" - j-{skill_name}\n")
|
|
276
|
-
keywords_field["lines"].append(" - polyfill\n")
|
|
277
|
-
else:
|
|
278
|
-
fields.append({
|
|
279
|
-
"key": "keywords",
|
|
280
|
-
"lines": ["keywords:\n", f" - j-{skill_name}\n", " - polyfill\n"],
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
examples_field = find_field("examples")
|
|
284
|
-
if examples_field is not None:
|
|
285
|
-
examples_field["lines"].append(f' - "j-{skill_name}"\n')
|
|
286
|
-
# if absent: per spec, do not fabricate an examples: list where none existed
|
|
287
|
-
|
|
288
|
-
new_frontmatter_lines = []
|
|
289
|
-
for f in fields:
|
|
290
|
-
new_frontmatter_lines.extend(f["lines"])
|
|
291
|
-
|
|
292
|
-
body_lines = lines[close_idx + 1:]
|
|
293
|
-
|
|
294
|
-
note_text = (
|
|
295
|
-
f"This skill is a literal-directory-name duplicate of `skills/{skill_name}/`. It exists so "
|
|
296
|
-
f"that `/j-{skill_name}` (and `j.j-{skill_name}`) give a guaranteed-unshadowed way to reach "
|
|
297
|
-
f"the same flow as `/{skill_name}`, even if a host tool's own built-in command of the same "
|
|
298
|
-
f"name would otherwise shadow or override the bare `/{skill_name}` alias (Claude Code's "
|
|
299
|
-
f"native skill resolution is a literal-string, directory-name-based match — see "
|
|
300
|
-
f"`docs/skill-authoring.md`'s \"Invocation Convention\").\n\n"
|
|
301
|
-
f"This file is generated/synced by `scripts/generate-j-alias.sh {skill_name}` from "
|
|
302
|
-
f"`skills/{skill_name}/SKILL.md` — do not hand-edit it; re-run the generator instead to "
|
|
303
|
-
f"pick up source changes."
|
|
304
|
-
)
|
|
305
|
-
note_block = []
|
|
306
|
-
for para in note_text.split("\n\n"):
|
|
307
|
-
note_block.append(para + "\n")
|
|
308
|
-
note_block.append("\n")
|
|
309
|
-
|
|
310
|
-
heading_idx = None
|
|
311
|
-
for idx, bl in enumerate(body_lines):
|
|
312
|
-
if bl.startswith("# "):
|
|
313
|
-
heading_idx = idx
|
|
314
|
-
break
|
|
315
|
-
|
|
316
|
-
if heading_idx is not None:
|
|
317
|
-
insert_at = heading_idx + 1
|
|
318
|
-
if insert_at < len(body_lines) and body_lines[insert_at].strip() == "":
|
|
319
|
-
insert_at += 1
|
|
320
|
-
prefix = body_lines[:insert_at]
|
|
321
|
-
if prefix and prefix[-1].strip() != "":
|
|
322
|
-
prefix = prefix + ["\n"]
|
|
323
|
-
new_body_lines = prefix + note_block + body_lines[insert_at:]
|
|
324
|
-
else:
|
|
325
|
-
new_body_lines = note_block + body_lines
|
|
326
|
-
|
|
327
|
-
new_lines = [lines[0]] + new_frontmatter_lines + [lines[close_idx]] + new_body_lines
|
|
328
|
-
|
|
329
|
-
with open(target_skill_md, "w", encoding="utf-8") as fh:
|
|
330
|
-
fh.writelines(new_lines)
|
|
331
|
-
|
|
332
|
-
print(f"generate-j-alias.sh: generated/synced skills/j-{skill_name}/ from skills/{skill_name}/")
|
|
333
|
-
PY
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: j.dev-done
|
|
3
|
-
description: Polyfill alias of the dev-done skill under a collision-safe directory name. Identical behavior to /dev-done — Commit the current work and immediately sync it into the .claude/ and .agents/ mirrors. Shortcut that chains /commit followed by /self-sync. Use when the bare /dev-done form is shadowed by another tool's own built-in command of the same name.
|
|
4
|
-
keywords:
|
|
5
|
-
- dev done
|
|
6
|
-
- commit and sync
|
|
7
|
-
- commit and mirror
|
|
8
|
-
- done syncing
|
|
9
|
-
- j-dev-done
|
|
10
|
-
- polyfill
|
|
11
|
-
examples:
|
|
12
|
-
- "dev-done E42_S04_T01"
|
|
13
|
-
- "commit this and sync the mirrors"
|
|
14
|
-
- "j-dev-done"
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
# Dev-Done — Commit, then Sync the Mirrors
|
|
18
|
-
|
|
19
|
-
This skill is a literal-directory-name duplicate of `skills/dev-done/`. It exists so that `/j-dev-done` (and `j.j-dev-done`) give a guaranteed-unshadowed way to reach the same flow as `/dev-done`, even if a host tool's own built-in command of the same name would otherwise shadow or override the bare `/dev-done` alias (Claude Code's native skill resolution is a literal-string, directory-name-based match — see `docs/skill-authoring.md`'s "Invocation Convention").
|
|
20
|
-
|
|
21
|
-
This file is generated/synced by `scripts/generate-j-alias.sh dev-done` from `skills/dev-done/SKILL.md` — do not hand-edit it; re-run the generator instead to pick up source changes.
|
|
22
|
-
|
|
23
|
-
Chains `/commit <scope-id>` and `/self-sync`, the same "convenience shortcut" pattern `skills/lgtm/SKILL.md`
|
|
24
|
-
uses for `/commit` + `/continue` — applied here to the commit -> mirror-sync sequence instead of the
|
|
25
|
-
commit -> next-task sequence. Useful right after implementing a root-level framework change
|
|
26
|
-
(`skills/`, `agents/`, `hooks/`, `scripts/`, `templates/`, `settings.json`), so the `.claude/` and
|
|
27
|
-
`.agents/` mirrors never sit stale waiting on a manual `/self-sync` call.
|
|
28
|
-
|
|
29
|
-
The deterministic decision of whether `/commit` halted early (nothing to commit) or completed
|
|
30
|
-
normally lives in `skills/j-dev-done/scripts/classify-commit-outcome.sh`, not inline here — see that
|
|
31
|
-
script's header for the exact contract.
|
|
32
|
-
|
|
33
|
-
## Instructions
|
|
34
|
-
|
|
35
|
-
1. Invoke the `/commit` skill with whatever scope-id argument `/dev-done` itself was given (e.g.
|
|
36
|
-
`/dev-done E42_S04_T01` invokes `/commit E42_S04_T01`) — the same EST scope-id argument contract
|
|
37
|
-
`/commit` already accepts (epic, story, or task id; see `skills/commit/SKILL.md`). Capture its full
|
|
38
|
-
output text.
|
|
39
|
-
|
|
40
|
-
2. Pass the captured output to the classifier script:
|
|
41
|
-
```
|
|
42
|
-
skills/j-dev-done/scripts/classify-commit-outcome.sh <<< "$COMMIT_OUTPUT"
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
3. If the script exits `1` (HALT): print its stdout — the exact message `No implementation to
|
|
46
|
-
commit.` — to the user, and stop. Do **not** invoke `/self-sync`.
|
|
47
|
-
|
|
48
|
-
4. If the script exits `0` (PROCEED): invoke the `/self-sync` skill and wait for it to finish. This
|
|
49
|
-
happens regardless of whether `/commit` reported drift or doc-sync findings along the way — those
|
|
50
|
-
are informational, not blocking (see `skills/commit/SKILL.md`).
|
|
51
|
-
|
|
52
|
-
5. Report both steps' output to the user in sequence — the commit result first, then the self-sync
|
|
53
|
-
summary.
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# ---------------------------------------------------------------------------
|
|
3
|
-
# skills/j-dev-done/scripts/classify-commit-outcome.sh
|
|
4
|
-
#
|
|
5
|
-
# Deterministic classifier backing `/dev-done` (story E42_S04, task
|
|
6
|
-
# E42_S04_T01). `/dev-done` chains `/commit <scope-id>` then `/self-sync`,
|
|
7
|
-
# but must NOT proceed to `/self-sync` when `/commit` halted early because
|
|
8
|
-
# there was nothing to commit. Per CLAUDE.md's "Skill Implementation
|
|
9
|
-
# Principle — Scripts Over Inline Logic", that halt-vs-proceed check is a
|
|
10
|
-
# deterministic, repeatable text match — it does not belong as inline
|
|
11
|
-
# conditional logic in `skills/j-dev-done/SKILL.md`, so it lives here instead,
|
|
12
|
-
# the same way `skills/self-sync/SKILL.md` delegates its own filesystem work
|
|
13
|
-
# to `skills/self-sync/scripts/run.js` rather than inlining it.
|
|
14
|
-
#
|
|
15
|
-
# `/commit` and `/self-sync` are themselves agent-executed skills, not plain
|
|
16
|
-
# executables — this script never shells out to invoke either one. It only
|
|
17
|
-
# classifies text that the calling agent already captured from `/commit`'s
|
|
18
|
-
# output. Invoking `/commit` and `/self-sync` remains `SKILL.md`'s job.
|
|
19
|
-
#
|
|
20
|
-
# ---------------------------------------------------------------------------
|
|
21
|
-
# WHAT IT MATCHES
|
|
22
|
-
# ---------------------------------------------------------------------------
|
|
23
|
-
# `skills/commit/SKILL.md`'s Instructions section states, verbatim:
|
|
24
|
-
#
|
|
25
|
-
# "If no epic, task, or story has been implemented, exit with the message:
|
|
26
|
-
# "No implementation to commit.""
|
|
27
|
-
#
|
|
28
|
-
# That exact sentence — "No implementation to commit." — is the ONLY halt
|
|
29
|
-
# signal this script recognizes. If a future edit to `skills/commit/SKILL.md`
|
|
30
|
-
# changes that wording, this script's match must be updated to match, or it
|
|
31
|
-
# will silently stop recognizing the halt case (see Dependencies & Risks in
|
|
32
|
-
# the task's execution plan).
|
|
33
|
-
#
|
|
34
|
-
# ---------------------------------------------------------------------------
|
|
35
|
-
# INVOCATION
|
|
36
|
-
# ---------------------------------------------------------------------------
|
|
37
|
-
# classify-commit-outcome.sh [<captured-commit-output>]
|
|
38
|
-
#
|
|
39
|
-
# The text `/commit` produced is passed either as the single argument, or
|
|
40
|
-
# (when no argument is given) read from stdin in full, e.g.:
|
|
41
|
-
#
|
|
42
|
-
# skills/j-dev-done/scripts/classify-commit-outcome.sh <<< "$COMMIT_OUTPUT"
|
|
43
|
-
# printf '%s' "$COMMIT_OUTPUT" | skills/j-dev-done/scripts/classify-commit-outcome.sh
|
|
44
|
-
#
|
|
45
|
-
# ---------------------------------------------------------------------------
|
|
46
|
-
# OUTPUT CONTRACT (stdout, single line, nothing else)
|
|
47
|
-
# ---------------------------------------------------------------------------
|
|
48
|
-
# HALT case stdout is the exact literal message "No implementation to
|
|
49
|
-
# commit." — the calling agent relays this stdout verbatim
|
|
50
|
-
# to the user and does NOT invoke `/self-sync`.
|
|
51
|
-
# PROCEED case stdout is the literal token "PROCEED" — the calling
|
|
52
|
-
# agent invokes `/self-sync` next.
|
|
53
|
-
#
|
|
54
|
-
# ---------------------------------------------------------------------------
|
|
55
|
-
# EXIT CODES
|
|
56
|
-
# ---------------------------------------------------------------------------
|
|
57
|
-
# 0 PROCEED — `/commit` completed normally (regardless of whether it
|
|
58
|
-
# reported drift or doc-sync findings along the way); stdout is
|
|
59
|
-
# "PROCEED".
|
|
60
|
-
# 1 HALT — `/commit` halted early with the exact "No implementation to
|
|
61
|
-
# commit." message; stdout is that exact message.
|
|
62
|
-
# 2 Usage/environment error (e.g. no input at all — neither an argument
|
|
63
|
-
# nor anything on stdin); nothing meaningful on stdout, an error on
|
|
64
|
-
# stderr.
|
|
65
|
-
# ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
set -euo pipefail
|
|
68
|
-
|
|
69
|
-
HALT_MESSAGE="No implementation to commit."
|
|
70
|
-
|
|
71
|
-
if [ "$#" -gt 1 ]; then
|
|
72
|
-
echo "Usage: $(basename "$0") [<captured-commit-output>]" >&2
|
|
73
|
-
echo " (or pipe the captured output on stdin with no argument)" >&2
|
|
74
|
-
exit 2
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
if [ "$#" -eq 1 ]; then
|
|
78
|
-
INPUT="$1"
|
|
79
|
-
else
|
|
80
|
-
# No argument — read the full captured output from stdin.
|
|
81
|
-
if [ -t 0 ]; then
|
|
82
|
-
echo "ERROR: no argument given and stdin is a terminal — nothing to classify" >&2
|
|
83
|
-
exit 2
|
|
84
|
-
fi
|
|
85
|
-
INPUT="$(cat)"
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
if [ -z "$INPUT" ]; then
|
|
89
|
-
echo "ERROR: no commit output provided to classify" >&2
|
|
90
|
-
exit 2
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
# Trim leading/trailing whitespace from the whole input for the exact-match case.
|
|
94
|
-
TRIMMED="$(printf '%s' "$INPUT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
95
|
-
|
|
96
|
-
if [ "$TRIMMED" = "$HALT_MESSAGE" ]; then
|
|
97
|
-
printf '%s\n' "$HALT_MESSAGE"
|
|
98
|
-
exit 1
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
# Also match the halt message appearing as its own standalone line anywhere
|
|
102
|
-
# in a longer captured response (e.g. wrapped in surrounding agent prose),
|
|
103
|
-
# since the calling agent's captured "output text" is free-form, not a
|
|
104
|
-
# guaranteed exact-equals string.
|
|
105
|
-
while IFS= read -r line; do
|
|
106
|
-
LINE_TRIMMED="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
107
|
-
if [ "$LINE_TRIMMED" = "$HALT_MESSAGE" ]; then
|
|
108
|
-
printf '%s\n' "$HALT_MESSAGE"
|
|
109
|
-
exit 1
|
|
110
|
-
fi
|
|
111
|
-
done <<< "$INPUT"
|
|
112
|
-
|
|
113
|
-
printf 'PROCEED\n'
|
|
114
|
-
exit 0
|