Skip to content

feat: skills framework — builtin skills, matching, CLI, and MCP tools#50

Closed
JordanCoin wants to merge 2 commits intofeat/intelligent-routingfrom
feat/skills-framework
Closed

feat: skills framework — builtin skills, matching, CLI, and MCP tools#50
JordanCoin wants to merge 2 commits intofeat/intelligent-routingfrom
feat/skills-framework

Conversation

@JordanCoin
Copy link
Copy Markdown
Owner

Summary

Phase 2 of the AI coding system evolution. Builds on #49 (intelligent routing).

Skills are markdown files with YAML frontmatter that provide context-aware guidance to AI agents. They're automatically matched against the user's intent, mentioned files, and detected languages — then injected into the prompt-submit hook output.

What's new

  • skills/ package — YAML frontmatter parser, multi-source loader (builtin → project-local → global), deduplication, weighted matching engine
  • 5 builtin skills embedded in the binary: hub-safety, refactor, test-first, explore, handoff
  • codemap skill list|show|init CLI subcommand
  • list_skills / get_skill MCP tools with progressive disclosure (list = metadata, get = full body)
  • Hook integration — prompt-submit matches and injects top 3 skills with <!-- codemap:skills --> marker, budget-capped at 8KB

The community contribution surface

Anyone can add a skill by dropping a .md file in .codemap/skills/ — no Go code needed. Project-local skills override builtins. This is the sustainability flywheel.

Bug fixes from parallel reviews

Source Finding Fix
Codex P1: Priority boost ranked all builtins even with zero signal Only apply priority after real match
Codex P2: No fallback name for frontmatter-less skills Derive from filename
Claude worktree review P1: Hub-edits counted events not unique files Count unique hub paths

Test plan

  • go test ./... -race — all 10 packages pass
  • codemap skill list shows 5 builtins
  • codemap skill show hub-safety renders full content
  • Irrelevant prompts ("hello world") inject zero skills
  • Relevant prompts ("refactor the auth module") inject matching skills only
  • Project-local skills override builtins (tested)
  • Codex review: 3 findings, all fixed

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 25, 2026 03:06
@JordanCoin JordanCoin force-pushed the feat/skills-framework branch from 1b97680 to 73f6020 Compare March 25, 2026 03:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new “skills framework” to codemap: skills are Markdown files with YAML frontmatter that can be loaded from builtin/project/global locations, matched against prompt intent/files/languages, and injected into hook output; it also exposes skills via a CLI subcommand and new MCP tools.

Changes:

  • Added skills/ package for parsing/loading embedded + local skills and matching the top skills by signals (intent category, languages, path patterns, priority).
  • Added 5 embedded builtin skills under skills/builtin/ and tests for parsing/loading/matching.
  • Integrated skills into hook prompt-submit, plus added codemap skill list|show|init and MCP tools list_skills / get_skill.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
skills/types.go Defines skill metadata/types and indexing helpers.
skills/loader.go Implements frontmatter parsing, multi-source loading, matching, and path matching.
skills/embed.go Embeds builtin skill markdown files into the binary.
skills/loader_test.go Adds unit/integration coverage for parsing/loading/matching skills.
skills/builtin/test-first.md Builtin “test-first” skill content.
skills/builtin/refactor.md Builtin “refactor” skill content.
skills/builtin/hub-safety.md Builtin “hub-safety” skill content.
skills/builtin/handoff.md Builtin “handoff” skill content.
skills/builtin/explore.md Builtin “explore” skill content.
cmd/hooks.go Injects matched skills into prompt-submit output (marker + bodies) and fixes hub edit counting.
cmd/hooks_test.go Updates session progress test for unique hub-file counting.
cmd/skill.go Adds codemap skill CLI subcommand (list/show/init).
mcp/main.go Adds MCP tools to list/get skills.
main.go Routes skill subcommand before global flag parsing.
go.mod Adds YAML dependency for frontmatter parsing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +38
## Test file conventions

| Language | Convention |
|----------|-----------|
| Go | `*_test.go` in same package |
| TypeScript | `*.test.ts` or `*.spec.ts` |
| Python | `test_*.py` or `*_test.py` |
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown table header uses || (empty first column), which renders incorrectly in many Markdown parsers. It likely should be a standard table like | Language | Convention | with |---|---| separator row.

Copilot uses AI. Check for mistakes.
Comment thread skills/builtin/handoff.md
Comment on lines +41 to +47
## Artifacts written

| File | Content |
|------|---------|
| `.codemap/handoff.latest.json` | Full artifact (all layers) |
| `.codemap/handoff.prefix.json` | Stable prefix snapshot |
| `.codemap/handoff.delta.json` | Dynamic delta snapshot |
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown tables use || (empty first column), which often renders incorrectly. Use standard table syntax (single leading |) for better compatibility.

Copilot uses AI. Check for mistakes.
Comment thread skills/loader.go
Comment on lines +223 to +237
// matchPath does a simple glob-like match: supports * and ** patterns,
// and falls back to substring matching.
func matchPath(pattern, path string) bool {
// Simple substring match for non-glob patterns
if !strings.ContainsAny(pattern, "*?") {
return strings.Contains(path, pattern)
}
// Use filepath.Match for simple globs
matched, _ := filepath.Match(pattern, path)
if matched {
return true
}
// Also try matching against just the filename
matched, _ = filepath.Match(pattern, filepath.Base(path))
return matched
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says matchPath supports ** patterns, but the implementation uses filepath.Match, which does not implement ** recursive glob semantics. Either implement ** handling (e.g., by translating to a recursive match / doublestar) or adjust the comment and documented pattern support.

Copilot uses AI. Check for mistakes.
Comment thread go.mod
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gopkg.in/yaml.v3 is imported directly (skills/loader.go) but is listed as // indirect in go.mod. Running go mod tidy (or moving it to the direct require block) will keep module metadata accurate.

Copilot uses AI. Check for mistakes.
Comment thread mcp/main.go
Comment on lines +1020 to +1023
if strings.HasPrefix(path, "~/") {
home := os.Getenv("HOME")
path = filepath.Join(home, path[2:])
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tilde expansion here uses os.Getenv("HOME"), which won’t work on all platforms (notably Windows). Prefer os.UserHomeDir() (and handle its error) to make ~/ expansion portable.

Copilot uses AI. Check for mistakes.
Comment thread cmd/hooks.go
Comment on lines +795 to +800
if totalBytes+len(body) > maxSkillOutputBytes {
remaining := maxSkillOutputBytes - totalBytes
if remaining > 100 {
body = body[:remaining] + "\n... (skill truncated)"
} else {
break
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skill output truncation can exceed the intended 8KB budget because you slice to remaining and then append the truncation suffix, making the final string longer than remaining. Also, byte-slicing can split a UTF-8 rune, producing invalid output. Consider truncating to a rune boundary and subtracting the suffix length from the remaining budget (or count bytes after adding the suffix).

Copilot uses AI. Check for mistakes.
Comment thread cmd/skill.go
fmt.Println("Commands:")
fmt.Println(" list Show all available skills with descriptions")
fmt.Println(" show <name> Print full skill content")
fmt.Println(" init Create a SKILL.md template in .codemap/skills/")
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This help text says init creates a SKILL.md template, but the implementation writes .codemap/skills/my-skill.md. Update the message or the filename to avoid confusing users about the expected convention.

Suggested change
fmt.Println(" init Create a SKILL.md template in .codemap/skills/")
fmt.Println(" init Create a my-skill.md template in .codemap/skills/")

Copilot uses AI. Check for mistakes.
Comment thread cmd/skill.go
Comment on lines +91 to +122
func runSkillInit(root string) {
skillsDir := root + "/.codemap/skills"
if err := os.MkdirAll(skillsDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating skills directory: %v\n", err)
os.Exit(1)
}

template := `---
name: my-skill
description: Describe when this skill should activate and what it does
priority: 5
keywords: ["keyword1", "keyword2"]
languages: ["go"]
---

# My Skill

Instructions for the AI agent when this skill is activated.

## When to use

Describe the situations where this skill applies.

## Steps

1. First step
2. Second step
3. Third step
`

path := skillsDir + "/my-skill.md"
if _, err := os.Stat(path); err == nil {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paths are constructed via string concatenation (root + "/.codemap/skills", skillsDir + "/my-skill.md"), which is not portable on Windows and inconsistent with the rest of the repo’s filepath.Join usage. Use filepath.Join(root, ".codemap", "skills") and filepath.Join(skillsDir, "my-skill.md").

Copilot uses AI. Check for mistakes.
Comment thread skills/loader.go
Comment on lines +73 to +85

path := filepath.Join(dir, entry.Name())
data, err := os.ReadFile(path)
if err != nil {
continue
}

skill, err := ParseSkill(string(data), path, source)
if err != nil {
continue
}
skills = append(skills, *skill)
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadSkillsFromDir silently skips files that fail to read or parse (continue), so a single malformed skill file will just disappear from codemap skill list/show with no user-visible error. Consider returning an aggregated error (or at least the first error) when the directory exists, and only ignore os.IsNotExist at the call site.

Copilot uses AI. Check for mistakes.
Comment thread skills/embed.go
Comment on lines +26 to +31
continue
}

skill, err := ParseSkill(string(data), "builtin/"+entry.Name(), builtinSource)
if err != nil {
continue
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadBuiltinSkills silently ignores embedded file read/parse errors. If an embedded skill is malformed, the binary will start without it and there’s no error surfaced to the caller. Consider failing fast (returning an error) when a builtin skill can’t be loaded.

Suggested change
continue
}
skill, err := ParseSkill(string(data), "builtin/"+entry.Name(), builtinSource)
if err != nil {
continue
return nil, err
}
skill, err := ParseSkill(string(data), "builtin/"+entry.Name(), builtinSource)
if err != nil {
return nil, err

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b97680f2d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/loader.go
Comment on lines +230 to +232
// Use filepath.Match for simple globs
matched, _ := filepath.Match(pattern, path)
if matched {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle recursive ** in path pattern matching

matchPath sends glob patterns to filepath.Match, but that matcher does not implement recursive ** and * cannot cross path separators. In practice, patterns users are likely to write for skill scoping (for example **/*.go or src/**) will fail to match nested paths like cmd/hooks.go, so relevant path-scoped skills are silently never selected.

Useful? React with 👍 / 👎.

…ools

Phase 2 of codemap's evolution. Skills are markdown files with YAML frontmatter
that provide context-aware guidance to AI agents. They're matched against the
user's intent, mentioned files, and detected languages.

New package: skills/
- types.go — SkillMeta, Skill, SkillIndex with fast lookup by name/keyword/language
- loader.go — Multi-source loading (builtin → project → global), YAML frontmatter
  parsing, deduplication (later sources override), weighted matching
- embed.go — go:embed for builtin skills
- 5 builtin skills: hub-safety, refactor, test-first, explore, handoff

New: cmd/skill.go — CLI subcommand (list, show, init)
New: MCP tools — list_skills (metadata only) and get_skill (full body)

Hook integration:
- hookPromptSubmit matches skills against intent + files + languages
- Top 3 skills injected with <!-- codemap:skills --> structured marker
- Budget-capped at 8KB to prevent context blowup

Bug fixes from parallel reviews:
- [P1] Priority boost only applies after real signal (codex finding)
- [P2] Fallback name derived from filename for frontmatter-less skills
- [P1] showSessionProgress now counts unique hub files, not hub events
  (Claude code-reviewer finding from Phase 1 worktree review)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JordanCoin JordanCoin force-pushed the feat/skills-framework branch from 73f6020 to 0484f8e Compare March 25, 2026 03:12
…oadmap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JordanCoin JordanCoin deleted the branch feat/intelligent-routing March 25, 2026 03:25
@JordanCoin JordanCoin closed this Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants