From f192932ed94664f40eec37a646f84150966f3785 Mon Sep 17 00:00:00 2001 From: Jordan Coin Jackson Date: Fri, 27 Mar 2026 20:04:13 -0400 Subject: [PATCH 1/2] add config setup skill routing --- .claude/skills/codemap-setup/SKILL.md | 52 +++++++++ .claude/skills/codemap/SKILL.md | 29 ++++- cmd/config.go | 26 ++--- cmd/config_more_test.go | 19 ++++ cmd/hooks.go | 61 +++++++++++ cmd/hooks_more_test.go | 49 +++++++++ config/config.go | 108 +++++++++++++++++++ config/config_test.go | 102 ++++++++++++++++++ plugins/codemap/.codex-plugin/plugin.json | 8 +- plugins/codemap/skills/codemap/SKILL.md | 29 ++++- plugins/codemap/skills/config-setup/SKILL.md | 52 +++++++++ skills/builtin/config-setup.md | 93 ++++++++++++++++ 12 files changed, 605 insertions(+), 23 deletions(-) create mode 100644 .claude/skills/codemap-setup/SKILL.md create mode 100644 plugins/codemap/skills/config-setup/SKILL.md create mode 100644 skills/builtin/config-setup.md diff --git a/.claude/skills/codemap-setup/SKILL.md b/.claude/skills/codemap-setup/SKILL.md new file mode 100644 index 0000000..e2c2759 --- /dev/null +++ b/.claude/skills/codemap-setup/SKILL.md @@ -0,0 +1,52 @@ +--- +name: config-setup +description: Set up or tune .codemap/config.json so Codemap focuses on code-relevant parts of the repo. Use when config is missing, boilerplate, noisy, or mismatched to the stack. +--- + +# Codemap Config Setup + +## Goal + +Write or improve `.codemap/config.json` so future Codemap calls stay focused on the code that matters for this repo. + +## Use this when + +1. `.codemap/config.json` is missing +2. The existing config looks like a bare bootstrap instead of a real project policy +3. Codemap output is dominated by assets, fixtures, generated files, vendor trees, PDFs, screenshots, models, or training data +4. The project stack is obvious, but Codemap is not prioritizing the right parts of the repo + +## Workflow + +1. Inspect the repo quickly before writing config + - Run `codemap .` + - If needed, run `codemap --deps .` + - Note the stack markers (`Cargo.toml`, `Package.swift`, `*.xcodeproj`, `go.mod`, `package.json`, `pyproject.toml`, etc.) + - Identify large non-code directories and noisy extensions + +2. Decide whether config is missing, boilerplate, or tuned + - Missing: no `.codemap/config.json` + - Boilerplate: only generic `only` values, no real shaping, no excludes despite obvious noise + - Tuned: contains intentional project-specific includes/excludes, depth, or routing hints + +3. Write a conservative code-first config + - Keep primary source-language `only` values when they help + - Add `exclude` entries for obvious non-code noise + - Set a moderate `depth` when the repo is broad + - Avoid overfitting or excluding real source directories + +4. Prefer stack-aware defaults + - Rust: focus `src`, `tests`, `benches`, `examples`; de-prioritize corpora, sample PDFs, training data, large generated artifacts + - iOS/Swift: focus app/framework source, tests, package/project manifests; de-prioritize `.xcassets`, screenshots, snapshots, vendor/build outputs + - TS/JS: focus `src`, `apps`, `packages`, `tests`; de-prioritize `dist`, `coverage`, Storybook assets, large fixture payloads + - Python: focus package roots, tests, tool config; de-prioritize notebooks, data dumps, models, fixtures when they overwhelm code + - Go: focus packages, cmd, internal, tests; de-prioritize generated assets, sample data, vendor-like noise + +5. Preserve user intent + - If config already looks curated, do not replace it wholesale + - Make minimal edits and explain why + +6. Verify immediately + - Rerun `codemap .` + - If the repo still looks noisy, refine `exclude` and possibly `depth` + - Only rerun `codemap --deps .` after tree output looks reasonable diff --git a/.claude/skills/codemap/SKILL.md b/.claude/skills/codemap/SKILL.md index c597306..05bb80f 100644 --- a/.claude/skills/codemap/SKILL.md +++ b/.claude/skills/codemap/SKILL.md @@ -1,12 +1,14 @@ --- name: codemap -description: Analyze codebase structure, dependencies, changes, cross-agent handoffs, and get code-aware intelligence. Use when user asks about project structure, where code is located, how files connect, what changed, how to resume work, before starting any coding task, or when you need risk analysis and skill guidance. +description: Analyze codebase structure, dependencies, changes, cross-agent handoffs, and get code-aware intelligence. Use when user asks about project structure, where code is located, how files connect, what changed, how to resume work, before starting any coding task, when you need risk analysis and skill guidance, or when Codemap should tune project config before analysis. --- # Codemap Codemap gives you instant architectural context about any codebase. It classifies your intent, detects risk, matches relevant skills, and tracks your working set — all automatically via hooks. +Codemap should also keep its own per-project config healthy. On first use in a repo, or when output is obviously noisy, tune `.codemap/config.json` before doing deeper analysis so future calls stay code-first instead of asset-first. + ## Commands ```bash @@ -21,12 +23,30 @@ codemap handoff --json . # Machine-readable handoff payload codemap skill list # Show available skills with descriptions codemap skill show # Get full skill instructions codemap skill init # Create custom skill template +codemap config show # Show current project config codemap context # Universal JSON context envelope codemap context --for "prompt" # With pre-classified intent + matched skills codemap context --compact # Minimal for token-constrained agents codemap serve --port 9471 # HTTP API for non-MCP integrations ``` +## First-Use Setup + +Before deeper Codemap analysis in a repo: + +1. Check `.codemap/config.json`. +2. If it is missing, clearly boilerplate, or obviously too noisy for the stack, run `codemap skill show config-setup` and follow it. +3. After writing or improving config, rerun `codemap .` and `codemap --deps`. + +Treat config as repo memory. Once tuned, future Codemap calls should benefit automatically. + +Signals that config needs setup or tuning: +- `.codemap/config.json` is missing +- config only contains generic auto-detected `only` values with no real project shaping +- large non-code directories dominate the tree output +- stack-specific noise is overwhelming source structure (`.xcassets`, screenshots, PDFs, training-data, fixtures, generated files, models, vendor directories) +- the repo stack is obvious, but the config does not reflect it + ## When to Use ### ALWAYS run `codemap .` when: @@ -56,6 +76,12 @@ codemap serve --port 9471 # HTTP API for non-MCP integrations - You need guidance for a specific task (hub editing, refactoring, testing) - Risk level is medium or high +### Run `codemap skill show config-setup` when: +- The repo has no `.codemap/config.json` +- The config looks like a bare bootstrap and not a real project policy +- Codemap output is cluttered by large non-code directories +- You want Codemap to make better future decisions for this specific repo + ### Run `codemap context` when: - Piping codemap intelligence to another tool - Need a structured JSON summary of the project state @@ -84,6 +110,7 @@ Skills matched: hub-safety, refactor — run `codemap skill show ` for gui | Skill | When to Pull | |-------|-------------| +| `config-setup` | Missing, boilerplate, or noisy `.codemap/config.json` | | `hub-safety` | Editing files imported by 3+ others | | `refactor` | Restructuring, renaming, moving code | | `test-first` | Writing tests, TDD workflows | diff --git a/cmd/config.go b/cmd/config.go index ac1abc3..d8531e8 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -161,7 +161,7 @@ func initProjectConfig(root string) (configInitResult, error) { func configShow(root string) { cfg := config.Load(root) - if isConfigEmpty(cfg) { + if cfg.IsZero() { cfgPath := config.ConfigPath(root) if _, err := os.Stat(cfgPath); os.IsNotExist(err) { fmt.Println("No config file found.") @@ -229,23 +229,15 @@ func configShow(root string) { fmt.Printf(" require_docs_for: %s\n", strings.Join(cfg.Drift.RequireDocsFor, ", ")) } } + + assessment := config.AssessSetup(root) + if assessment.State == config.SetupStateBoilerplate { + fmt.Println() + fmt.Println("Note: this config still looks like a bootstrap.") + fmt.Println("Run `codemap skill show config-setup` to tune it for this repo.") + } } func isConfigEmpty(cfg config.ProjectConfig) bool { - if len(cfg.Only) > 0 || len(cfg.Exclude) > 0 || cfg.Depth > 0 { - return false - } - if strings.TrimSpace(cfg.Mode) != "" { - return false - } - if cfg.Budgets.SessionStartBytes > 0 || cfg.Budgets.DiffBytes > 0 || cfg.Budgets.MaxHubs > 0 { - return false - } - if strings.TrimSpace(cfg.Routing.Retrieval.Strategy) != "" || cfg.Routing.Retrieval.TopK > 0 || len(cfg.Routing.Subsystems) > 0 { - return false - } - if cfg.Drift.Enabled || cfg.Drift.RecentCommits > 0 || len(cfg.Drift.RequireDocsFor) > 0 { - return false - } - return true + return cfg.IsZero() } diff --git a/cmd/config_more_test.go b/cmd/config_more_test.go index 3791e4d..f1a2335 100644 --- a/cmd/config_more_test.go +++ b/cmd/config_more_test.go @@ -92,6 +92,25 @@ func TestConfigShowNoConfigFile(t *testing.T) { } } +func TestConfigShowBoilerplateConfigSuggestsTuning(t *testing.T) { + root := t.TempDir() + cfgPath := config.ConfigPath(root) + if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(cfgPath, []byte(`{"only":["rs","toml"],"depth":4}`+"\n"), 0o644); err != nil { + t.Fatal(err) + } + + out := captureOutput(func() { configShow(root) }) + if !strings.Contains(out, "looks like a bootstrap") { + t.Fatalf("expected bootstrap note, got:\n%s", out) + } + if !strings.Contains(out, "config-setup") { + t.Fatalf("expected config-setup guidance, got:\n%s", out) + } +} + func mustWriteConfigFixture(t *testing.T, path string, content string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { diff --git a/cmd/hooks.go b/cmd/hooks.go index 999f9c0..4f1f8bf 100644 --- a/cmd/hooks.go +++ b/cmd/hooks.go @@ -300,6 +300,8 @@ func hookSessionStart(root string) error { structureBudget := projCfg.SessionStartOutputBytes() maxHubs := projCfg.HubDisplayLimit() + showConfigSetupHint(root) + exe, err := os.Executable() if err == nil { depth := limits.AdaptiveDepth(fileCount) @@ -782,6 +784,7 @@ func showMatchedSkills(root string, intent TaskIntent) { } matches := idx.MatchSkills(intent.Category, intent.Files, langs, 3) + matches = injectConfigSetupSkill(root, idx, matches) if len(matches) == 0 { return } @@ -808,6 +811,64 @@ func showMatchedSkills(root string, intent TaskIntent) { fmt.Printf("Skills matched: %s — run `codemap skill show ` for guidance\n", strings.Join(names, ", ")) } +func injectConfigSetupSkill(root string, idx *skills.SkillIndex, matches []skills.MatchResult) []skills.MatchResult { + assessment := config.AssessSetup(root) + if !assessment.NeedsAttention() { + return matches + } + + skill, ok := idx.ByName["config-setup"] + if !ok || skill == nil { + return matches + } + for _, match := range matches { + if match.Skill != nil && match.Skill.Meta.Name == "config-setup" { + return matches + } + } + + reason := "config:" + string(assessment.State) + if len(assessment.Reasons) > 0 { + reason = reason + " " + assessment.Reasons[0] + } + + injected := skills.MatchResult{ + Skill: skill, + Score: 100, + Reason: reason, + } + matches = append([]skills.MatchResult{injected}, matches...) + if len(matches) > 3 { + matches = matches[:3] + } + return matches +} + +func showConfigSetupHint(root string) { + assessment := config.AssessSetup(root) + if !assessment.NeedsAttention() { + return + } + + type configHint struct { + State string `json:"state"` + Reasons []string `json:"reasons,omitempty"` + } + if data, err := json.Marshal(configHint{ + State: string(assessment.State), + Reasons: assessment.Reasons, + }); err == nil { + fmt.Printf("\n", string(data)) + } + + fmt.Println("⚙️ Codemap config setup recommended:") + for _, reason := range assessment.Reasons { + fmt.Printf(" • %s\n", reason) + } + fmt.Println(" • Run `codemap skill show config-setup` and tune `.codemap/config.json` before deeper analysis.") + fmt.Println() +} + // showDriftWarnings checks and displays documentation drift warnings. func showDriftWarnings(root string, cfg config.DriftConfig, routing config.RoutingConfig) { warnings := CheckDrift(root, cfg, routing) diff --git a/cmd/hooks_more_test.go b/cmd/hooks_more_test.go index 8feccdf..349ec17 100644 --- a/cmd/hooks_more_test.go +++ b/cmd/hooks_more_test.go @@ -443,6 +443,55 @@ func TestHookPromptSubmitShowsContextAndProgress(t *testing.T) { }) } +func TestHookPromptSubmitInjectsConfigSetupSkillWhenConfigMissing(t *testing.T) { + root := t.TempDir() + + withStdinInput(t, mustJSONInput(t, map[string]string{ + "prompt": "help me understand this repo structure", + }), func() { + var hookErr error + out := captureOutput(func() { hookErr = hookPromptSubmit(root) }) + if hookErr != nil { + t.Fatalf("hookPromptSubmit() error: %v", hookErr) + } + if !strings.Contains(out, `"name":"config-setup"`) { + t.Fatalf("expected config-setup skill marker, got:\n%s", out) + } + if !strings.Contains(out, "Skills matched: config-setup") { + t.Fatalf("expected config-setup skill hint, got:\n%s", out) + } + }) +} + +func TestShowConfigSetupHint(t *testing.T) { + t.Run("missing config emits setup hint", func(t *testing.T) { + out := captureOutput(func() { showConfigSetupHint(t.TempDir()) }) + if !strings.Contains(out, "codemap:config") { + t.Fatalf("expected config marker, got:\n%s", out) + } + if !strings.Contains(out, "config setup recommended") { + t.Fatalf("expected setup heading, got:\n%s", out) + } + if !strings.Contains(out, "config-setup") { + t.Fatalf("expected config-setup guidance, got:\n%s", out) + } + }) + + t.Run("ready config stays quiet", func(t *testing.T) { + root := t.TempDir() + writeProjectConfig(t, root, config.ProjectConfig{ + Only: []string{"go"}, + Exclude: []string{"fixtures"}, + Depth: 4, + }) + + out := captureOutput(func() { showConfigSetupHint(root) }) + if strings.TrimSpace(out) != "" { + t.Fatalf("expected no output for ready config, got:\n%s", out) + } + }) +} + func TestFindChildReposAndSessionStartVariants(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git not available") diff --git a/config/config.go b/config/config.go index f6d8f18..f81153a 100644 --- a/config/config.go +++ b/config/config.go @@ -68,6 +68,27 @@ const ( maxMaxHubs = 100 ) +type SetupState string + +const ( + SetupStateReady SetupState = "ready" + SetupStateMissing SetupState = "missing" + SetupStateMalformed SetupState = "malformed" + SetupStateEmpty SetupState = "empty" + SetupStateBoilerplate SetupState = "boilerplate" +) + +// SetupAssessment describes whether a repo's config needs setup attention. +type SetupAssessment struct { + State SetupState `json:"state"` + Reasons []string `json:"reasons,omitempty"` +} + +// NeedsAttention reports whether the config should be initialized or tuned. +func (a SetupAssessment) NeedsAttention() bool { + return a.State != SetupStateReady +} + func clampBudget(v, def, max int) int { if v <= 0 { return def @@ -91,6 +112,51 @@ func clampRange(v, def, min, max int) int { return v } +// IsZero reports whether the config has no active project policy. +func (c ProjectConfig) IsZero() bool { + if len(c.Only) > 0 || len(c.Exclude) > 0 || c.Depth > 0 { + return false + } + if strings.TrimSpace(c.Mode) != "" { + return false + } + if c.Budgets.SessionStartBytes > 0 || c.Budgets.DiffBytes > 0 || c.Budgets.MaxHubs > 0 { + return false + } + if strings.TrimSpace(c.Routing.Retrieval.Strategy) != "" || c.Routing.Retrieval.TopK > 0 || len(c.Routing.Subsystems) > 0 { + return false + } + if c.Drift.Enabled || c.Drift.RecentCommits > 0 || len(c.Drift.RequireDocsFor) > 0 { + return false + } + return true +} + +// LooksBoilerplate reports whether the config resembles a bare bootstrap. +// This intentionally treats extension-only configs as a first draft that +// should usually be tuned with project-specific excludes or policy. +func (c ProjectConfig) LooksBoilerplate() bool { + if c.IsZero() { + return false + } + if len(c.Exclude) > 0 { + return false + } + if strings.TrimSpace(c.Mode) != "" { + return false + } + if c.Budgets.SessionStartBytes > 0 || c.Budgets.DiffBytes > 0 || c.Budgets.MaxHubs > 0 { + return false + } + if strings.TrimSpace(c.Routing.Retrieval.Strategy) != "" || c.Routing.Retrieval.TopK > 0 || len(c.Routing.Subsystems) > 0 { + return false + } + if c.Drift.Enabled || c.Drift.RecentCommits > 0 || len(c.Drift.RequireDocsFor) > 0 { + return false + } + return len(c.Only) > 0 +} + // ModeOrDefault returns a valid hook orchestration mode. func (c ProjectConfig) ModeOrDefault() string { mode := strings.ToLower(strings.TrimSpace(c.Mode)) @@ -152,3 +218,45 @@ func Load(root string) ProjectConfig { } return cfg } + +// AssessSetup inspects .codemap/config.json and reports whether it should be +// initialized or tuned before deeper Codemap analysis. +func AssessSetup(root string) SetupAssessment { + data, err := os.ReadFile(ConfigPath(root)) + if err != nil { + if os.IsNotExist(err) { + return SetupAssessment{ + State: SetupStateMissing, + Reasons: []string{"No .codemap/config.json is present yet."}, + } + } + return SetupAssessment{ + State: SetupStateMalformed, + Reasons: []string{fmt.Sprintf("Codemap could not read the config file: %v", err)}, + } + } + + var cfg ProjectConfig + if err := json.Unmarshal(data, &cfg); err != nil { + return SetupAssessment{ + State: SetupStateMalformed, + Reasons: []string{"The existing .codemap/config.json is malformed JSON."}, + } + } + + if cfg.IsZero() { + return SetupAssessment{ + State: SetupStateEmpty, + Reasons: []string{"The config exists but does not shape Codemap output yet."}, + } + } + + if cfg.LooksBoilerplate() { + return SetupAssessment{ + State: SetupStateBoilerplate, + Reasons: []string{"The config only contains generic bootstrap filters and should be tuned for this repo."}, + } + } + + return SetupAssessment{State: SetupStateReady} +} diff --git a/config/config_test.go b/config/config_test.go index 4e140b6..eadea5b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "os" "path/filepath" + "strings" "testing" "codemap/limits" @@ -221,3 +222,104 @@ func TestPolicyDefaultsAndClamps(t *testing.T) { } }) } + +func TestProjectConfigStateHelpers(t *testing.T) { + t.Run("zero config is zero and not boilerplate", func(t *testing.T) { + var cfg ProjectConfig + if !cfg.IsZero() { + t.Fatal("expected zero-value config to be zero") + } + if cfg.LooksBoilerplate() { + t.Fatal("did not expect zero-value config to look boilerplate") + } + }) + + t.Run("extension-only config looks boilerplate", func(t *testing.T) { + cfg := ProjectConfig{ + Only: []string{"go", "ts"}, + } + if cfg.IsZero() { + t.Fatal("did not expect extension-only config to be zero") + } + if !cfg.LooksBoilerplate() { + t.Fatal("expected extension-only config to look boilerplate") + } + }) + + t.Run("project-shaped config does not look boilerplate", func(t *testing.T) { + cfg := ProjectConfig{ + Only: []string{"swift"}, + Exclude: []string{".xcassets", "Snapshots"}, + Depth: 4, + } + if cfg.LooksBoilerplate() { + t.Fatal("did not expect tuned config to look boilerplate") + } + }) +} + +func TestAssessSetup(t *testing.T) { + t.Run("missing config needs setup", func(t *testing.T) { + assessment := AssessSetup(t.TempDir()) + if assessment.State != SetupStateMissing { + t.Fatalf("AssessSetup() state = %q, want %q", assessment.State, SetupStateMissing) + } + if !assessment.NeedsAttention() { + t.Fatal("expected missing config to need attention") + } + }) + + t.Run("malformed config needs setup", func(t *testing.T) { + root := t.TempDir() + codemapDir := filepath.Join(root, ".codemap") + if err := os.MkdirAll(codemapDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(codemapDir, "config.json"), []byte("{bad json"), 0o644); err != nil { + t.Fatal(err) + } + + assessment := AssessSetup(root) + if assessment.State != SetupStateMalformed { + t.Fatalf("AssessSetup() state = %q, want %q", assessment.State, SetupStateMalformed) + } + }) + + t.Run("bootstrap config is boilerplate", func(t *testing.T) { + root := t.TempDir() + codemapDir := filepath.Join(root, ".codemap") + if err := os.MkdirAll(codemapDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(codemapDir, "config.json"), []byte(`{"only":["rs","toml"]}`), 0o644); err != nil { + t.Fatal(err) + } + + assessment := AssessSetup(root) + if assessment.State != SetupStateBoilerplate { + t.Fatalf("AssessSetup() state = %q, want %q", assessment.State, SetupStateBoilerplate) + } + if len(assessment.Reasons) == 0 || !strings.Contains(strings.ToLower(assessment.Reasons[0]), "bootstrap") { + t.Fatalf("expected bootstrap reason, got %v", assessment.Reasons) + } + }) + + t.Run("tuned config is ready", func(t *testing.T) { + root := t.TempDir() + codemapDir := filepath.Join(root, ".codemap") + if err := os.MkdirAll(codemapDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(codemapDir, "config.json"), []byte(`{"only":["swift"],"exclude":[".xcassets"],"depth":4}`), 0o644); err != nil { + t.Fatal(err) + } + + assessment := AssessSetup(root) + if assessment.State != SetupStateReady { + t.Fatalf("AssessSetup() state = %q, want %q", assessment.State, SetupStateReady) + } + if assessment.NeedsAttention() { + t.Fatal("did not expect tuned config to need attention") + } + }) +} diff --git a/plugins/codemap/.codex-plugin/plugin.json b/plugins/codemap/.codex-plugin/plugin.json index 792d9f3..707134a 100644 --- a/plugins/codemap/.codex-plugin/plugin.json +++ b/plugins/codemap/.codex-plugin/plugin.json @@ -21,8 +21,8 @@ "mcpServers": "./.mcp.json", "interface": { "displayName": "Codemap", - "shortDescription": "Code-aware context and skill routing for local repos", - "longDescription": "Use Codemap to inspect project structure, trace dependencies, evaluate change risk, match task-specific skills, and build cross-agent handoffs.", + "shortDescription": "Code-aware context, config tuning, and skill routing for local repos", + "longDescription": "Use Codemap to tune per-project config, inspect project structure, trace dependencies, evaluate change risk, match task-specific skills, and build cross-agent handoffs.", "developerName": "JordanCoin", "category": "Coding", "capabilities": [ @@ -33,9 +33,9 @@ "composerIcon": "./assets/icon.png", "logo": "./assets/logo.png", "defaultPrompt": [ + "Set up Codemap for this repo and tune the project config.", "Show me this project's structure and hub files.", - "What changed on this branch and what looks risky?", - "Build a Codemap handoff for the next agent." + "What changed on this branch and what looks risky?" ], "screenshots": [ "./assets/screenshot1.png" diff --git a/plugins/codemap/skills/codemap/SKILL.md b/plugins/codemap/skills/codemap/SKILL.md index c597306..05bb80f 100644 --- a/plugins/codemap/skills/codemap/SKILL.md +++ b/plugins/codemap/skills/codemap/SKILL.md @@ -1,12 +1,14 @@ --- name: codemap -description: Analyze codebase structure, dependencies, changes, cross-agent handoffs, and get code-aware intelligence. Use when user asks about project structure, where code is located, how files connect, what changed, how to resume work, before starting any coding task, or when you need risk analysis and skill guidance. +description: Analyze codebase structure, dependencies, changes, cross-agent handoffs, and get code-aware intelligence. Use when user asks about project structure, where code is located, how files connect, what changed, how to resume work, before starting any coding task, when you need risk analysis and skill guidance, or when Codemap should tune project config before analysis. --- # Codemap Codemap gives you instant architectural context about any codebase. It classifies your intent, detects risk, matches relevant skills, and tracks your working set — all automatically via hooks. +Codemap should also keep its own per-project config healthy. On first use in a repo, or when output is obviously noisy, tune `.codemap/config.json` before doing deeper analysis so future calls stay code-first instead of asset-first. + ## Commands ```bash @@ -21,12 +23,30 @@ codemap handoff --json . # Machine-readable handoff payload codemap skill list # Show available skills with descriptions codemap skill show # Get full skill instructions codemap skill init # Create custom skill template +codemap config show # Show current project config codemap context # Universal JSON context envelope codemap context --for "prompt" # With pre-classified intent + matched skills codemap context --compact # Minimal for token-constrained agents codemap serve --port 9471 # HTTP API for non-MCP integrations ``` +## First-Use Setup + +Before deeper Codemap analysis in a repo: + +1. Check `.codemap/config.json`. +2. If it is missing, clearly boilerplate, or obviously too noisy for the stack, run `codemap skill show config-setup` and follow it. +3. After writing or improving config, rerun `codemap .` and `codemap --deps`. + +Treat config as repo memory. Once tuned, future Codemap calls should benefit automatically. + +Signals that config needs setup or tuning: +- `.codemap/config.json` is missing +- config only contains generic auto-detected `only` values with no real project shaping +- large non-code directories dominate the tree output +- stack-specific noise is overwhelming source structure (`.xcassets`, screenshots, PDFs, training-data, fixtures, generated files, models, vendor directories) +- the repo stack is obvious, but the config does not reflect it + ## When to Use ### ALWAYS run `codemap .` when: @@ -56,6 +76,12 @@ codemap serve --port 9471 # HTTP API for non-MCP integrations - You need guidance for a specific task (hub editing, refactoring, testing) - Risk level is medium or high +### Run `codemap skill show config-setup` when: +- The repo has no `.codemap/config.json` +- The config looks like a bare bootstrap and not a real project policy +- Codemap output is cluttered by large non-code directories +- You want Codemap to make better future decisions for this specific repo + ### Run `codemap context` when: - Piping codemap intelligence to another tool - Need a structured JSON summary of the project state @@ -84,6 +110,7 @@ Skills matched: hub-safety, refactor — run `codemap skill show ` for gui | Skill | When to Pull | |-------|-------------| +| `config-setup` | Missing, boilerplate, or noisy `.codemap/config.json` | | `hub-safety` | Editing files imported by 3+ others | | `refactor` | Restructuring, renaming, moving code | | `test-first` | Writing tests, TDD workflows | diff --git a/plugins/codemap/skills/config-setup/SKILL.md b/plugins/codemap/skills/config-setup/SKILL.md new file mode 100644 index 0000000..e2c2759 --- /dev/null +++ b/plugins/codemap/skills/config-setup/SKILL.md @@ -0,0 +1,52 @@ +--- +name: config-setup +description: Set up or tune .codemap/config.json so Codemap focuses on code-relevant parts of the repo. Use when config is missing, boilerplate, noisy, or mismatched to the stack. +--- + +# Codemap Config Setup + +## Goal + +Write or improve `.codemap/config.json` so future Codemap calls stay focused on the code that matters for this repo. + +## Use this when + +1. `.codemap/config.json` is missing +2. The existing config looks like a bare bootstrap instead of a real project policy +3. Codemap output is dominated by assets, fixtures, generated files, vendor trees, PDFs, screenshots, models, or training data +4. The project stack is obvious, but Codemap is not prioritizing the right parts of the repo + +## Workflow + +1. Inspect the repo quickly before writing config + - Run `codemap .` + - If needed, run `codemap --deps .` + - Note the stack markers (`Cargo.toml`, `Package.swift`, `*.xcodeproj`, `go.mod`, `package.json`, `pyproject.toml`, etc.) + - Identify large non-code directories and noisy extensions + +2. Decide whether config is missing, boilerplate, or tuned + - Missing: no `.codemap/config.json` + - Boilerplate: only generic `only` values, no real shaping, no excludes despite obvious noise + - Tuned: contains intentional project-specific includes/excludes, depth, or routing hints + +3. Write a conservative code-first config + - Keep primary source-language `only` values when they help + - Add `exclude` entries for obvious non-code noise + - Set a moderate `depth` when the repo is broad + - Avoid overfitting or excluding real source directories + +4. Prefer stack-aware defaults + - Rust: focus `src`, `tests`, `benches`, `examples`; de-prioritize corpora, sample PDFs, training data, large generated artifacts + - iOS/Swift: focus app/framework source, tests, package/project manifests; de-prioritize `.xcassets`, screenshots, snapshots, vendor/build outputs + - TS/JS: focus `src`, `apps`, `packages`, `tests`; de-prioritize `dist`, `coverage`, Storybook assets, large fixture payloads + - Python: focus package roots, tests, tool config; de-prioritize notebooks, data dumps, models, fixtures when they overwhelm code + - Go: focus packages, cmd, internal, tests; de-prioritize generated assets, sample data, vendor-like noise + +5. Preserve user intent + - If config already looks curated, do not replace it wholesale + - Make minimal edits and explain why + +6. Verify immediately + - Rerun `codemap .` + - If the repo still looks noisy, refine `exclude` and possibly `depth` + - Only rerun `codemap --deps .` after tree output looks reasonable diff --git a/skills/builtin/config-setup.md b/skills/builtin/config-setup.md new file mode 100644 index 0000000..86d1197 --- /dev/null +++ b/skills/builtin/config-setup.md @@ -0,0 +1,93 @@ +--- +name: config-setup +description: Set up or tune .codemap/config.json so Codemap focuses on code-relevant parts of the repo. Use when config is missing, boilerplate, noisy, or mismatched to the stack. +priority: 8 +keywords: ["setup", "config", "tune", "initialize", "noise", "boilerplate", "exclude", "onboard"] +languages: ["go", "typescript", "javascript", "python", "rust", "ruby", "swift", "java", "kotlin", "csharp"] +--- + +# Codemap Config Setup + +## Goal + +Write or improve `.codemap/config.json` so future Codemap calls stay focused on the code that matters for this repo. + +## Use this when + +1. `.codemap/config.json` is missing +2. The existing config looks like a bare bootstrap instead of a real project policy +3. Codemap output is dominated by assets, fixtures, generated files, vendor trees, PDFs, screenshots, models, or training data +4. The project stack is obvious, but Codemap is not prioritizing the right parts of the repo + +## Workflow + +1. Inspect the repo quickly before writing config + - Run `codemap .` + - If needed, run `codemap --deps .` + - Note the stack markers (`Cargo.toml`, `Package.swift`, `*.xcodeproj`, `go.mod`, `package.json`, `pyproject.toml`, etc.) + - Identify large non-code directories and noisy extensions + +2. Decide whether config is missing, boilerplate, or tuned + - Missing: no `.codemap/config.json` + - Boilerplate: only generic `only` values, no real shaping, no excludes despite obvious noise + - Tuned: contains intentional project-specific includes/excludes, depth, or routing hints + +3. Write a conservative code-first config + - Keep primary source-language `only` values when they help + - Add `exclude` entries for obvious non-code noise + - Set a moderate `depth` when the repo is broad + - Avoid overfitting or excluding real source directories + +4. Prefer stack-aware defaults + - Rust: focus `src`, `tests`, `benches`, `examples`; de-prioritize corpora, sample PDFs, training data, large generated artifacts + - iOS/Swift: focus app/framework source, tests, package/project manifests; de-prioritize `.xcassets`, screenshots, snapshots, vendor/build outputs + - TS/JS: focus `src`, `apps`, `packages`, `tests`; de-prioritize `dist`, `coverage`, Storybook assets, large fixture payloads + - Python: focus package roots, tests, tool config; de-prioritize notebooks, data dumps, models, fixtures when they overwhelm code + - Go: focus packages, cmd, internal, tests; de-prioritize generated assets, sample data, vendor-like noise + +5. Preserve user intent + - If config already looks curated, do not replace it wholesale + - Make minimal edits and explain why + +6. Verify immediately + - Rerun `codemap .` + - If the repo still looks noisy, refine `exclude` and possibly `depth` + - Only rerun `codemap --deps .` after tree output looks reasonable + +## Heuristics + +- Exclude giant non-code directories before narrowing source extensions further +- Prefer directory exclusions over extension-only exclusions when the directory is clearly not core to the codebase +- Do not exclude tests just because they are large +- Do not exclude docs that are part of routing or subsystem understanding +- Keep the first config conservative; optimize further only if the output remains noisy + +## Example outcomes + +### Rust library with large fixture data + +```json +{ + "only": ["rs", "toml", "md"], + "exclude": ["training-data", "test-pdfs", "*.pdf"], + "depth": 4 +} +``` + +### iOS app with asset bloat + +```json +{ + "only": ["swift", "m", "mm", "h", "plist", "md"], + "exclude": [".xcassets", "Snapshots", "Screenshots", "DerivedData"], + "depth": 4 +} +``` + +## Output expectations + +After tuning config, briefly report: + +1. Why the old config was missing or too noisy +2. What exclusions or depth choices you added +3. Whether future Codemap runs should now be materially cleaner From f1d54269b1f92cc016d37a5eed9828805f238c71 Mon Sep 17 00:00:00 2001 From: Jordan Coin Jackson Date: Fri, 27 Mar 2026 20:36:10 -0400 Subject: [PATCH 2/2] fix config setup review feedback --- .../{codemap-setup => config-setup}/SKILL.md | 0 cmd/config.go | 30 +++++++++++------ cmd/config_more_test.go | 19 +++++++++++ cmd/hooks.go | 1 + cmd/hooks_more_test.go | 32 +++++++++++++++++++ config/config.go | 7 ++++ config/config_test.go | 16 ++++++++++ 7 files changed, 95 insertions(+), 10 deletions(-) rename .claude/skills/{codemap-setup => config-setup}/SKILL.md (100%) diff --git a/.claude/skills/codemap-setup/SKILL.md b/.claude/skills/config-setup/SKILL.md similarity index 100% rename from .claude/skills/codemap-setup/SKILL.md rename to .claude/skills/config-setup/SKILL.md diff --git a/cmd/config.go b/cmd/config.go index d8531e8..564e2ff 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -160,19 +160,30 @@ func initProjectConfig(root string) (configInitResult, error) { } func configShow(root string) { - cfg := config.Load(root) - if cfg.IsZero() { - cfgPath := config.ConfigPath(root) - if _, err := os.Stat(cfgPath); os.IsNotExist(err) { - fmt.Println("No config file found.") - fmt.Printf("Run 'codemap config init' to create %s\n", cfgPath) - } else { - fmt.Println("Config is empty (no filters active).") + assessment := config.AssessSetup(root) + cfgPath := config.ConfigPath(root) + switch assessment.State { + case config.SetupStateMissing: + fmt.Println("No config file found.") + fmt.Printf("Run 'codemap config init' to create %s\n", cfgPath) + return + case config.SetupStateEmpty: + fmt.Println("Config is empty (no filters active).") + if len(assessment.Reasons) > 0 { + fmt.Println(assessment.Reasons[0]) } return + case config.SetupStateMalformed: + fmt.Println("Config is malformed or unreadable.") + if len(assessment.Reasons) > 0 { + fmt.Println(assessment.Reasons[0]) + } + fmt.Printf("Fix %s or rerun 'codemap config init' to recreate it.\n", cfgPath) + return } - fmt.Printf("Config: %s\n", config.ConfigPath(root)) + cfg := config.Load(root) + fmt.Printf("Config: %s\n", cfgPath) fmt.Println() if len(cfg.Only) > 0 { fmt.Printf(" only: %s\n", strings.Join(cfg.Only, ", ")) @@ -230,7 +241,6 @@ func configShow(root string) { } } - assessment := config.AssessSetup(root) if assessment.State == config.SetupStateBoilerplate { fmt.Println() fmt.Println("Note: this config still looks like a bootstrap.") diff --git a/cmd/config_more_test.go b/cmd/config_more_test.go index f1a2335..144681b 100644 --- a/cmd/config_more_test.go +++ b/cmd/config_more_test.go @@ -92,6 +92,25 @@ func TestConfigShowNoConfigFile(t *testing.T) { } } +func TestConfigShowMalformedConfig(t *testing.T) { + root := t.TempDir() + cfgPath := config.ConfigPath(root) + if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(cfgPath, []byte("{broken"), 0o644); err != nil { + t.Fatal(err) + } + + out := captureOutput(func() { configShow(root) }) + if !strings.Contains(out, "malformed or unreadable") { + t.Fatalf("expected malformed guidance, got:\n%s", out) + } + if !strings.Contains(out, "rerun 'codemap config init'") { + t.Fatalf("expected recreate guidance, got:\n%s", out) + } +} + func TestConfigShowBoilerplateConfigSuggestsTuning(t *testing.T) { root := t.TempDir() cfgPath := config.ConfigPath(root) diff --git a/cmd/hooks.go b/cmd/hooks.go index 4f1f8bf..c834cd2 100644 --- a/cmd/hooks.go +++ b/cmd/hooks.go @@ -1502,6 +1502,7 @@ func hookSessionStartMultiRepo(root string, childRepos []string) error { repoPath := filepath.Join(root, repo) projCfg := config.Load(repoPath) + showConfigSetupHint(repoPath) depth := 2 if projCfg.Depth > 0 { diff --git a/cmd/hooks_more_test.go b/cmd/hooks_more_test.go index 349ec17..938cda2 100644 --- a/cmd/hooks_more_test.go +++ b/cmd/hooks_more_test.go @@ -726,4 +726,36 @@ func TestDaemonCommandHelpersAndMultiRepoShellout(t *testing.T) { } } }) + + t.Run("multi repo start emits setup hints for child repos needing config", func(t *testing.T) { + root := t.TempDir() + for _, repo := range []string{"svc-a", "svc-b"} { + repoPath := filepath.Join(root, repo) + if err := os.MkdirAll(repoPath, 0o755); err != nil { + t.Fatal(err) + } + } + + withHookRuntimeStubs( + t, + func() (string, error) { return "/tmp/codemap-hook", nil }, + func(name string, args ...string) *exec.Cmd { + return exec.Command("sh", "-c", "exit 0") + }, + nil, + nil, + ) + + out := captureOutput(func() { + if err := hookSessionStartMultiRepo(root, []string{"svc-a", "svc-b"}); err != nil { + t.Fatalf("hookSessionStartMultiRepo() error: %v", err) + } + }) + if !strings.Contains(out, "codemap:config") { + t.Fatalf("expected config marker in multi-repo output, got:\n%s", out) + } + if !strings.Contains(out, "config-setup") { + t.Fatalf("expected config-setup guidance in multi-repo output, got:\n%s", out) + } + }) } diff --git a/config/config.go b/config/config.go index f81153a..caa914e 100644 --- a/config/config.go +++ b/config/config.go @@ -236,6 +236,13 @@ func AssessSetup(root string) SetupAssessment { } } + if strings.TrimSpace(string(data)) == "" { + return SetupAssessment{ + State: SetupStateEmpty, + Reasons: []string{"The config file is blank and does not shape Codemap output yet."}, + } + } + var cfg ProjectConfig if err := json.Unmarshal(data, &cfg); err != nil { return SetupAssessment{ diff --git a/config/config_test.go b/config/config_test.go index eadea5b..f3437dd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -285,6 +285,22 @@ func TestAssessSetup(t *testing.T) { } }) + t.Run("blank config is empty", func(t *testing.T) { + root := t.TempDir() + codemapDir := filepath.Join(root, ".codemap") + if err := os.MkdirAll(codemapDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(codemapDir, "config.json"), []byte(" \n\t "), 0o644); err != nil { + t.Fatal(err) + } + + assessment := AssessSetup(root) + if assessment.State != SetupStateEmpty { + t.Fatalf("AssessSetup() state = %q, want %q", assessment.State, SetupStateEmpty) + } + }) + t.Run("bootstrap config is boilerplate", func(t *testing.T) { root := t.TempDir() codemapDir := filepath.Join(root, ".codemap")