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/.claude/skills/config-setup/SKILL.md b/.claude/skills/config-setup/SKILL.md new file mode 100644 index 0000000..e2c2759 --- /dev/null +++ b/.claude/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/cmd/config.go b/cmd/config.go index ac1abc3..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 isConfigEmpty(cfg) { - 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, ", ")) @@ -229,23 +240,14 @@ func configShow(root string) { fmt.Printf(" require_docs_for: %s\n", strings.Join(cfg.Drift.RequireDocsFor, ", ")) } } + + 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..144681b 100644 --- a/cmd/config_more_test.go +++ b/cmd/config_more_test.go @@ -92,6 +92,44 @@ 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) + 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..c834cd2 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) @@ -1441,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 8feccdf..938cda2 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") @@ -677,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 f6d8f18..caa914e 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,52 @@ 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)}, + } + } + + 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{ + 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..f3437dd 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,120 @@ 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("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") + 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