Skip to content

feat: context protocol, HTTP API, and agent-aware handoff#51

Closed
JordanCoin wants to merge 1 commit intofeat/skills-frameworkfrom
feat/context-protocol
Closed

feat: context protocol, HTTP API, and agent-aware handoff#51
JordanCoin wants to merge 1 commit intofeat/skills-frameworkfrom
feat/context-protocol

Conversation

@JordanCoin
Copy link
Copy Markdown
Owner

Summary

Phase 3 — the final phase. Makes codemap's intelligence consumable by any AI tool.

codemap context — Universal JSON Envelope

codemap context                          # Full envelope
codemap context --for "refactor auth"    # With pre-classified intent
codemap context --compact                # Minimal for token-constrained agents

Output includes project info, intent classification, working set, matched skills, and handoff reference — everything an AI tool needs in one call.

codemap serve — HTTP API

codemap serve --port 9471
  GET /api/context?intent=refactor+auth&compact=true
  GET /api/skills?language=go&category=refactor
  GET /api/skills/<name>
  GET /api/working-set
  GET /api/health

Enables VS Code extensions, CI pipelines, web dashboards, and non-MCP AI tools to consume codemap intelligence.

Agent-Aware Handoff History

  • AgentEntry records which agent worked, when, and what it edited
  • Auto-detects Claude Code, Codex, and Cursor from environment
  • History carried across sessions, capped at 20 entries
  • True multi-agent continuity — each agent sees what previous agents did

Test plan

  • go test ./... -race — all 10 packages pass
  • codemap context --for "refactor auth" returns full envelope with intent + skills
  • codemap context --compact strips skills/handoff, limits working set
  • Binary builds on all platforms (inherits from Phase 1/2 CI)

🤖 Generated with Claude Code

Phase 3 of codemap's evolution into a code-aware AI coding system.

New: codemap context
- Universal JSON envelope any AI tool can consume
- --for "prompt" pre-classifies intent with matched skills
- --compact mode for token-constrained agents
- Includes project info, intent, working set, skills, handoff ref

New: codemap serve --port 9471
- GET /api/context?intent=refactor+auth&compact=true
- GET /api/skills?language=go&category=refactor
- GET /api/skills/<name>
- GET /api/working-set
- GET /api/health
- Uses net/http stdlib, no new dependencies

New: Agent-aware handoff history
- AgentEntry type records agent ID, timestamps, files edited
- Session-stop auto-detects agent (Claude Code, Codex, Cursor)
- History carried across sessions, capped at 20 entries
- Enables true multi-agent continuity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 03:21
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

Adds new interfaces for exposing codemap’s “intelligence” to external tools by introducing a JSON context envelope (codemap context), an HTTP API server (codemap serve), and agent-aware handoff history persisted in the handoff artifact.

Changes:

  • Add codemap context to emit a standardized JSON “context envelope” (optionally compact and/or intent-aware).
  • Add codemap serve to expose context, skills, and working-set information via HTTP endpoints.
  • Persist per-session agent history into the handoff artifact and cap it to the last 20 entries.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
main.go Dispatch context and serve subcommands before global flag parsing.
cmd/context.go Implements JSON context envelope generation and CLI output.
cmd/serve.go Implements HTTP API server and JSON response helpers.
cmd/hooks.go Records agent session metadata into handoff artifact history.
handoff/types.go Extends persisted handoff schema with AgentEntry and AgentHistory.

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

Comment on lines +37 to +110
// GET /api/context — full context envelope
mux.HandleFunc("/api/context", func(w http.ResponseWriter, r *http.Request) {
prompt := r.URL.Query().Get("intent")
compact := r.URL.Query().Get("compact") == "true"
envelope := buildContextEnvelope(absRoot, prompt, compact)
writeJSON(w, envelope)
})

// GET /api/skills — list available skills
mux.HandleFunc("/api/skills", func(w http.ResponseWriter, r *http.Request) {
idx, err := skills.LoadSkills(absRoot)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}

// Optional filters
lang := r.URL.Query().Get("language")
category := r.URL.Query().Get("category")

if category != "" || lang != "" {
var langs []string
if lang != "" {
langs = []string{lang}
}
matches := idx.MatchSkills(category, nil, langs, 10)
writeJSON(w, matches)
return
}

// Return all skill metadata (no bodies)
type skillMeta struct {
Name string `json:"name"`
Description string `json:"description"`
Source string `json:"source"`
Keywords []string `json:"keywords,omitempty"`
Languages []string `json:"languages,omitempty"`
Priority int `json:"priority,omitempty"`
}
var meta []skillMeta
for _, s := range idx.Skills {
meta = append(meta, skillMeta{
Name: s.Meta.Name,
Description: s.Meta.Description,
Source: s.Source,
Keywords: s.Meta.Keywords,
Languages: s.Meta.Languages,
Priority: s.Meta.Priority,
})
}
writeJSON(w, meta)
})

// GET /api/skills/:name — get full skill
mux.HandleFunc("/api/skills/", func(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/api/skills/")
if name == "" {
writeError(w, http.StatusBadRequest, "skill name required")
return
}

idx, err := skills.LoadSkills(absRoot)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}

skill, ok := idx.ByName[name]
if !ok {
writeError(w, http.StatusNotFound, fmt.Sprintf("skill %q not found", name))
return
}
writeJSON(w, 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.

New HTTP handlers in RunServe (context/skills/working-set/health) are not covered by tests, while cmd already has extensive automated tests. Consider adding httptest-based tests to verify status codes and response shapes (e.g., /api/skills/<name> 404 vs 200, and /api/context?compact=true omits large fields).

Copilot uses AI. Check for mistakes.
if os.Getenv("CODEX") == "1" {
return "codex"
}
// Cursor sets CURSOR=1
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 "Cursor sets CURSOR=1", but the detection logic actually checks CURSOR_SESSION_ID. Please update the comment to match the environment signal being used (or update the detection if CURSOR=1 is the intended contract).

Suggested change
// Cursor sets CURSOR=1
// Cursor sets CURSOR_SESSION_ID

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +183
// Calculate budget
data, _ := json.Marshal(envelope)
envelope.Budget.TotalBytes = len(data)

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.

Budget.TotalBytes is computed via json.Marshal(envelope) (no indentation), but RunContext outputs the envelope using an indented encoder. That makes total_bytes systematically under-report the actual bytes written to stdout. Consider computing the budget from the same encoding format you output (e.g., encode into a bytes.Buffer with the same indent settings), and handle the marshal/encode error instead of ignoring it.

Copilot uses AI. Check for mistakes.
Comment on lines +208 to +219
// Detect languages from file extensions
langSet := make(map[string]bool)
if info != nil {
for file := range info.Importers {
if lang := scanner.DetectLanguage(file); lang != "" {
langSet[lang] = true
}
}
}
for lang := range langSet {
ctx.Languages = append(ctx.Languages, lang)
}
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.

Languages is built by ranging over a map (langSet), which yields a nondeterministic order. Since this command is meant to be consumed by tools, unstable ordering can cause noisy diffs/caches and make tests flaky. Consider sorting ctx.Languages before returning.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +23
fs := flag.NewFlagSet("serve", flag.ExitOnError)
port := fs.Int("port", 9471, "Port to listen on")
if err := fs.Parse(args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
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.

RunServe uses flag.NewFlagSet(..., flag.ExitOnError) but then checks if err := fs.Parse(args); err != nil { ... }. With ExitOnError, Parse exits the process on error, so this block is effectively unreachable. Consider switching to flag.ContinueOnError for controlled errors/usage output (and to validate fs.NArg()), or remove the redundant error check.

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +125
// GET /api/health — simple health check
mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]string{"status": "ok", "root": absRoot})
})
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 server binds to all interfaces by default (addr := fmt.Sprintf(":%d", *port)), but the startup message implies localhost-only, and endpoints like /api/context and /api/health can expose sensitive local filesystem/project info (e.g., root). Consider defaulting to 127.0.0.1 (or adding a --host/--listen flag), and avoid returning absRoot in the health response unless explicitly requested.

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +139
if err := http.ListenAndServe(addr, mux); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
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.

http.ListenAndServe with the default http.Server has no timeouts, which makes the endpoint more vulnerable to slow-client/resource-exhaustion scenarios. Consider constructing an http.Server with at least ReadHeaderTimeout (and optionally ReadTimeout/WriteTimeout/IdleTimeout) and calling server.ListenAndServe().

Copilot uses AI. Check for mistakes.
Comment on lines +1168 to +1193
// Record this agent session in handoff history
agentEntry := handoff.AgentEntry{
AgentID: detectAgentID(),
StartedAt: sessionStartTime(state),
EndedAt: time.Now(),
}
if state != nil {
seen := make(map[string]bool)
for _, e := range state.RecentEvents {
if !seen[e.Path] {
seen[e.Path] = true
agentEntry.FilesEdited = append(agentEntry.FilesEdited, e.Path)
}
}
}

// Carry over history from previous artifact and append current session
if prev, err := handoff.ReadLatest(root); err == nil && prev != nil {
artifact.AgentHistory = prev.AgentHistory
}
artifact.AgentHistory = append(artifact.AgentHistory, agentEntry)

// Cap history to last 20 entries
if len(artifact.AgentHistory) > 20 {
artifact.AgentHistory = artifact.AgentHistory[len(artifact.AgentHistory)-20:]
}
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.

Agent history recording/capping logic in writeSessionHandoff is new behavior but doesn’t appear to have test coverage (the cmd package has extensive hook tests). Consider adding tests that verify: (1) agent ID detection from env vars, (2) FilesEdited deduping from RecentEvents, and (3) history is capped to the last 20 entries when prior artifacts already contain history.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +84
fs := flag.NewFlagSet("context", flag.ExitOnError)
forPrompt := fs.String("for", "", "Pre-classify intent for this prompt")
compact := fs.Bool("compact", false, "Minimal output for token-constrained agents")
if err := fs.Parse(args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
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.

RunContext uses flag.NewFlagSet(..., flag.ExitOnError) but then checks if err := fs.Parse(args); err != nil { ... }. With ExitOnError, Parse will call os.Exit(2) on error, so this error-handling block is effectively unreachable. Consider switching to flag.ContinueOnError (and printing a concise usage), or keep ExitOnError and remove the redundant err check.

Also consider rejecting extra positional args (e.g. fs.NArg() > 1) instead of silently ignoring them.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +116
func buildContextEnvelope(root, prompt string, compact bool) ContextEnvelope {
projCfg := config.Load(root)
info := getHubInfoNoFallback(root)

envelope := ContextEnvelope{
Version: 1,
GeneratedAt: time.Now(),
Project: buildProjectContext(root, info),
Budget: BudgetInfo{Compact: compact},
}

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.

New codemap context output paths/filters and compact vs full behavior aren't covered by tests in cmd (this package already has substantial test coverage). Consider adding unit tests around buildContextEnvelope to lock down: (1) compact omits skills/handoff, (2) intent classification is included when --for is provided, and (3) output fields like Languages are stable.

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: 924abeabaf

ℹ️ 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".

writeJSON(w, map[string]string{"status": "ok", "root": absRoot})
})

addr := fmt.Sprintf(":%d", *port)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind serve listener to loopback by default

The server address is built as ":<port>", which makes http.ListenAndServe bind on all network interfaces, not just localhost. In environments like shared dev boxes, CI runners, or remote VMs, this exposes /api/context and /api/skills to the network even though the startup message says http://localhost.... Use an explicit loopback bind (for example 127.0.0.1:<port>) or add a host flag with a safe default.

Useful? React with 👍 / 👎.

Comment on lines +211 to +213
for file := range info.Importers {
if lang := scanner.DetectLanguage(file); lang != "" {
langSet[lang] = true
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 Derive project languages from more than importer keys

Language detection iterates only over info.Importers keys, but that map only contains files that are imported by at least one other file. Repos with entrypoints/scripts or sparse dependency edges (for example a single-file project) will report languages: [] even when source files exist, making the context envelope inaccurate for downstream routing and tool selection.

Useful? React with 👍 / 👎.

JordanCoin added a commit that referenced this pull request Mar 25, 2026
- Bind HTTP server to 127.0.0.1 by default (security: P1)
- Add --host flag for explicit network bind control
- Add server timeouts (ReadHeader, Read, Write, Idle)
- Remove absRoot from /api/health response
- Use flag.ContinueOnError for controlled error handling
- Sort Languages output for deterministic JSON
- Detect languages from imports + hubs, not just importers
- Fix comment: Cursor detection uses CURSOR_SESSION_ID

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JordanCoin added a commit that referenced this pull request Mar 25, 2026
…andoff (#52)

* feat: add skills framework — builtin skills, matching, CLI, and MCP tools

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>

* docs: update README with skills framework, intelligent routing, and roadmap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add context protocol, HTTP API, and agent-aware handoff

Phase 3 of codemap's evolution into a code-aware AI coding system.

New: codemap context
- Universal JSON envelope any AI tool can consume
- --for "prompt" pre-classifies intent with matched skills
- --compact mode for token-constrained agents
- Includes project info, intent, working set, skills, handoff ref

New: codemap serve --port 9471
- GET /api/context?intent=refactor+auth&compact=true
- GET /api/skills?language=go&category=refactor
- GET /api/skills/<name>
- GET /api/working-set
- GET /api/health
- Uses net/http stdlib, no new dependencies

New: Agent-aware handoff history
- AgentEntry type records agent ID, timestamps, files edited
- Session-stop auto-detects agent (Claude Code, Codex, Cursor)
- History carried across sessions, capped at 20 entries
- Enables true multi-agent continuity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR #51 review comments

- Bind HTTP server to 127.0.0.1 by default (security: P1)
- Add --host flag for explicit network bind control
- Add server timeouts (ReadHeader, Read, Write, Idle)
- Remove absRoot from /api/health response
- Use flag.ContinueOnError for controlled error handling
- Sort Languages output for deterministic JSON
- Detect languages from imports + hubs, not just importers
- Fix comment: Cursor detection uses CURSOR_SESSION_ID

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update README with context protocol, HTTP API, and agent handoff

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: fix gofmt alignment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JordanCoin
Copy link
Copy Markdown
Owner Author

Superseded by #52 which combined Phase 2+3 and is now merged to main.

@JordanCoin JordanCoin closed this Mar 25, 2026
@JordanCoin JordanCoin deleted the feat/context-protocol branch March 25, 2026 03:41
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