@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.
Files changed (97) hide show
  1. package/README.md +85 -78
  2. package/agents/developer.md +1 -1
  3. package/agents/scrum-master.md +20 -2
  4. package/agents/tester.md +3 -3
  5. package/hooks/on_session_end.sh +5 -5
  6. package/lib/generate-agent-context.js +2 -2
  7. package/lib/generate-copilot-hooks.js +1 -1
  8. package/lib/generate-skill-allow-list.js +37 -3
  9. package/lib/mirror.js +1 -1
  10. package/lib/postinstall-manifest.js +1 -1
  11. package/lib/skill-allow-list.json +2 -2
  12. package/mcp/help/index.js +8 -17
  13. package/mcp/help/scan.js +73 -0
  14. package/package.json +6 -1
  15. package/project/app/api/parsers/knowledge-graph.js +100 -9
  16. package/project/app/api/routes/health.js +36 -0
  17. package/project/app/api/scripts/capture-snapshot.js +9 -6
  18. package/project/app/ui/dist/assets/{index-CdK3Qrep.css → index-BVR_7Owg.css} +1 -1
  19. package/project/app/ui/dist/assets/index-CtU2xLQm.js +104 -0
  20. package/project/app/ui/dist/index.html +2 -2
  21. package/project/app/ui/package.json +4 -0
  22. package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
  23. package/scripts/acquire-concurrency-slot.sh +1 -1
  24. package/scripts/apply-j-prefix.sh +46 -5
  25. package/scripts/audit-twin-divergence.sh +73 -5
  26. package/scripts/build-pages-site.sh +1 -1
  27. package/scripts/check-public-playbook-steps.sh +158 -52
  28. package/scripts/check-publicignore-match.sh +2 -2
  29. package/scripts/compute-deploy-reconcile.sh +5 -5
  30. package/scripts/delete-bare-skill-dirs.sh +330 -0
  31. package/scripts/generate-legacy-shipped-paths.js +2 -2
  32. package/scripts/idea_manager.sh +258 -3
  33. package/scripts/mark-deployed.sh +2 -2
  34. package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
  35. package/scripts/populate-knowledge-graph.js +213 -5
  36. package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
  37. package/scripts/postinstall.js +1 -1
  38. package/scripts/repoint-skill-refs.sh +539 -0
  39. package/scripts/todo_manager.sh +1 -1
  40. package/scripts/verify-legacy-seed-reconcile.sh +10 -10
  41. package/scripts/verify-postinstall-reconcile.sh +7 -7
  42. package/scripts/write-context-digest.sh +1 -1
  43. package/skills/j-clearify/SKILL.md +2 -2
  44. package/skills/j-close-story/scripts/check-privatized.sh +4 -4
  45. package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
  46. package/skills/j-do/SKILL.md +101 -17
  47. package/skills/j-doc-sync/SKILL.md +1 -0
  48. package/skills/j-gitignore/SKILL.md +157 -0
  49. package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
  50. package/skills/j-gitignore/scripts/_catalog.sh +105 -0
  51. package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
  52. package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
  53. package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
  54. package/skills/j-idea/SKILL.md +78 -6
  55. package/skills/j-idea/assets/idea_template.md +1 -1
  56. package/skills/j-improve/SKILL.md +1 -1
  57. package/skills/j-init/SKILL.md +53 -14
  58. package/skills/j-init/assets/.gitignore_template +1 -2
  59. package/skills/j-init/assets/scope-thresholds_template.json +5 -2
  60. package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
  61. package/skills/j-init/scripts/init.sh +22 -8
  62. package/skills/j-playbook/SKILL.md +1 -1
  63. package/skills/j-publish/SKILL.md +1 -1
  64. package/skills/j-publish/adapters/npm-ci.md +6 -1
  65. package/skills/j-publish/adapters/npm.md +1 -1
  66. package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
  67. package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
  68. package/skills/j-reconcile/SKILL.md +2 -2
  69. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +11 -11
  70. package/skills/j-redo/SKILL.md +1 -1
  71. package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
  72. package/skills/j-spinoff/SKILL.md +1 -1
  73. package/skills/j-status/SKILL.md +15 -0
  74. package/skills/j-todo/SKILL.md +3 -1
  75. package/skills/j-uncharted/SKILL.md +55 -8
  76. package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
  77. package/skills/j-uncharted/scripts/detect-dependencies.sh +1 -1
  78. package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
  79. package/skills/j-uncharted/scripts/elicitation-state.sh +46 -8
  80. package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
  81. package/skills/j-wtf/SKILL.md +1 -1
  82. package/skills/jenga/SKILL.md +43 -9
  83. package/skills/jenga/playbooks/board-hygiene.json +32 -0
  84. package/skills/jenga/playbooks/schema.json +73 -6
  85. package/skills/jenga/playbooks/understand-then-commit.json +19 -0
  86. package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
  87. package/skills/jenga/scripts/load-playbooks.sh +23 -11
  88. package/skills/jenga/scripts/match-playbook.sh +1 -1
  89. package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
  90. package/templates/SKILL_TEMPLATE.md +12 -0
  91. package/templates/permission-levels/level-4-elevated.json +1 -1
  92. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  93. package/templates/playbook-types.json +34 -6
  94. package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
  95. package/scripts/generate-j-alias.sh +0 -333
  96. package/skills/j-dev-done/SKILL.md +0 -53
  97. 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