@jenga-ai/agent 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +75 -243
  2. package/agents/developer.md +5 -5
  3. package/agents/scrum-master.md +23 -23
  4. package/agents/tester.md +5 -5
  5. package/lib/generate-skill-allow-list.js +9 -3
  6. package/lib/skill-allow-list.json +2 -3
  7. package/package.json +15 -25
  8. package/scripts/apply-j-prefix.sh +25 -12
  9. package/scripts/generate-j-alias.sh +333 -0
  10. package/skills/{brainstorm → j-brainstorm}/SKILL.md +9 -2
  11. package/skills/{btw → j-btw}/SKILL.md +9 -2
  12. package/skills/{clearify → j-clearify}/SKILL.md +9 -2
  13. package/skills/{close-story → j-close-story}/SKILL.md +17 -10
  14. package/skills/{close-story → j-close-story}/scripts/check-privatized.sh +2 -2
  15. package/skills/{close-story → j-close-story}/scripts/check-story-closeable.sh +1 -1
  16. package/skills/{close-story → j-close-story}/scripts/extract-task-diff-stats.sh +1 -1
  17. package/skills/{commit → j-commit}/SKILL.md +9 -2
  18. package/skills/j-continue/SKILL.md +36 -0
  19. package/skills/{deep-dive → j-deep-dive}/SKILL.md +9 -8
  20. package/skills/{dev-done → j-dev-done}/SKILL.md +11 -4
  21. package/skills/{dev-done → j-dev-done}/scripts/classify-commit-outcome.sh +4 -4
  22. package/skills/{distribute → j-distribute}/SKILL.md +17 -10
  23. package/skills/{distribute → j-distribute}/scripts/distribute-changes.sh +1 -1
  24. package/skills/{do → j-do}/SKILL.md +12 -5
  25. package/skills/{doc → j-doc}/README.md +5 -5
  26. package/skills/{doc → j-doc}/SKILL.md +15 -8
  27. package/skills/{doc → j-doc}/authoring-notes.md +1 -1
  28. package/skills/{doc-sync → j-doc-sync}/SKILL.md +9 -2
  29. package/skills/{dooo → j-dooo}/SKILL.md +9 -2
  30. package/skills/j-error/SKILL.md +36 -0
  31. package/skills/{evaluate → j-evaluate}/SKILL.md +9 -2
  32. package/skills/j-examplify/SKILL.md +49 -0
  33. package/skills/{help → j-help}/SKILL.md +9 -2
  34. package/skills/{idea → j-idea}/SKILL.md +10 -3
  35. package/skills/{idea → j-idea}/assets/idea_handoff_template.md +1 -1
  36. package/skills/{improve → j-improve}/SKILL.md +9 -2
  37. package/skills/j-init/SKILL.md +2 -2
  38. package/skills/j-jbp/SKILL.md +32 -0
  39. package/skills/j-lgtm/SKILL.md +28 -0
  40. package/skills/{pi-plan → j-pi-plan}/SKILL.md +10 -3
  41. package/skills/{proceed → j-proceed}/SKILL.md +9 -2
  42. package/skills/{publish → j-publish}/SKILL.md +47 -40
  43. package/skills/{publish → j-publish}/adapters/droplet.md +1 -1
  44. package/skills/{publish → j-publish}/adapters/mobile-ios.md +3 -3
  45. package/skills/{publish → j-publish}/adapters/npm-ci.md +3 -3
  46. package/skills/{publish → j-publish}/adapters/npm.md +8 -8
  47. package/skills/{publish → j-publish}/assets/ci-contract.md +2 -2
  48. package/skills/{publish → j-publish}/schemas/publish.schema.json +1 -1
  49. package/skills/{publish → j-publish}/scripts/npm_stage_inspect.sh +34 -1
  50. package/skills/{publish → j-publish}/scripts/npm_stage_pipeline.sh +9 -4
  51. package/skills/{publish → j-publish}/scripts/publish_deploy.sh +4 -4
  52. package/skills/{publish → j-publish}/scripts/validate_npm_stage_env.sh +1 -1
  53. package/skills/{publish → j-publish}/wizards/droplet.md +1 -1
  54. package/skills/{publish → j-publish}/wizards/mobile-ios.md +1 -1
  55. package/skills/{publish → j-publish}/wizards/npm-ci.md +1 -1
  56. package/skills/{publish → j-publish}/wizards/npm.md +1 -1
  57. package/skills/{reconcile → j-reconcile}/SKILL.md +12 -5
  58. package/skills/{reconcile → j-reconcile}/scripts/detect-unlinked-code.sh +2 -2
  59. package/skills/{reconcile → j-reconcile}/scripts/resolve-reconcile-scope.sh +3 -3
  60. package/skills/{reconcile-origin → j-reconcile-origin}/SKILL.md +13 -6
  61. package/skills/{redo → j-redo}/SKILL.md +9 -2
  62. package/skills/{skillify → j-skillify}/SKILL.md +10 -3
  63. package/skills/{spinoff → j-spinoff}/SKILL.md +9 -2
  64. package/skills/{status → j-status}/SKILL.md +9 -2
  65. package/skills/{todo → j-todo}/SKILL.md +10 -3
  66. package/skills/{todo → j-todo}/assets/todo_handoff_template.md +1 -1
  67. package/skills/{todo → j-todo}/scripts/add_trivial_task.sh +3 -3
  68. package/skills/{todo → j-todo}/scripts/update_story_tasks.py +2 -2
  69. package/skills/{uncharted → j-uncharted}/SKILL.md +35 -28
  70. package/skills/{uncharted → j-uncharted}/assets/UNDERSTANDING_DOC_TEMPLATE.md +2 -2
  71. package/skills/{uncharted → j-uncharted}/scripts/detect-dependencies.sh +1 -1
  72. package/skills/{uncharted → j-uncharted}/scripts/detect-tests.sh +1 -1
  73. package/skills/{uncharted → j-uncharted}/scripts/directory-triage.sh +3 -3
  74. package/skills/{uncharted → j-uncharted}/scripts/elicitation-state.sh +3 -3
  75. package/skills/{uncharted → j-uncharted}/scripts/enumerate-target.sh +1 -1
  76. package/skills/{uncharted → j-uncharted}/scripts/import-source.sh +1 -1
  77. package/skills/{uncharted → j-uncharted}/scripts/inspect-provenance.sh +1 -1
  78. package/skills/{uncharted → j-uncharted}/scripts/resolve-segment-target.sh +5 -5
  79. package/skills/{uncharted → j-uncharted}/scripts/run-engine.sh +1 -1
  80. package/skills/{uncharted → j-uncharted}/scripts/validate-proposed-items.sh +2 -2
  81. package/skills/{uncharted → j-uncharted}/scripts/write-backfilled-epics.sh +1 -1
  82. package/skills/j-wtf/SKILL.md +27 -0
  83. package/skills/jenga/SKILL.md +1 -1
  84. package/skills/jenga-permission-level/SKILL.md +1 -1
  85. package/templates/SCRUM_BOARD_SCHEMA.md +1 -1
  86. package/templates/agent-context.md.tpl +10 -10
  87. package/templates/copilot-instructions.md.tpl +57 -19
  88. package/skills/continue/SKILL.md +0 -29
  89. package/skills/error/SKILL.md +0 -29
  90. package/skills/examplify/SKILL.md +0 -42
  91. package/skills/init/SKILL.md +0 -155
  92. package/skills/init/assets/scope-thresholds_template.json +0 -7
  93. package/skills/init/assets/strategy_stub_template.md +0 -38
  94. package/skills/init/assets/workflow_template.json +0 -30
  95. package/skills/init/scripts/apply-project-visibility.sh +0 -176
  96. package/skills/init/scripts/detect-existing-codebase.sh +0 -166
  97. package/skills/init/scripts/init.sh +0 -116
  98. package/skills/jbp/SKILL.md +0 -25
  99. package/skills/lgtm/SKILL.md +0 -21
  100. package/skills/skillify/assets/init-new/assets/.gitignore_template +0 -15
  101. package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +0 -13
  102. package/skills/skillify/assets/init-new/assets/directory_structure.txt +0 -14
  103. package/skills/skillify/assets/init-new/assets/test-config_template.json +0 -4
  104. package/skills/wtf/SKILL.md +0 -20
  105. /package/skills/{close-story → j-close-story}/scripts/compute-scope-divergence.sh +0 -0
  106. /package/skills/{close-story → j-close-story}/scripts/extract-diff-stats.sh +0 -0
  107. /package/skills/{close-story → j-close-story}/scripts/update-task-frontmatter.sh +0 -0
  108. /package/skills/{commit → j-commit}/assets/user_instructions_template.md +0 -0
  109. /package/skills/{distribute → j-distribute}/CONFIG_SCHEMA.md +0 -0
  110. /package/skills/{distribute → j-distribute}/scripts/check-version.sh +0 -0
  111. /package/skills/{distribute → j-distribute}/scripts/commit-version-bump.sh +0 -0
  112. /package/skills/{do → j-do}/assets/intent-vs-diff-prompt.md +0 -0
  113. /package/skills/{do → j-do}/assets/sender_template.json +0 -0
  114. /package/skills/{doc → j-doc}/assets/path-objectives.yaml +0 -0
  115. /package/skills/{doc → j-doc}/scripts/resolve_last_update.py +0 -0
  116. /package/skills/{doc-sync → j-doc-sync}/assets/default_excludes.txt +0 -0
  117. /package/skills/{doc-sync → j-doc-sync}/assets/doc_targets.md +0 -0
  118. /package/skills/{evaluate → j-evaluate}/assets/evaluation_invokation_template.yml +0 -0
  119. /package/skills/{evaluate → j-evaluate}/assets/evaluation_rapport_template.md +0 -0
  120. /package/skills/{idea → j-idea}/assets/idea_template.md +0 -0
  121. /package/skills/{pi-plan → j-pi-plan}/assets/epic.json +0 -0
  122. /package/skills/{pi-plan → j-pi-plan}/assets/story_template.md +0 -0
  123. /package/skills/{publish → j-publish}/assets/ExportOptions.plist.template +0 -0
  124. /package/skills/{publish → j-publish}/assets/ownership-matrix.md +0 -0
  125. /package/skills/{publish → j-publish}/assets/publish.example.json +0 -0
  126. /package/skills/{publish → j-publish}/assets/publish.example.npm-ci.json +0 -0
  127. /package/skills/{publish → j-publish}/assets/publish.example.npm.json +0 -0
  128. /package/skills/{publish → j-publish}/assets/secrets-guide.md +0 -0
  129. /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-minimal.json +0 -0
  130. /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-empty-secrets.json +0 -0
  131. /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-workflow-path.json +0 -0
  132. /package/skills/{publish → j-publish}/scripts/check_target_config.sh +0 -0
  133. /package/skills/{publish → j-publish}/scripts/droplet_pipeline.sh +0 -0
  134. /package/skills/{publish → j-publish}/scripts/finalize_changelog.sh +0 -0
  135. /package/skills/{publish → j-publish}/scripts/generate_release_notes.sh +0 -0
  136. /package/skills/{publish → j-publish}/scripts/ios_pipeline.sh +0 -0
  137. /package/skills/{publish → j-publish}/scripts/npm_ci_pipeline.sh +0 -0
  138. /package/skills/{publish → j-publish}/scripts/npm_pipeline.sh +0 -0
  139. /package/skills/{publish → j-publish}/scripts/publish_common.sh +0 -0
  140. /package/skills/{publish → j-publish}/scripts/reconcile_tags.sh +0 -0
  141. /package/skills/{publish → j-publish}/scripts/run_gates.sh +0 -0
  142. /package/skills/{publish → j-publish}/scripts/setup_wizard.sh +0 -0
  143. /package/skills/{publish → j-publish}/scripts/show_history.sh +0 -0
  144. /package/skills/{publish → j-publish}/scripts/suggest_semver_bump.sh +0 -0
  145. /package/skills/{publish → j-publish}/scripts/validate_config.sh +0 -0
  146. /package/skills/{publish → j-publish}/scripts/validate_droplet_env.sh +0 -0
  147. /package/skills/{publish → j-publish}/scripts/validate_ios_env.sh +0 -0
  148. /package/skills/{publish → j-publish}/scripts/validate_npm_ci_env.sh +0 -0
  149. /package/skills/{publish → j-publish}/scripts/validate_npm_env.sh +0 -0
  150. /package/skills/{publish → j-publish}/scripts/write_ledger_entry.sh +0 -0
  151. /package/skills/{reconcile → j-reconcile}/assets/report_format.md +0 -0
  152. /package/skills/{reconcile-origin → j-reconcile-origin}/scripts/reconcile-origin.sh +0 -0
  153. /package/skills/{skillify → j-skillify}/assets/init-new/SKILL.md +0 -0
  154. /package/skills/{init → j-skillify/assets/init-new}/assets/.gitignore_template +0 -0
  155. /package/skills/{init → j-skillify/assets/init-new}/assets/PROJECT_SUMMARY_template.md +0 -0
  156. /package/skills/{init → j-skillify/assets/init-new}/assets/directory_structure.txt +0 -0
  157. /package/skills/{init → j-skillify/assets/init-new}/assets/test-config_template.json +0 -0
  158. /package/skills/{skillify → j-skillify}/assets/init-new/assets/workflow_template.json +0 -0
  159. /package/skills/{skillify → j-skillify}/assets/init-new/scripts/init.sh +0 -0
  160. /package/skills/{skillify → j-skillify}/assets/init-old/SKILL.md +0 -0
  161. /package/skills/{status → j-status}/assets/output_format.md +0 -0
  162. /package/skills/{todo → j-todo}/assets/todo_template.md +0 -0
  163. /package/skills/{uncharted → j-uncharted}/assets/SEGMENT_PROPOSAL_TEMPLATE.md +0 -0
  164. /package/skills/{uncharted → j-uncharted}/scripts/apply-subsystem-cap.sh +0 -0
  165. /package/skills/{uncharted → j-uncharted}/scripts/discover-subsystems.sh +0 -0
package/agents/tester.md CHANGED
@@ -167,7 +167,7 @@ Always include the sender object in the response.
167
167
 
168
168
  **Trigger.** During steps 4-6 of "Invoked for test implementation and/or execution" above, you find a defect. Before defaulting to step 9's rapport path, classify it: is this **mechanical** (safe to fix in place) or does it **need a rapport** (a design decision or real risk is involved)? This classification is what determines which of the two remediation paths below you take — it is not optional bookkeeping, and it happens at the moment the defect is found, not retroactively.
169
169
 
170
- **Motivating case.** `project/rapports/analysis/E42_S04-execution-overhead-postmortem.md` Finding 3: the tester found `skills/dev-done/scripts/classify-commit-outcome.sh` committed without its executable bit (`chmod +x`) plus a dead, unreachable error branch. Both were one-line-class fixes, but the only path available at the time was the full formal one — a `test_failure` rapport, a separate developer fix commit, a re-verification pass, and a second complete `j:self-sync` mirror run. That produced 3 of the task's 6 total commits for defects that needed no design judgment at all. This subsection exists so that pattern doesn't repeat.
170
+ **Motivating case.** `project/rapports/analysis/E42_S04-execution-overhead-postmortem.md` Finding 3: the tester found `skills/dev-done/scripts/classify-commit-outcome.sh` committed without its executable bit (`chmod +x`) plus a dead, unreachable error branch. Both were one-line-class fixes, but the only path available at the time was the full formal one — a `test_failure` rapport, a separate developer fix commit, a re-verification pass, and a second complete `j.self-sync` mirror run. That produced 3 of the task's 6 total commits for defects that needed no design judgment at all. This subsection exists so that pattern doesn't repeat.
171
171
 
172
172
  **Qualifying examples (mechanical — fix in place).**
173
173
  - A missing executable bit or other permission-bit error (e.g. a script committed without `chmod +x`) — the exact E42_S04 Finding 3 case.
@@ -258,11 +258,11 @@ Append this as a new array entry — never overwrite existing log content. This
258
258
 
259
259
  **Trigger.** The task being verified — or its parent story — carries `crucial_level: locked` in frontmatter, per `templates/SCRUM_BOARD_SCHEMA.md`'s "Crucial Flag Fields (Story, Task)" section.
260
260
 
261
- **Effect — forced inline scope.** `execution_scope` is force-set to `inline` for any `locked` task, overriding whatever scope `j:jenga`'s Execution Scope Assignment heuristics would otherwise assign — or auto-correcting a wrong value in place, with a logged `override_justification` note. The concrete mechanism is `skills/jenga/SKILL.md` Phase 0.5's **Rule 4 — `crucial_level: locked` forces `execution_scope: inline`** (added by E39_S03_T03). As tester, verify this field is actually `inline` on any `locked` item you're validating — a value that slipped through would itself be a defect worth flagging.
261
+ **Effect — forced inline scope.** `execution_scope` is force-set to `inline` for any `locked` task, overriding whatever scope `j.jenga`'s Execution Scope Assignment heuristics would otherwise assign — or auto-correcting a wrong value in place, with a logged `override_justification` note. The concrete mechanism is `skills/jenga/SKILL.md` Phase 0.5's **Rule 4 — `crucial_level: locked` forces `execution_scope: inline`** (added by E39_S03_T03). As tester, verify this field is actually `inline` on any `locked` item you're validating — a value that slipped through would itself be a defect worth flagging.
262
262
 
263
- **Effect — dispatch-time rejection of backgrounding.** A `locked` task can never be routed to a background subagent, a worktree-isolated session, or a bundled `j:jenga` story-batch execution, regardless of its `execution_scope` value. This is enforced at two points, both added by E39_S03_T04: `skills/jenga/SKILL.md` Phase 3.5 step 5's **Guard: locked-task disqualifier (defense-in-depth)** and `skills/do/SKILL.md` Section 4.2's **Locked-task dispatch guard (defense-in-depth)**. This matters directly to you as tester: you must never yourself dispatch, recommend, or improvise a background subagent, a separate worktree-isolated session, or a bundled batch run in order to verify a `locked` item faster or in parallel with other work — verification of a `locked` item happens in the same foreground session the guards already pinned it to, same as implementation.
263
+ **Effect — dispatch-time rejection of backgrounding.** A `locked` task can never be routed to a background subagent, a worktree-isolated session, or a bundled `j.jenga` story-batch execution, regardless of its `execution_scope` value. This is enforced at two points, both added by E39_S03_T04: `skills/jenga/SKILL.md` Phase 3.5 step 5's **Guard: locked-task disqualifier (defense-in-depth)** and `skills/do/SKILL.md` Section 4.2's **Locked-task dispatch guard (defense-in-depth)**. This matters directly to you as tester: you must never yourself dispatch, recommend, or improvise a background subagent, a separate worktree-isolated session, or a bundled batch run in order to verify a `locked` item faster or in parallel with other work — verification of a `locked` item happens in the same foreground session the guards already pinned it to, same as implementation.
264
264
 
265
- **No agent-discretion obligation.** Unlike `advisory` (a reporting-cadence habit) and `gated` (a confirmation you must actively pause and perform), `locked` requires no judgment call from you. It is fully enforced by pre-flight validation (Rule 4) and dispatch-time guards (the Phase 3.5 and `j:do` guards above) before the developer ever begins work — none of this depends on you noticing or remembering anything mid-verification. Your only obligation is to recognize that a `locked` task always runs (and was always verified) in the current foreground session, and to never suggest or perform a workaround that would route around that guarantee. If you find evidence during verification that a `locked` item was actually run in a backgrounded or worktree-isolated context, treat that as a guard failure worth flagging (see Rapport System), not something to silently pass.
265
+ **No agent-discretion obligation.** Unlike `advisory` (a reporting-cadence habit) and `gated` (a confirmation you must actively pause and perform), `locked` requires no judgment call from you. It is fully enforced by pre-flight validation (Rule 4) and dispatch-time guards (the Phase 3.5 and `j.do` guards above) before the developer ever begins work — none of this depends on you noticing or remembering anything mid-verification. Your only obligation is to recognize that a `locked` task always runs (and was always verified) in the current foreground session, and to never suggest or perform a workaround that would route around that guarantee. If you find evidence during verification that a `locked` item was actually run in a backgrounded or worktree-isolated context, treat that as a guard failure worth flagging (see Rapport System), not something to silently pass.
266
266
 
267
267
  ### Invoked for analysis or comparison testing
268
268
  When invoked to run an analysis or comparison:
@@ -455,7 +455,7 @@ There is no default analytics run. Analytics only happen when explicitly scoped
455
455
 
456
456
  ## Investigative Mode
457
457
 
458
- **Trigger.** You are sometimes dispatched not to validate a task's implementation, but purely to build understanding of what the existing test suite actually covers for a named flow or target — e.g. by the scrum-master during `j:uncharted`'s conversational architecture elicitation, alongside the developer's Investigative Mode pass over the same flow. This is a distinct dispatch mode from the standard Sender Object / Task Intake / Status Management flow above, recognized by the request itself (you are asked to *trace coverage*, not to *validate a task*), not by any board field.
458
+ **Trigger.** You are sometimes dispatched not to validate a task's implementation, but purely to build understanding of what the existing test suite actually covers for a named flow or target — e.g. by the scrum-master during `j.uncharted`'s conversational architecture elicitation, alongside the developer's Investigative Mode pass over the same flow. This is a distinct dispatch mode from the standard Sender Object / Task Intake / Status Management flow above, recognized by the request itself (you are asked to *trace coverage*, not to *validate a task*), not by any board field.
459
459
 
460
460
  **Hard constraints.** Investigative Mode is strictly read-only:
461
461
  - No worktree is created for write purposes, no test files are written or modified, no test runs that mutate state, no dependency installs.
@@ -55,10 +55,16 @@ function extractName(content) {
55
55
  const nameMatch = fmMatch[1].match(/^name:\s*(.+)$/m);
56
56
  if (!nameMatch) return null;
57
57
  const rawName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
58
- // Frontmatter may carry either the pre-E50_S01 bare form or the post-rename "j:<name>" form
59
- // (skills/*/SKILL.md was migrated in E50_S01_T02) — strip the prefix so the allow-list always
58
+ // Frontmatter may carry the pre-E50_S01 bare form, the E50_S01 "j:<name>" form, or the
59
+ // current "j.<name>" form (E50_S07_T01 swapped the separator because GitHub Copilot CLI
60
+ // rejects ":" in a skill name) — strip whichever prefix is present so the allow-list always
60
61
  // holds bare identifiers, matching mcp/router/skill-index.js's `bareName` handling.
61
- return rawName.replace(/^j:/i, "");
62
+ //
63
+ // Both separators must stay accepted: this guard is the E50_S02 anti-masquerading check, and
64
+ // a normalizer that fails to strip silently populates the allow-list with prefixed entries
65
+ // ("j.brainstorm" instead of "brainstorm"), which no longer match what the guard compares
66
+ // against. That is a security-guard failure, not a cosmetic one.
67
+ return rawName.replace(/^j[.:]/i, "");
62
68
  }
63
69
 
64
70
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
- "generated_at": "2026-09-04T00:52:12.012Z",
3
- "skill_count": 37,
2
+ "generated_at": "2026-09-06T15:53:37.575Z",
3
+ "skill_count": 36,
4
4
  "skills": [
5
5
  "brainstorm",
6
6
  "btw",
@@ -22,7 +22,6 @@
22
22
  "idea",
23
23
  "improve",
24
24
  "init",
25
- "j-init",
26
25
  "jbp",
27
26
  "jenga",
28
27
  "jenga-permission-level",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jenga-ai/agent",
3
- "version": "2.0.0",
4
- "description": "Structured multi-agent development workflow for AI coding agents \u2014 scrum board, role-bounded scrum master / developer / tester agents, and 30+ slash-command skills. Works with Claude Code, GitHub Copilot, and Codex.",
3
+ "version": "3.0.0",
4
+ "description": "An agentic development workflow for Claude Code, Copilot, and Codex — with a persistent Epic/Story/Task board, an isolated git worktree per task, and a separate tester agent that runs your test suite before anything is marked done.",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -54,36 +54,26 @@
54
54
  "url": "https://github.com/samwelmunga/jenga-npm/issues"
55
55
  },
56
56
  "keywords": [
57
- "ai-agents",
57
+ "ai",
58
+ "agent",
58
59
  "agentic",
59
- "agentic-workflow",
60
- "multi-agent",
61
- "agent-framework",
62
- "ai-development",
60
+ "workflow",
61
+ "framework",
62
+ "engineering",
63
+ "development",
64
+ "coding",
65
+ "context",
66
+ "software",
67
+ "kanban",
68
+ "board",
69
+ "management",
63
70
  "claude",
64
71
  "claude-code",
65
72
  "agents-md",
66
73
  "copilot",
67
- "github-copilot",
68
74
  "codex",
69
- "skills",
70
- "project-management",
71
- "developer-tools",
72
- "cli",
73
75
  "jenga",
74
- "jenga-ai",
75
- "ai-coding",
76
- "ai-coding-agent",
77
- "coding-agent",
78
- "coding-agents",
79
- "ai-workflow",
80
- "coding-workflow",
81
- "development-workflow",
82
- "software-development",
83
- "developer-workflow",
84
- "ai-framework",
85
- "agentic-engineering",
86
- "ai-engineering"
76
+ "jenga-ai"
87
77
  ],
88
78
  "author": {
89
79
  "name": "Samwel Munga",
@@ -1,23 +1,29 @@
1
1
  #!/usr/bin/env bash
2
- # scripts/apply-j-prefix.sh — mechanical j: prefix rename for skill invocation
2
+ # scripts/apply-j-prefix.sh — mechanical j. prefix rename for skill invocation
3
3
  #
4
4
  # Implements the mechanism decided in docs/skill-authoring.md's "Invocation Convention"
5
- # section (E50_S01_T01):
5
+ # section (E50_S01_T01), with the separator corrected from ":" to "." in E50_S07_T01:
6
6
  #
7
7
  # 1. Frontmatter-only rename. For every skills/<name>/SKILL.md, rewrites the
8
- # frontmatter `name:` field from `<name>` to `j:<name>`. Directory names are
9
- # left untouched — ":" is not a Windows-safe filename character, and Claude
10
- # Code's native skill resolution is a literal-string match against the
8
+ # frontmatter `name:` field from `<name>` to `j.<name>`. Directory names are
9
+ # left untouched — neither ":" nor "." is worth putting in a path here, and
10
+ # Claude Code's native skill resolution is a literal-string match against the
11
11
  # discovery-path directory name, independent of frontmatter content (see the
12
12
  # docs section for the full investigation and citation).
13
13
  # 2. Bare-form prose rewrite. For every agents/*.md file, rewrites bare
14
- # "/<name>" invocation mentions in prose to "j:<name>", for every skill name
14
+ # "/<name>" invocation mentions in prose to "j.<name>", for every skill name
15
15
  # discovered in step 1 — longest-name-first, boundary-safe (skill names
16
16
  # contain hyphens, so a naive `\b` regex would misfire — e.g. it would
17
17
  # falsely match "/doc" inside "/doc-sync"; this only rewrites a match when
18
18
  # the character immediately before/after it is not itself an identifier
19
19
  # character: letter, digit, "-", or ":").
20
20
  #
21
+ # Separator history (E50_S07): this script originally emitted "j:<name>". GitHub
22
+ # Copilot CLI validates the frontmatter `name:` VALUE and rejects anything outside
23
+ # [A-Za-z0-9], hyphen, underscore, dot, and space — so the colon made every skill
24
+ # fail to load there. The separator is now "." which is on that allow-list. The
25
+ # namespace intent of E50 is unchanged; only the separator character moved.
26
+ #
21
27
  # This script does NOT remove or disable the old bare "/<name>" form. The
22
28
  # migration decision recorded alongside the mechanism above is "alias" — both
23
29
  # forms keep resolving. This script only ever writes inside skills/ and
@@ -34,8 +40,10 @@
34
40
  # --skills-only Only process skills/*/SKILL.md frontmatter (step 1).
35
41
  # --agents-only Only process agents/*.md prose (step 2).
36
42
  #
37
- # Idempotent: already-migrated skills (name already starts with "j:") are
38
- # silently skipped, not re-applied or reported as an error.
43
+ # Idempotent: already-migrated skills (name already starts with "j.") are
44
+ # silently skipped, not re-applied or reported as an error. Skills still carrying
45
+ # the legacy "j:<name>" form are REPAIRED to "j.<name>" rather than skipped —
46
+ # skipping them would silently leave a Copilot-breaking name in place.
39
47
 
40
48
  set -euo pipefail
41
49
 
@@ -125,10 +133,15 @@ if os.path.isdir(skills_dir):
125
133
 
126
134
  skill_names.append(entry)
127
135
 
128
- if current_name.startswith("j:"):
136
+ if current_name.startswith("j."):
129
137
  continue # already migrated — not an error, nothing to do
130
138
 
131
- if current_name != entry:
139
+ # Legacy "j:<name>" form (emitted by this script before E50_S07_T01).
140
+ # The colon is rejected by GitHub Copilot CLI's skill-name validator, so
141
+ # this is repaired to "j.<name>" rather than skipped as "already migrated".
142
+ base_name = current_name[2:] if current_name.startswith("j:") else current_name
143
+
144
+ if base_name != entry:
132
145
  errors.append(
133
146
  f"{skill_path}: frontmatter name '{current_name}' does not match "
134
147
  f"directory name '{entry}' — skipped for safety"
@@ -138,7 +151,7 @@ if os.path.isdir(skills_dir):
138
151
  if not do_skills:
139
152
  continue
140
153
 
141
- new_line = f"name: j:{entry}\n"
154
+ new_line = f"name: j.{entry}\n"
142
155
  changes.append({
143
156
  "type": "frontmatter",
144
157
  "file": skill_path,
@@ -185,7 +198,7 @@ if do_agents and os.path.isdir(agents_dir):
185
198
  continue # e.g. "/doc" inside "/doc-sync"
186
199
  matches.append((start, end))
187
200
  for start, end in reversed(matches):
188
- text = text[:start] + f"j:{name}" + text[end:]
201
+ text = text[:start] + f"j.{name}" + text[end:]
189
202
  file_changes += 1
190
203
 
191
204
  if file_changes:
@@ -0,0 +1,333 @@
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,21 +1,28 @@
1
1
  ---
2
- name: j:brainstorm
3
- description: Engage the scrum-master agent in a focused planning session to define, refine, or challenge features, improvements, tasks, stories, and epics. The agent asks probing questions, challenges assumptions, and helps shape ideas into actionable backlog items.
2
+ name: j.brainstorm
3
+ description: Polyfill alias of the brainstorm skill under a collision-safe directory name. Identical behavior to /brainstorm — Engage the scrum-master agent in a focused planning session to define, refine, or challenge features, improvements, tasks, stories, and epics. The agent asks probing questions, challenges assumptions, and helps shape ideas into actionable backlog items. Use when the bare /brainstorm form is shadowed by another tool's own built-in command of the same name.
4
4
  keywords:
5
5
  - brainstorm
6
6
  - plan
7
7
  - ideate
8
8
  - feature planning
9
9
  - requirements
10
+ - j-brainstorm
11
+ - polyfill
10
12
  examples:
11
13
  - "let's brainstorm ideas for X"
12
14
  - "I need to plan a new feature"
15
+ - "j-brainstorm"
13
16
  metadata:
14
17
  prefered_agent: scrum-master
15
18
  ---
16
19
 
17
20
  # Brainstorm — Collaborative Planning with the Scrum Master
18
21
 
22
+ This skill is a literal-directory-name duplicate of `skills/brainstorm/`. It exists so that `/j-brainstorm` (and `j.j-brainstorm`) give a guaranteed-unshadowed way to reach the same flow as `/brainstorm`, even if a host tool's own built-in command of the same name would otherwise shadow or override the bare `/brainstorm` alias (Claude Code's native skill resolution is a literal-string, directory-name-based match — see `docs/skill-authoring.md`'s "Invocation Convention").
23
+
24
+ This file is generated/synced by `scripts/generate-j-alias.sh brainstorm` from `skills/brainstorm/SKILL.md` — do not hand-edit it; re-run the generator instead to pick up source changes.
25
+
19
26
  ## Instructions
20
27
 
21
28
  1. **Invoke the `scrum-master` agent** — Pass the user's input directly to it as the opening prompt for the brainstorm session.
@@ -1,21 +1,28 @@
1
1
  ---
2
- name: j:btw
3
- description: Capture a new mission (feature, change, or addition) and fit it into the project's Epic/Story structure, then choose to implement now or defer.
2
+ name: j.btw
3
+ description: Polyfill alias of the btw skill under a collision-safe directory name. Identical behavior to /btw — Capture a new mission (feature, change, or addition) and fit it into the project's Epic/Story structure, then choose to implement now or defer. Use when the bare /btw form is shadowed by another tool's own built-in command of the same name.
4
4
  keywords:
5
5
  - btw
6
6
  - capture
7
7
  - mission
8
8
  - new idea
9
9
  - side task
10
+ - j-btw
11
+ - polyfill
10
12
  examples:
11
13
  - "btw I also need to add X"
12
14
  - "capture this as a new task"
15
+ - "j-btw"
13
16
  metadata:
14
17
  prefered_agent: scrum-master
15
18
  ---
16
19
 
17
20
  # BTW — Capture a Mission
18
21
 
22
+ This skill is a literal-directory-name duplicate of `skills/btw/`. It exists so that `/j-btw` (and `j.j-btw`) give a guaranteed-unshadowed way to reach the same flow as `/btw`, even if a host tool's own built-in command of the same name would otherwise shadow or override the bare `/btw` alias (Claude Code's native skill resolution is a literal-string, directory-name-based match — see `docs/skill-authoring.md`'s "Invocation Convention").
23
+
24
+ This file is generated/synced by `scripts/generate-j-alias.sh btw` from `skills/btw/SKILL.md` — do not hand-edit it; re-run the generator instead to pick up source changes.
25
+
19
26
  ## Instructions
20
27
 
21
28
  1. **Gather mission details** — Ask the user: