Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# 🛑 STOP — Run codemap before ANY task

## Repo Root Requirement (Critical)

Run codemap from the git repository root. Hooks and context files resolve from the current working directory, so running from a subdirectory can break hook context.

```bash
cd "$(git rev-parse --show-toplevel)"
```

`codemap` expects these at repo root:
- `.git/`
- `.codemap/`
- `.claude/settings.local.json` (project-local hooks)

```bash
codemap . # Project structure
codemap --deps # How files connect
Expand Down
68 changes: 58 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,66 @@ scoop install codemap

> Other options: [Releases](https://github.com/JordanCoin/codemap/releases) | `go install` | Build from source

## Quick Start
## Recommended Setup (Hooks + Daemon + Config)

No repo clone is required for normal users.
Run setup from your git repo root (not a subdirectory), or hooks may not resolve project context.

```bash
# install codemap first (package manager)
brew tap JordanCoin/tap && brew install codemap

# then run setup inside your project
cd /path/to/your/project
codemap setup
```

`codemap setup` is the default onboarding path and configures the pieces that make codemap most useful with Claude:
- creates `.codemap/config.json` (if missing) with auto-detected language filters
- installs codemap hooks into `.claude/settings.local.json` (project-local by default)
- hooks automatically start/read daemon state on session start

Use global Claude settings instead of project-local settings:

```bash
codemap setup --global
```

Windows equivalent:

```bash
scoop bucket add codemap https://github.com/JordanCoin/scoop-codemap
scoop install codemap
cd C:\path\to\your\project
codemap setup
```

Optional helper scripts (mainly for contributors running from this repo):
- macOS/Linux: `./scripts/onboard.sh /path/to/your/project`
- Windows (PowerShell): `./scripts/onboard.ps1 -ProjectRoot C:\path\to\your\project`

## Verify Setup

1. Restart Claude Code or open a new session.
2. At session start, you should see codemap project context.
3. Edit a file and confirm pre/post edit hook context appears.

## Daily Commands

```bash
codemap . # Fast tree/context view (respects .codemap/config.json)
codemap --diff # What changed vs main
codemap handoff . # Save layered handoff for cross-agent continuation
codemap --deps . # Dependency flow (requires ast-grep)
```

## Other Commands

```bash
codemap . # Project tree
codemap --only swift . # Just Swift files
codemap --exclude .xcassets,Fonts,.png . # Hide assets
codemap --depth 2 . # Limit depth
codemap --diff # What changed vs main
codemap --deps . # Dependency flow
codemap config init # Create .codemap/config.json
codemap handoff . # Save cross-agent handoff summary
codemap github.com/user/repo # Remote GitHub repo
codemap --only swift .
codemap --exclude .xcassets,Fonts,.png .
codemap --depth 2 .
codemap github.com/user/repo
```

## Options
Expand Down
103 changes: 57 additions & 46 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
Expand All @@ -12,6 +13,15 @@ import (
"codemap/scanner"
)

var errConfigExists = errors.New("config already exists")

type configInitResult struct {
Path string
TopExts []string
TotalFiles int
MatchedFiles int
}

// nonCodeExtensions are extensions excluded from "config init" auto-detection.
// These are documentation, data, or lock files that rarely represent the
// project's primary code.
Expand Down Expand Up @@ -46,106 +56,107 @@ func RunConfig(subCmd, root string) {
}

func configInit(root string) {
cfgPath := config.ConfigPath(root)

// Warn if config already exists
if _, err := os.Stat(cfgPath); err == nil {
result, err := initProjectConfig(root)
if errors.Is(err, errConfigExists) {
cfgPath := config.ConfigPath(root)
fmt.Fprintf(os.Stderr, "Config already exists: %s\n", cfgPath)
fmt.Fprintln(os.Stderr, "Use 'codemap config show' to view it, or edit directly.")
os.Exit(1)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating config: %v\n", err)
os.Exit(1)
}

fmt.Printf("Created %s\n", result.Path)
fmt.Println()
if len(result.TopExts) == 0 {
fmt.Println("No code extensions detected — wrote empty config.")
} else {
fmt.Printf(" only: %s\n", strings.Join(result.TopExts, ", "))
if result.TotalFiles > 0 {
fmt.Printf(" (%d of %d files)\n", result.MatchedFiles, result.TotalFiles)
}
}
fmt.Println()
fmt.Println("Edit the file to add 'exclude' patterns or adjust 'depth'.")
}

func initProjectConfig(root string) (configInitResult, error) {
cfgPath := config.ConfigPath(root)
result := configInitResult{Path: cfgPath}

if _, err := os.Stat(cfgPath); err == nil {
return result, errConfigExists
} else if err != nil && !os.IsNotExist(err) {
return result, err
}

// Scan the repo to find top extensions
gitCache := scanner.NewGitIgnoreCache(root)
files, err := scanner.ScanFiles(root, gitCache, nil, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error scanning files: %v\n", err)
os.Exit(1)
return result, fmt.Errorf("scan files: %w", err)
}

// Count extensions
extCount := make(map[string]int)
for _, f := range files {
ext := strings.TrimPrefix(f.Ext, ".")
if ext == "" {
continue
}
ext = strings.ToLower(ext)
if nonCodeExtensions[ext] {
ext := strings.TrimPrefix(strings.ToLower(f.Ext), ".")
if ext == "" || nonCodeExtensions[ext] {
continue
}
extCount[ext]++
}

// Sort by frequency
type extEntry struct {
Ext string
Count int
}
var entries []extEntry
for ext, count := range extCount {
entries = append(entries, extEntry{ext, count})
entries = append(entries, extEntry{Ext: ext, Count: count})
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Count > entries[j].Count
})

// Take top 5
var topExts []string
for i, e := range entries {
if i >= 5 {
break
}
topExts = append(topExts, e.Ext)
result.TopExts = append(result.TopExts, e.Ext)
}

if len(topExts) == 0 {
fmt.Println("No code extensions detected — writing empty config.")
topExts = nil
}
cfg := config.ProjectConfig{Only: result.TopExts}

cfg := config.ProjectConfig{
Only: topExts,
}

// Ensure .codemap/ directory exists
if err := os.MkdirAll(filepath.Dir(cfgPath), 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating directory: %v\n", err)
os.Exit(1)
return result, fmt.Errorf("create .codemap directory: %w", err)
}

data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "Error encoding config: %v\n", err)
os.Exit(1)
return result, fmt.Errorf("encode config: %w", err)
}
data = append(data, '\n')

if err := os.WriteFile(cfgPath, data, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error writing config: %v\n", err)
os.Exit(1)
return result, fmt.Errorf("write config: %w", err)
}

fmt.Printf("Created %s\n", cfgPath)
fmt.Println()
fmt.Printf(" only: %s\n", strings.Join(topExts, ", "))
if len(files) > 0 && len(topExts) > 0 {
// Count how many files match the selected extensions
matchExts := make(map[string]bool)
for _, ext := range topExts {
result.TotalFiles = len(files)
if len(result.TopExts) > 0 {
matchExts := make(map[string]bool, len(result.TopExts))
for _, ext := range result.TopExts {
matchExts[ext] = true
}
matched := 0
for _, f := range files {
ext := strings.TrimPrefix(strings.ToLower(f.Ext), ".")
if matchExts[ext] {
matched++
result.MatchedFiles++
}
}
fmt.Printf(" (%d of %d files)\n", matched, len(files))
}
fmt.Println()
fmt.Println("Edit the file to add 'exclude' patterns or adjust 'depth'.")

return result, nil
}

func configShow(root string) {
Expand Down
Loading
Loading