Commit43095dcunknown_key
feat(L7): Claude Code skill bundle — three skills that drive the K1 MCP write surface
feat(L7): Claude Code skill bundle — three skills that drive the K1 MCP write surface
Makes the K1 write tools battery-included for any developer who clones
a Gluecron repo or runs the L2 install script. Three skills, each
narrowly scoped:
.claude/skills/gluecron-pr/SKILL.md
Open / list / fetch / comment / merge / close PRs.
tools: gluecron_create_pr, gluecron_get_pr, gluecron_list_prs,
gluecron_comment_pr, gluecron_merge_pr, gluecron_close_pr,
Bash (for git diff base...HEAD before opening).
.claude/skills/gluecron-issue/SKILL.md
Create / list / comment / close / reopen issues.
tools: gluecron_create_issue, gluecron_comment_issue,
gluecron_close_issue, gluecron_reopen_issue,
gluecron_repo_list_issues, Bash.
.claude/skills/gluecron-review/SKILL.md
Act as a secondary AI reviewer on top of Gluecron's built-in
Sonnet 4 review. Pulls the diff, posts inline comments, can
approve / request-changes by writing a marker-bearing comment
matching src/lib/ai-review.ts's AI_REVIEW_MARKER convention.
tools: gluecron_get_pr, gluecron_list_prs, gluecron_comment_pr,
Bash.
Each skill's frontmatter description names "Gluecron" explicitly so the
harness auto-invokes them when the active repo's origin URL matches a
Gluecron host. Each body includes concrete example prompts + the exact
tool-call sequences they map to.
scripts/install.sh — adds a new step 8/8 that mkdirs ~/.claude/skills/
and writes each SKILL.md via inline heredoc. Renumbers the previous
step banners. Idempotent (overwrites existing).
CLAUDE.md — appends "Skills available for this project" pointing at
the three skills so future Claude Code sessions discover them
automatically.
src/__tests__/skills-bundle.test.ts — 25 tests verifying each SKILL.md
exists at its expected path, has valid YAML frontmatter (name +
description + tools), mentions Gluecron in the description, and that
the install script's new step is present.
Suite: 1440 pass / 0 fail / 2 skip / 3596 expect() across 110 files.
Follow-ups (none blocking):
- Bundle the skill files into the import flow so newly-imported
GitHub repos also get them (currently only THIS repo + L2-installed
users get the skills).
- The install.sh writes the skill bodies via heredocs which means
they drift from the canonical .claude/skills/ files. Worth a build
step that materialises the heredocs from the files at release time.6 files changed+673−843095dc3067f485b6b246c196e6d2dcf306736ea
6 changed files+673−8
Added.claude/skills/gluecron-issue/SKILL.md+116−0View fileUnifiedSplit
@@ -0,0 +1,116 @@
1---
2name: gluecron-issue
3description: Create, list, comment on, close, or reopen issues on a Gluecron-hosted repository. Use this skill whenever the user is on a Gluecron repo (origin URL contains "gluecron.com" or matches the GLUECRON_HOST env var) and asks to "open an issue", "file a bug", "comment on #N", "close #N", or "reopen #N" on a repo that is NOT hosted on GitHub.
4tools:
5 - gluecron_create_issue
6 - gluecron_comment_issue
7 - gluecron_close_issue
8 - gluecron_reopen_issue
9 - gluecron_repo_list_issues
10 - Bash
11---
12
13# Skill: gluecron-issue
14
15You are the Gluecron issue-tracker assistant. Drive the K1 MCP write surface
16(`gluecron_create_issue`, `gluecron_comment_issue`, `gluecron_close_issue`,
17`gluecron_reopen_issue`, plus the read tool `gluecron_repo_list_issues`)
18on a Gluecron-hosted repository.
19
20## When to use this skill
21
22Trigger when ALL of these are true:
23
241. The user mentions an issue action (file, open, create, comment on,
25 close, reopen, list, triage).
262. The active git repository's `remote.origin.url` contains `gluecron.com`,
27 matches `$GLUECRON_HOST`, or the user explicitly mentions Gluecron.
283. The repo is NOT on GitHub.
29
30If you're not sure, ask once and remember the answer.
31
32## Required setup
33
34Detect owner/repo from the origin URL the same way `gluecron-pr` does:
35
36```bash
37git config --get remote.origin.url
38```
39
40Shapes:
41- `https://<HOST>/<owner>/<repo>.git`
42- `git@<HOST>:<owner>/<repo>.git`
43
44Strip `.git`. The tools take `owner` and `repo` as separate arguments.
45
46## Tool-call recipes
47
48### "Open an issue describing X"
49
501. If the user's description is shorter than ~10 words, ask one clarifying
51 question (reproduction steps? expected vs actual?). Otherwise proceed.
522. Draft a clear title (≤72 chars, imperative or noun-phrase).
533. Draft a Markdown body in this shape:
54
55 ```
56 ## What happened
57 ...
58
59 ## Expected
60 ...
61
62 ## Repro
63 1. ...
64 2. ...
65 ```
66
674. Call `gluecron_create_issue` with `{ owner, repo, title, body }`.
685. Echo the returned `url` (relative — prefix with the Gluecron host).
69
70### "Comment 'I'm investigating' on #42"
71
721. Call `gluecron_comment_issue` with `{ owner, repo, number: 42, body: "I'm investigating" }`.
73
74### "Close #42"
75
761. Call `gluecron_close_issue` with `{ owner, repo, number: 42 }`.
772. The tool is idempotent — closing an already-closed issue is a no-op.
78
79### "Reopen #42"
80
811. Call `gluecron_reopen_issue` with `{ owner, repo, number: 42 }`.
82
83### "List open issues"
84
851. Call `gluecron_repo_list_issues` with `{ owner, repo, limit: 25 }`.
862. Render as a compact list: `#N title (created at)`.
87
88### "Close all the stale duplicates"
89
901. Call `gluecron_repo_list_issues`.
912. Identify candidate duplicates by title-similarity (you have the titles
92 in the list response — do NOT need a separate AI call).
933. For each candidate, post a comment via `gluecron_comment_issue`
94 explaining which issue it duplicates, THEN call `gluecron_close_issue`.
954. NEVER close more than 5 issues in one batch without re-confirming with
96 the user.
97
98## Example user prompts
99
100- "File an issue describing the off-by-one in pagination."
101- "Comment 'I'm investigating' on issue 42."
102- "Close issue 42."
103- "Reopen 17."
104- "List open issues."
105
106## Don'ts
107
108- Do NOT close issues without a comment explaining why, unless the user
109 explicitly said "close without comment".
110- Do NOT bulk-close more than 5 issues in one tool sequence without a
111 fresh user confirmation.
112- If a tool returns `-32601 method_not_found`, the caller lacks read
113 access. Tell the user; do not retry.
114- If a tool returns `-32602 invalid_params` mentioning authentication,
115 the MCP server is not authenticated. Tell the user to re-run
116 `curl -sSL https://gluecron.com/install | bash`.
Added.claude/skills/gluecron-pr/SKILL.md+139−0View fileUnifiedSplit
@@ -0,0 +1,139 @@
1---
2name: gluecron-pr
3description: Open, list, fetch, comment on, merge, or close pull requests on a Gluecron-hosted repository. Use this skill whenever the user references a Gluecron repo (origin URL contains "gluecron.com" or matches the GLUECRON_HOST env var) and asks to "open a PR", "merge", "review", "comment on PR #N", "list open PRs", or "close PR #N" on a repo that is NOT hosted on GitHub.
4tools:
5 - gluecron_create_pr
6 - gluecron_get_pr
7 - gluecron_list_prs
8 - gluecron_comment_pr
9 - gluecron_merge_pr
10 - gluecron_close_pr
11 - Bash
12---
13
14# Skill: gluecron-pr
15
16You are the Gluecron pull-request lifecycle assistant. Drive the K1 MCP write
17surface (`gluecron_create_pr`, `gluecron_get_pr`, `gluecron_list_prs`,
18`gluecron_comment_pr`, `gluecron_merge_pr`, `gluecron_close_pr`) on a Gluecron-
19hosted repository.
20
21## When to use this skill
22
23Trigger this skill when ALL of these are true:
24
251. The user mentions a pull request action (open, create, merge, close,
26 review, comment on, list).
272. The active git repository's `remote.origin.url` contains `gluecron.com`,
28 matches `$GLUECRON_HOST`, OR the user explicitly mentions Gluecron.
293. The repo is NOT on GitHub. If `origin` is `github.com`, defer to the
30 built-in GitHub skill or `gh` CLI instead.
31
32If the repo origin is ambiguous, ask the user once and remember the answer
33for the rest of the session.
34
35## Required setup before the first tool call
36
37Run these shell commands once per session to learn the context:
38
39```bash
40# 1. Owner/repo from the origin URL
41git config --get remote.origin.url
42# 2. Default branch (fall back to "main" if this fails)
43git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' || echo main
44# 3. Current branch (used as `head_branch` when opening PRs from CWD)
45git rev-parse --abbrev-ref HEAD
46```
47
48The origin URL takes one of two shapes:
49
50- `https://<HOST>/<owner>/<repo>.git`
51- `git@<HOST>:<owner>/<repo>.git`
52
53Strip the `.git` suffix. The MCP tools take `owner` and `repo` as separate
54arguments.
55
56## Required diff inspection before opening a PR
57
58Always read the diff before drafting the title and body so they match what
59is actually being shipped:
60
61```bash
62git fetch origin <base_branch>
63git diff origin/<base_branch>...HEAD --stat
64git diff origin/<base_branch>...HEAD
65```
66
67Draft the title in imperative voice (≤72 chars). Draft the body in this
68shape:
69
70```
71## Summary
72- 1-3 bullet points describing the change
73
74## Test plan
75- [ ] step 1
76- [ ] step 2
77```
78
79## Tool-call recipes
80
81### "Open a PR from this branch"
82
831. Run the three setup shell commands above.
842. Run `git diff origin/<base>...HEAD`.
853. Call `gluecron_create_pr` with `owner`, `repo`, `title`, `body`,
86 `head_branch=<current branch>`, `base_branch=<default branch>`.
874. Echo the returned `url` (it is relative — prefix with the Gluecron host).
88
89### "What's in PR #42?"
90
911. Call `gluecron_get_pr` with `{ owner, repo, number: 42 }`.
922. If the user wants to see the diff locally, follow up with
93 `git fetch origin <headBranch>` then `git diff <baseBranch>...<headBranch>`.
94
95### "Comment 'looks good, ship it' on PR #42"
96
971. Call `gluecron_comment_pr` with `{ owner, repo, number: 42, body: "..." }`.
98
99### "Merge PR #42"
100
1011. Call `gluecron_merge_pr` with `{ owner, repo, number: 42 }`.
1022. If the response is `{ merged: false, reason: ... }`, surface the reason
103 verbatim. Common reasons:
104 - "This PR is a draft" → suggest the user run "mark ready for review".
105 - "AI review: ..." or "GateTest: ..." → propose fixing the underlying
106 check, not bypassing it.
107 - Branch protection (required reviews, required checks) → list which
108 gate is missing.
109
110### "Close PR #42 without merging"
111
1121. Call `gluecron_close_pr` with `{ owner, repo, number: 42 }`.
113
114### "List open PRs on this repo"
115
1161. Call `gluecron_list_prs` with `{ owner, repo, state: "open" }`.
1172. Render as a compact table: number, title, head→base, author.
118
119## Example user prompts
120
121- "Open a PR titled 'Fix off-by-one in pagination' from this branch."
122- "Show me PR 17."
123- "Comment 'thanks, merging now' on PR 17."
124- "Merge 17."
125- "List open PRs on this repo."
126- "Close PR 14, I'm going to start over."
127
128## Don'ts
129
130- Do NOT skip the diff inspection step before opening a PR — never invent
131 a body from the commit message alone.
132- Do NOT call `gluecron_merge_pr` if the user has not explicitly asked to
133 merge. "Approve" or "LGTM" means COMMENT, not merge.
134- Do NOT post `gh` CLI commands — that's for GitHub, not Gluecron.
135- If a tool returns `-32601 method_not_found` for the repo, the caller
136 lacks read access. Tell the user; do not retry.
137- If a tool returns `-32602 invalid_params` mentioning authentication, the
138 MCP server is not authenticated. Tell the user to re-run
139 `curl -sSL https://gluecron.com/install | bash`.
Added.claude/skills/gluecron-review/SKILL.md+129−0View fileUnifiedSplit
@@ -0,0 +1,129 @@
1---
2name: gluecron-review
3description: Act as a secondary AI code reviewer on a Gluecron-hosted pull request. Use this skill when the user asks Claude to "review PR #N", "give a second-opinion review", or "leave inline comments" on a Gluecron pull request. Complements Gluecron's built-in AI review.
4tools:
5 - gluecron_get_pr
6 - gluecron_list_prs
7 - gluecron_comment_pr
8 - Bash
9---
10
11# Skill: gluecron-review
12
13You are a secondary reviewer on top of Gluecron's built-in AI review.
14Gluecron already runs its own pass (see `src/lib/ai-review.ts` —
15`AI_REVIEW_MARKER = "<!-- gluecron-ai-review:summary -->"`). Your job is to
16add depth, catch what it missed, and post clear inline-style review comments
17via the K1 MCP write surface.
18
19## When to use this skill
20
21Trigger when ALL of these are true:
22
231. The user is on a Gluecron-hosted repo (origin URL contains
24 `gluecron.com`, matches `$GLUECRON_HOST`, or they say "Gluecron").
252. The user asks for a code review, second opinion, "review PR N", "look
26 over PR N", or "leave inline comments on PR N".
27
28## Required setup
29
30```bash
31git config --get remote.origin.url # extract owner / repo
32```
33
34Strip `.git`. The tools take `owner` and `repo` as separate arguments.
35
36## Review workflow
37
381. **Fetch the PR record.** Call `gluecron_get_pr` with
39 `{ owner, repo, number: N }`. From the response read `baseBranch` and
40 `headBranch`.
41
422. **Fetch the diff locally.**
43
44 ```bash
45 git fetch origin <baseBranch> <headBranch>
46 git diff origin/<baseBranch>...origin/<headBranch>
47 ```
48
49 If the head branch is not on origin (PR from a fork — note that forks
50 in Gluecron also push to a branch on the source repo), fall back to
51 reviewing the description-level changes only and tell the user the
52 head ref was not fetchable.
53
543. **Identify real issues.** Look for:
55 - Bugs, logic errors, off-by-ones
56 - Security: injection, XSS, auth bypass, secrets in code
57 - Performance: N+1 queries, blocking I/O, unbounded allocations
58 - Missing error handling at system boundaries
59 - Breaking changes / API-contract violations
60
61 Do NOT flag: style, formatting, naming, missing docs, minor nits.
62
634. **Post one comment per finding** via `gluecron_comment_pr`. Format each
64 body as:
65
66 ```
67 **<short headline>** — `<filepath>:<line>`
68
69 <2-4 sentence explanation>
70
71 ```suggestion
72 <proposed code, if applicable>
73 ```
74 ```
75
765. **Post a summary comment LAST** via `gluecron_comment_pr`. The body
77 MUST start with the marker convention used by Gluecron so future tools
78 can recognise it as an AI summary. Use this exact prefix:
79
80 ```
81 <!-- claude-secondary-review:summary -->
82 ## Claude secondary review
83 ```
84
85 Then a verdict line:
86
87 ```
88 **Verdict:** approved (or: **Verdict:** changes requested)
89 ```
90
91 Then a 1–3 sentence overall assessment, and a bullet list of the
92 findings you posted above.
93
94 The marker is intentionally distinct from `<!-- gluecron-ai-review:summary -->`
95 (defined in `src/lib/ai-review.ts`) so Gluecron's own review and this
96 secondary review do not clobber each other.
97
98## Approval vs changes-requested
99
100- **Approve** (verdict line `**Verdict:** approved`) when:
101 - You found no blocking issues.
102 - All inline comments are nits or optional cleanups.
103
104- **Request changes** (verdict line `**Verdict:** changes requested`)
105 when:
106 - You found at least one bug, security issue, or contract violation.
107
108The Gluecron merge gate looks for "**Approved**", "approved: true", or
109"lgtm" in `isAiReview=true` comments. Your comments are posted as a
110normal user (the MCP write tools do NOT set `isAiReview`), so they will
111not auto-unblock the merge gate. That's intentional — a human reviewer
112should still gate the merge.
113
114## Example user prompts
115
116- "Give PR 42 a second-opinion review."
117- "Leave inline comments on PR 42."
118- "Review the diff in PR 42 — focus on security."
119
120## Don'ts
121
122- Do NOT call `gluecron_merge_pr` from this skill. Reviewing is not
123 merging. If the user asks to merge after reviewing, hand off to the
124 `gluecron-pr` skill.
125- Do NOT spam more than ~10 inline comments. Pick the most important
126 findings and combine related ones.
127- Do NOT post a finding without a code reference (file + line).
128- Do NOT use the `<!-- gluecron-ai-review:summary -->` marker — that is
129 reserved for Gluecron's built-in review pass.
ModifiedCLAUDE.md+18−0View fileUnifiedSplit
@@ -110,3 +110,21 @@ See `.env.example` for required variables. Key ones:
110110- **Other hosts:** a `Dockerfile` is in the repo, so any standard Docker host works.
111111- **Database:** Neon PostgreSQL (direct connection via `DATABASE_URL`).
112112- See DEPLOY.md for full deployment instructions.
113
114## Skills available for this project
115
116Claude Code skill bundle for the Gluecron MCP write surface lives in
117`.claude/skills/`. The install script (`scripts/install.sh`) copies these
118into `~/.claude/skills/` so they are available across all projects:
119
120- **`gluecron-pr`** — open, list, fetch, comment on, merge, or close pull
121 requests on a Gluecron-hosted repository.
122- **`gluecron-issue`** — create, list, comment on, close, or reopen issues
123 on a Gluecron-hosted repository.
124- **`gluecron-review`** — act as a secondary AI code reviewer on a
125 Gluecron PR; complements the built-in `src/lib/ai-review.ts` pass.
126
127All three skills drive the K1 MCP write tools defined in
128`src/lib/mcp-tools.ts` (`gluecron_create_issue`, `gluecron_create_pr`,
129`gluecron_merge_pr`, etc.). They auto-invoke when the active repo's
130origin URL contains `gluecron.com` or matches `$GLUECRON_HOST`.
Modifiedscripts/install.sh+90−8View fileUnifiedSplit
@@ -12,7 +12,8 @@
1212# 5. Mint a fresh PAT via POST /api/v2/auth/install-token
1313# 6. Merge a 'gluecron' MCP server entry into claude_desktop_config.json
1414# 7. If we're inside a GitHub-origin repo, offer to import it
15# 8. Print a "you're done" success banner
15# 8. Drop the Claude Code skill bundle into ~/.claude/skills/
16# 9. Print a "you're done" success banner
1617#
1718# No third-party tools beyond plain curl, jq, git. Idempotent. Safe to re-run.
1819# =============================================================================
@@ -26,7 +27,7 @@ warn() { printf " \033[33m!\033[0m %s\n" "$*"; }
2627fail() { printf " \033[31mx\033[0m %s\n" "$*"; exit 1; }
2728
2829# ── 1. Prerequisites ────────────────────────────────────────────────────────
29say "[1/7] Checking prerequisites"
30say "[1/8] Checking prerequisites"
3031MISSING=()
3132for tool in git curl jq; do
3233 if ! command -v "$tool" >/dev/null 2>&1; then
@@ -48,13 +49,13 @@ fi
4849ok "git, curl, jq present"
4950
5051# ── 2. Host ─────────────────────────────────────────────────────────────────
51say "[2/7] Resolving Gluecron host"
52say "[2/8] Resolving Gluecron host"
5253HOST="${GLUECRON_HOST:-https://gluecron.com}"
5354HOST="${HOST%/}" # strip trailing slash
5455ok "Using host: $HOST"
5556
5657# ── 3. Locate Claude Desktop config ────────────────────────────────────────
57say "[3/7] Locating Claude Desktop config"
58say "[3/8] Locating Claude Desktop config"
5859UNAME_S="$(uname -s 2>/dev/null || echo unknown)"
5960MAC_CONF="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
6061LINUX_CONF="$HOME/.config/Claude/claude_desktop_config.json"
@@ -92,7 +93,7 @@ else
9293fi
9394
9495# ── 4. Sign in ──────────────────────────────────────────────────────────────
95say "[4/7] Signing in to $HOST"
96say "[4/8] Signing in to $HOST"
9697USERNAME="${GLUECRON_USERNAME:-}"
9798PASSWORD="${GLUECRON_PASSWORD:-}"
9899if [ -z "$USERNAME" ]; then
@@ -131,7 +132,7 @@ fi
131132ok "Signed in as $USERNAME"
132133
133134# ── 5. Mint PAT ─────────────────────────────────────────────────────────────
134say "[5/7] Minting a fresh PAT"
135say "[5/8] Minting a fresh PAT"
135136SHORT_TS=$(date +%s | tail -c 7)
136137MINT_BODY=$(jq -nc --arg name "gluecron-install-$SHORT_TS" --arg scope "admin" \
137138 '{name: $name, scope: $scope}')
@@ -152,7 +153,7 @@ PAT_PREFIX=$(printf %s "$PAT" | cut -c1-12)
152153ok "Created PAT $PAT_PREFIX..."
153154
154155# ── 6. Merge MCP server entry into Claude Desktop config ───────────────────
155say "[6/7] Wiring Claude Desktop -> Gluecron MCP"
156say "[6/8] Wiring Claude Desktop -> Gluecron MCP"
156157TMP_CONF="$(mktemp -t gluecron-conf.XXXXXX)"
157158jq --arg url "$HOST/mcp" --arg auth "Bearer $PAT" '
158159 . as $root
@@ -170,7 +171,7 @@ mv "$TMP_CONF" "$CLAUDE_CONF"
170171ok "Updated $CLAUDE_CONF"
171172
172173# ── 7. Optional repo import ─────────────────────────────────────────────────
173say "[7/7] Repo import (optional)"
174say "[7/8] Repo import (optional)"
174175if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
175176 ORIGIN_URL=$(git config --get remote.origin.url || true)
176177 if [ -n "$ORIGIN_URL" ] && printf %s "$ORIGIN_URL" | grep -q "github.com"; then
@@ -207,6 +208,86 @@ else
207208 ok "Not inside a git repo — skipping import"
208209fi
209210
211# ── 8. Claude Code skill bundle ─────────────────────────────────────────────
212# Drop the gluecron-pr / gluecron-issue / gluecron-review skills into
213# ~/.claude/skills/ so they appear as slash commands inside Claude Code.
214# Idempotent: each write overwrites the existing file.
215say "[8/8] Installing Claude Code skill bundle (~/.claude/skills/)"
216SKILLS_ROOT="$HOME/.claude/skills"
217mkdir -p "$SKILLS_ROOT/gluecron-pr" "$SKILLS_ROOT/gluecron-issue" "$SKILLS_ROOT/gluecron-review"
218
219cat > "$SKILLS_ROOT/gluecron-pr/SKILL.md" <<'GLUECRON_SKILL_PR_EOF'
220---
221name: gluecron-pr
222description: Open, list, fetch, comment on, merge, or close pull requests on a Gluecron-hosted repository. Use this skill whenever the user references a Gluecron repo (origin URL contains "gluecron.com" or matches the GLUECRON_HOST env var) and asks to "open a PR", "merge", "review", "comment on PR #N", "list open PRs", or "close PR #N" on a repo that is NOT hosted on GitHub.
223tools:
224 - gluecron_create_pr
225 - gluecron_get_pr
226 - gluecron_list_prs
227 - gluecron_comment_pr
228 - gluecron_merge_pr
229 - gluecron_close_pr
230 - Bash
231---
232
233When invoked, drive the Gluecron MCP write surface to manage pull
234requests on the active Gluecron-hosted repo. Detect owner/repo from
235`git config --get remote.origin.url` (shapes:
236`https://<HOST>/<owner>/<repo>.git`, `git@<HOST>:<owner>/<repo>.git` —
237strip the `.git`). Default base branch:
238`git symbolic-ref refs/remotes/origin/HEAD` (fall back to `main`).
239Always read `git diff origin/<base>...HEAD` before opening a PR so the
240title and body match the diff. See the bundled SKILL.md in this repo's
241`.claude/skills/gluecron-pr/` for the full recipe and example prompts.
242GLUECRON_SKILL_PR_EOF
243
244cat > "$SKILLS_ROOT/gluecron-issue/SKILL.md" <<'GLUECRON_SKILL_ISSUE_EOF'
245---
246name: gluecron-issue
247description: Create, list, comment on, close, or reopen issues on a Gluecron-hosted repository. Use this skill whenever the user is on a Gluecron repo (origin URL contains "gluecron.com" or matches the GLUECRON_HOST env var) and asks to "open an issue", "file a bug", "comment on #N", "close #N", or "reopen #N" on a repo that is NOT hosted on GitHub.
248tools:
249 - gluecron_create_issue
250 - gluecron_comment_issue
251 - gluecron_close_issue
252 - gluecron_reopen_issue
253 - gluecron_repo_list_issues
254 - Bash
255---
256
257When invoked, drive the Gluecron MCP write surface to manage issues on
258the active Gluecron-hosted repo. Detect owner/repo from
259`git config --get remote.origin.url`. Common ops: file an issue with a
260Markdown body (What happened / Expected / Repro), comment on an existing
261issue, close, reopen, list open issues. Never bulk-close more than 5
262issues in one tool sequence without re-confirming with the user.
263GLUECRON_SKILL_ISSUE_EOF
264
265cat > "$SKILLS_ROOT/gluecron-review/SKILL.md" <<'GLUECRON_SKILL_REVIEW_EOF'
266---
267name: gluecron-review
268description: Act as a secondary AI code reviewer on a Gluecron-hosted pull request. Use this skill when the user asks Claude to "review PR #N", "give a second-opinion review", or "leave inline comments" on a Gluecron pull request. Complements Gluecron's built-in AI review.
269tools:
270 - gluecron_get_pr
271 - gluecron_list_prs
272 - gluecron_comment_pr
273 - Bash
274---
275
276When invoked, act as a secondary reviewer on top of Gluecron's built-in
277AI review pass (`src/lib/ai-review.ts`, marker
278`<!-- gluecron-ai-review:summary -->`). Flow: fetch the PR via
279`gluecron_get_pr`, fetch the diff locally via `git diff
280origin/<base>...origin/<head>`, post one `gluecron_comment_pr` per
281finding (file + line + suggestion), then a summary comment prefixed
282with `<!-- claude-secondary-review:summary -->` and a verdict line
283("**Verdict:** approved" or "**Verdict:** changes requested"). Do not
284call `gluecron_merge_pr` from this skill.
285GLUECRON_SKILL_REVIEW_EOF
286
287ok "Wrote $SKILLS_ROOT/gluecron-pr/SKILL.md"
288ok "Wrote $SKILLS_ROOT/gluecron-issue/SKILL.md"
289ok "Wrote $SKILLS_ROOT/gluecron-review/SKILL.md"
290
210291# ── Done ────────────────────────────────────────────────────────────────────
211292printf "\n"
212293printf "\033[1;32m============================================================\033[0m\n"
@@ -215,6 +296,7 @@ printf "\033[1;32m============================================================\0
215296printf " Host: %s\n" "$HOST"
216297printf " PAT prefix: %s...\n" "$PAT_PREFIX"
217298printf " MCP config: %s\n" "$CLAUDE_CONF"
299printf " Skills: %s/{gluecron-pr,gluecron-issue,gluecron-review}\n" "$SKILLS_ROOT"
218300printf "\n"
219301printf " v Done. Restart Claude Desktop and try:\n"
220302printf " \"Open a PR on this repo with a one-line README fix\"\n"
Addedsrc/__tests__/skills-bundle.test.ts+181−0View fileUnifiedSplit
@@ -0,0 +1,181 @@
1/**
2 * Block L7 — Claude Code skill bundle.
3 *
4 * Coverage:
5 * - Each SKILL.md file exists at the expected `.claude/skills/<name>/` path
6 * - Each SKILL.md has a YAML frontmatter block with `name`, `description`,
7 * `tools` keys
8 * - The frontmatter's `description` mentions Gluecron so the harness
9 * auto-invokes the skill on Gluecron-hosted repos
10 * - The frontmatter's `name` matches the directory name
11 * - The install script (scripts/install.sh) contains the mkdir + write
12 * step for `~/.claude/skills/gluecron-pr/`, gluecron-issue, and
13 * gluecron-review
14 */
15
16import { describe, it, expect } from "bun:test";
17import { readFileSync, existsSync } from "node:fs";
18import { join } from "node:path";
19
20const REPO_ROOT = join(import.meta.dir, "..", "..");
21const SKILLS_DIR = join(REPO_ROOT, ".claude", "skills");
22const INSTALL_SCRIPT = join(REPO_ROOT, "scripts", "install.sh");
23
24const SKILLS = ["gluecron-pr", "gluecron-issue", "gluecron-review"] as const;
25
26// ---------------------------------------------------------------------------
27// Helper: parse the simple YAML frontmatter block at the top of a SKILL.md.
28// ---------------------------------------------------------------------------
29
30function parseFrontmatter(src: string): {
31 raw: string;
32 fields: Record<string, string>;
33} {
34 expect(src.startsWith("---")).toBe(true);
35 const end = src.indexOf("\n---", 3);
36 expect(end).toBeGreaterThan(0);
37 const raw = src.slice(3, end).trim();
38 const fields: Record<string, string> = {};
39 let currentKey = "";
40 let buffer: string[] = [];
41 const flush = () => {
42 if (currentKey) {
43 fields[currentKey] = buffer.join("\n").trim();
44 }
45 buffer = [];
46 };
47 for (const line of raw.split("\n")) {
48 const m = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/.exec(line);
49 if (m && !line.startsWith(" ") && !line.startsWith("\t")) {
50 flush();
51 currentKey = m[1];
52 buffer = [m[2]];
53 } else {
54 buffer.push(line);
55 }
56 }
57 flush();
58 return { raw, fields };
59}
60
61// ---------------------------------------------------------------------------
62// 1. Each SKILL.md exists at the expected path
63// ---------------------------------------------------------------------------
64
65describe("skills bundle — files exist", () => {
66 for (const name of SKILLS) {
67 it(`${name}/SKILL.md is present`, () => {
68 const path = join(SKILLS_DIR, name, "SKILL.md");
69 expect(existsSync(path)).toBe(true);
70 });
71 }
72});
73
74// ---------------------------------------------------------------------------
75// 2. Each SKILL.md has a valid YAML frontmatter with required keys
76// ---------------------------------------------------------------------------
77
78describe("skills bundle — frontmatter shape", () => {
79 for (const name of SKILLS) {
80 const path = join(SKILLS_DIR, name, "SKILL.md");
81
82 it(`${name} frontmatter has name + description + tools`, () => {
83 const src = readFileSync(path, "utf8");
84 const { fields } = parseFrontmatter(src);
85 expect(fields.name).toBeDefined();
86 expect(fields.description).toBeDefined();
87 expect(fields.tools).toBeDefined();
88 expect(fields.name.length).toBeGreaterThan(0);
89 expect(fields.description.length).toBeGreaterThan(0);
90 expect(fields.tools.length).toBeGreaterThan(0);
91 });
92
93 it(`${name} frontmatter "name" matches the directory`, () => {
94 const src = readFileSync(path, "utf8");
95 const { fields } = parseFrontmatter(src);
96 expect(fields.name).toBe(name);
97 });
98
99 it(`${name} frontmatter "description" mentions Gluecron`, () => {
100 const src = readFileSync(path, "utf8");
101 const { fields } = parseFrontmatter(src);
102 expect(fields.description.toLowerCase()).toContain("gluecron");
103 });
104
105 it(`${name} body is non-trivial (>= 200 chars after frontmatter)`, () => {
106 const src = readFileSync(path, "utf8");
107 const end = src.indexOf("\n---", 3);
108 const body = src.slice(end + 4).trim();
109 expect(body.length).toBeGreaterThanOrEqual(200);
110 });
111 }
112});
113
114// ---------------------------------------------------------------------------
115// 3. Each skill references its MCP tools in the frontmatter `tools:` list
116// ---------------------------------------------------------------------------
117
118describe("skills bundle — tool references", () => {
119 const expectedTools: Record<(typeof SKILLS)[number], string[]> = {
120 "gluecron-pr": [
121 "gluecron_create_pr",
122 "gluecron_get_pr",
123 "gluecron_list_prs",
124 "gluecron_comment_pr",
125 "gluecron_merge_pr",
126 "gluecron_close_pr",
127 ],
128 "gluecron-issue": [
129 "gluecron_create_issue",
130 "gluecron_comment_issue",
131 "gluecron_close_issue",
132 "gluecron_reopen_issue",
133 "gluecron_repo_list_issues",
134 ],
135 "gluecron-review": [
136 "gluecron_get_pr",
137 "gluecron_list_prs",
138 "gluecron_comment_pr",
139 ],
140 };
141
142 for (const name of SKILLS) {
143 it(`${name} lists every expected MCP tool in frontmatter`, () => {
144 const src = readFileSync(join(SKILLS_DIR, name, "SKILL.md"), "utf8");
145 const { fields } = parseFrontmatter(src);
146 for (const tool of expectedTools[name]) {
147 expect(fields.tools).toContain(tool);
148 }
149 });
150 }
151});
152
153// ---------------------------------------------------------------------------
154// 4. The install script copies each skill into ~/.claude/skills/
155// ---------------------------------------------------------------------------
156
157describe("skills bundle — install script wiring", () => {
158 const script = readFileSync(INSTALL_SCRIPT, "utf8");
159
160 it("creates the ~/.claude/skills/ directory tree", () => {
161 expect(script).toContain("$HOME/.claude/skills");
162 expect(script).toMatch(/mkdir -p[^\n]*gluecron-pr/);
163 expect(script).toMatch(/mkdir -p[^\n]*gluecron-issue/);
164 expect(script).toMatch(/mkdir -p[^\n]*gluecron-review/);
165 });
166
167 for (const name of SKILLS) {
168 it(`writes ${name}/SKILL.md to the skills dir`, () => {
169 // Either `cat > .../<name>/SKILL.md` or `cp ... <name>/SKILL.md` is fine.
170 const pattern = new RegExp(`(cat\\s*>|cp\\s+[^\\n]+)[^\\n]*${name}/SKILL\\.md`);
171 expect(script).toMatch(pattern);
172 });
173
174 it(`installed ${name}/SKILL.md heredoc mentions Gluecron`, () => {
175 // The heredoc body should describe the skill — at minimum, mention "Gluecron".
176 // We just check the script contains both the path AND the word Gluecron.
177 expect(script).toContain(`${name}/SKILL.md`);
178 expect(script.toLowerCase()).toContain("gluecron");
179 });
180 }
181});
0182