@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.
- package/README.md +75 -243
- package/agents/developer.md +5 -5
- package/agents/scrum-master.md +23 -23
- package/agents/tester.md +5 -5
- package/lib/generate-skill-allow-list.js +9 -3
- package/lib/skill-allow-list.json +2 -3
- package/package.json +15 -25
- package/scripts/apply-j-prefix.sh +25 -12
- package/scripts/generate-j-alias.sh +333 -0
- package/skills/{brainstorm → j-brainstorm}/SKILL.md +9 -2
- package/skills/{btw → j-btw}/SKILL.md +9 -2
- package/skills/{clearify → j-clearify}/SKILL.md +9 -2
- package/skills/{close-story → j-close-story}/SKILL.md +17 -10
- package/skills/{close-story → j-close-story}/scripts/check-privatized.sh +2 -2
- package/skills/{close-story → j-close-story}/scripts/check-story-closeable.sh +1 -1
- package/skills/{close-story → j-close-story}/scripts/extract-task-diff-stats.sh +1 -1
- package/skills/{commit → j-commit}/SKILL.md +9 -2
- package/skills/j-continue/SKILL.md +36 -0
- package/skills/{deep-dive → j-deep-dive}/SKILL.md +9 -8
- package/skills/{dev-done → j-dev-done}/SKILL.md +11 -4
- package/skills/{dev-done → j-dev-done}/scripts/classify-commit-outcome.sh +4 -4
- package/skills/{distribute → j-distribute}/SKILL.md +17 -10
- package/skills/{distribute → j-distribute}/scripts/distribute-changes.sh +1 -1
- package/skills/{do → j-do}/SKILL.md +12 -5
- package/skills/{doc → j-doc}/README.md +5 -5
- package/skills/{doc → j-doc}/SKILL.md +15 -8
- package/skills/{doc → j-doc}/authoring-notes.md +1 -1
- package/skills/{doc-sync → j-doc-sync}/SKILL.md +9 -2
- package/skills/{dooo → j-dooo}/SKILL.md +9 -2
- package/skills/j-error/SKILL.md +36 -0
- package/skills/{evaluate → j-evaluate}/SKILL.md +9 -2
- package/skills/j-examplify/SKILL.md +49 -0
- package/skills/{help → j-help}/SKILL.md +9 -2
- package/skills/{idea → j-idea}/SKILL.md +10 -3
- package/skills/{idea → j-idea}/assets/idea_handoff_template.md +1 -1
- package/skills/{improve → j-improve}/SKILL.md +9 -2
- package/skills/j-init/SKILL.md +2 -2
- package/skills/j-jbp/SKILL.md +32 -0
- package/skills/j-lgtm/SKILL.md +28 -0
- package/skills/{pi-plan → j-pi-plan}/SKILL.md +10 -3
- package/skills/{proceed → j-proceed}/SKILL.md +9 -2
- package/skills/{publish → j-publish}/SKILL.md +47 -40
- package/skills/{publish → j-publish}/adapters/droplet.md +1 -1
- package/skills/{publish → j-publish}/adapters/mobile-ios.md +3 -3
- package/skills/{publish → j-publish}/adapters/npm-ci.md +3 -3
- package/skills/{publish → j-publish}/adapters/npm.md +8 -8
- package/skills/{publish → j-publish}/assets/ci-contract.md +2 -2
- package/skills/{publish → j-publish}/schemas/publish.schema.json +1 -1
- package/skills/{publish → j-publish}/scripts/npm_stage_inspect.sh +34 -1
- package/skills/{publish → j-publish}/scripts/npm_stage_pipeline.sh +9 -4
- package/skills/{publish → j-publish}/scripts/publish_deploy.sh +4 -4
- package/skills/{publish → j-publish}/scripts/validate_npm_stage_env.sh +1 -1
- package/skills/{publish → j-publish}/wizards/droplet.md +1 -1
- package/skills/{publish → j-publish}/wizards/mobile-ios.md +1 -1
- package/skills/{publish → j-publish}/wizards/npm-ci.md +1 -1
- package/skills/{publish → j-publish}/wizards/npm.md +1 -1
- package/skills/{reconcile → j-reconcile}/SKILL.md +12 -5
- package/skills/{reconcile → j-reconcile}/scripts/detect-unlinked-code.sh +2 -2
- package/skills/{reconcile → j-reconcile}/scripts/resolve-reconcile-scope.sh +3 -3
- package/skills/{reconcile-origin → j-reconcile-origin}/SKILL.md +13 -6
- package/skills/{redo → j-redo}/SKILL.md +9 -2
- package/skills/{skillify → j-skillify}/SKILL.md +10 -3
- package/skills/{spinoff → j-spinoff}/SKILL.md +9 -2
- package/skills/{status → j-status}/SKILL.md +9 -2
- package/skills/{todo → j-todo}/SKILL.md +10 -3
- package/skills/{todo → j-todo}/assets/todo_handoff_template.md +1 -1
- package/skills/{todo → j-todo}/scripts/add_trivial_task.sh +3 -3
- package/skills/{todo → j-todo}/scripts/update_story_tasks.py +2 -2
- package/skills/{uncharted → j-uncharted}/SKILL.md +35 -28
- package/skills/{uncharted → j-uncharted}/assets/UNDERSTANDING_DOC_TEMPLATE.md +2 -2
- package/skills/{uncharted → j-uncharted}/scripts/detect-dependencies.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/detect-tests.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/directory-triage.sh +3 -3
- package/skills/{uncharted → j-uncharted}/scripts/elicitation-state.sh +3 -3
- package/skills/{uncharted → j-uncharted}/scripts/enumerate-target.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/import-source.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/inspect-provenance.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/resolve-segment-target.sh +5 -5
- package/skills/{uncharted → j-uncharted}/scripts/run-engine.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/validate-proposed-items.sh +2 -2
- package/skills/{uncharted → j-uncharted}/scripts/write-backfilled-epics.sh +1 -1
- package/skills/j-wtf/SKILL.md +27 -0
- package/skills/jenga/SKILL.md +1 -1
- package/skills/jenga-permission-level/SKILL.md +1 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +1 -1
- package/templates/agent-context.md.tpl +10 -10
- package/templates/copilot-instructions.md.tpl +57 -19
- package/skills/continue/SKILL.md +0 -29
- package/skills/error/SKILL.md +0 -29
- package/skills/examplify/SKILL.md +0 -42
- package/skills/init/SKILL.md +0 -155
- package/skills/init/assets/scope-thresholds_template.json +0 -7
- package/skills/init/assets/strategy_stub_template.md +0 -38
- package/skills/init/assets/workflow_template.json +0 -30
- package/skills/init/scripts/apply-project-visibility.sh +0 -176
- package/skills/init/scripts/detect-existing-codebase.sh +0 -166
- package/skills/init/scripts/init.sh +0 -116
- package/skills/jbp/SKILL.md +0 -25
- package/skills/lgtm/SKILL.md +0 -21
- package/skills/skillify/assets/init-new/assets/.gitignore_template +0 -15
- package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +0 -13
- package/skills/skillify/assets/init-new/assets/directory_structure.txt +0 -14
- package/skills/skillify/assets/init-new/assets/test-config_template.json +0 -4
- package/skills/wtf/SKILL.md +0 -20
- /package/skills/{close-story → j-close-story}/scripts/compute-scope-divergence.sh +0 -0
- /package/skills/{close-story → j-close-story}/scripts/extract-diff-stats.sh +0 -0
- /package/skills/{close-story → j-close-story}/scripts/update-task-frontmatter.sh +0 -0
- /package/skills/{commit → j-commit}/assets/user_instructions_template.md +0 -0
- /package/skills/{distribute → j-distribute}/CONFIG_SCHEMA.md +0 -0
- /package/skills/{distribute → j-distribute}/scripts/check-version.sh +0 -0
- /package/skills/{distribute → j-distribute}/scripts/commit-version-bump.sh +0 -0
- /package/skills/{do → j-do}/assets/intent-vs-diff-prompt.md +0 -0
- /package/skills/{do → j-do}/assets/sender_template.json +0 -0
- /package/skills/{doc → j-doc}/assets/path-objectives.yaml +0 -0
- /package/skills/{doc → j-doc}/scripts/resolve_last_update.py +0 -0
- /package/skills/{doc-sync → j-doc-sync}/assets/default_excludes.txt +0 -0
- /package/skills/{doc-sync → j-doc-sync}/assets/doc_targets.md +0 -0
- /package/skills/{evaluate → j-evaluate}/assets/evaluation_invokation_template.yml +0 -0
- /package/skills/{evaluate → j-evaluate}/assets/evaluation_rapport_template.md +0 -0
- /package/skills/{idea → j-idea}/assets/idea_template.md +0 -0
- /package/skills/{pi-plan → j-pi-plan}/assets/epic.json +0 -0
- /package/skills/{pi-plan → j-pi-plan}/assets/story_template.md +0 -0
- /package/skills/{publish → j-publish}/assets/ExportOptions.plist.template +0 -0
- /package/skills/{publish → j-publish}/assets/ownership-matrix.md +0 -0
- /package/skills/{publish → j-publish}/assets/publish.example.json +0 -0
- /package/skills/{publish → j-publish}/assets/publish.example.npm-ci.json +0 -0
- /package/skills/{publish → j-publish}/assets/publish.example.npm.json +0 -0
- /package/skills/{publish → j-publish}/assets/secrets-guide.md +0 -0
- /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-minimal.json +0 -0
- /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-empty-secrets.json +0 -0
- /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-workflow-path.json +0 -0
- /package/skills/{publish → j-publish}/scripts/check_target_config.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/droplet_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/finalize_changelog.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/generate_release_notes.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/ios_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/npm_ci_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/npm_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/publish_common.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/reconcile_tags.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/run_gates.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/setup_wizard.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/show_history.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/suggest_semver_bump.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_config.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_droplet_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_ios_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_npm_ci_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_npm_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/write_ledger_entry.sh +0 -0
- /package/skills/{reconcile → j-reconcile}/assets/report_format.md +0 -0
- /package/skills/{reconcile-origin → j-reconcile-origin}/scripts/reconcile-origin.sh +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/SKILL.md +0 -0
- /package/skills/{init → j-skillify/assets/init-new}/assets/.gitignore_template +0 -0
- /package/skills/{init → j-skillify/assets/init-new}/assets/PROJECT_SUMMARY_template.md +0 -0
- /package/skills/{init → j-skillify/assets/init-new}/assets/directory_structure.txt +0 -0
- /package/skills/{init → j-skillify/assets/init-new}/assets/test-config_template.json +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/assets/workflow_template.json +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/scripts/init.sh +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-old/SKILL.md +0 -0
- /package/skills/{status → j-status}/assets/output_format.md +0 -0
- /package/skills/{todo → j-todo}/assets/todo_template.md +0 -0
- /package/skills/{uncharted → j-uncharted}/assets/SEGMENT_PROPOSAL_TEMPLATE.md +0 -0
- /package/skills/{uncharted → j-uncharted}/scripts/apply-subsystem-cap.sh +0 -0
- /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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
59
|
-
//
|
|
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
|
-
|
|
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-
|
|
3
|
-
"skill_count":
|
|
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": "
|
|
4
|
-
"description": "
|
|
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
|
|
57
|
+
"ai",
|
|
58
|
+
"agent",
|
|
58
59
|
"agentic",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
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
|
|
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
|
|
9
|
-
# left untouched — ":" is
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|