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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
with:
distribution: goreleaser
version: latest
args: release ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run) && '--snapshot --clean' || '--clean' }}
args: release --config .goreleaser.full.yml ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run) && '--snapshot --clean' || '--clean' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ secrets.SCOOP_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ __pycache__/
.codemap/
/codemap-dev
/dev_codemap
/bundled-tools/
firebase-debug.log
firebalse-debug.log
coverage.out
Expand Down
146 changes: 146 additions & 0 deletions .goreleaser.full.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
version: 2

project_name: codemap

before:
hooks:
- go mod tidy
- ./scripts/download-bundled-astgrep.sh

builds:
- id: codemap
main: .
binary: codemap
env:
- CGO_ENABLED=0
ldflags:
- -s -w
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
ignore:
- goos: windows
goarch: arm64

archives:
- id: default
ids:
- codemap
formats:
- tar.gz
format_overrides:
- goos: windows
formats:
- zip
files:
- README.md
- LICENSE*
- src: scanner/sg-rules/*
dst: sg-rules
- id: full
ids:
- codemap
name_template: '{{ .ProjectName }}-full_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
formats:
- tar.gz
format_overrides:
- goos: windows
formats:
- zip
files:
- README.md
- LICENSE*
- src: scanner/sg-rules/*
dst: sg-rules
- src: bundled-tools/{{ .Os }}_{{ .Arch }}/ast-grep{{ if eq .Os "windows" }}.exe{{ end }}
strip_parent: true
- src: bundled-tools/{{ .Os }}_{{ .Arch }}/sg{{ if eq .Os "windows" }}.exe{{ end }}
strip_parent: true

checksum:
name_template: 'checksums.txt'

changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^chore:'

release:
github:
owner: JordanCoin
name: codemap

brews:
- repository:
owner: JordanCoin
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
ids:
- default
directory: .
homepage: https://github.com/JordanCoin/codemap
description: Generate a brain map of your codebase for LLM context
license: MIT
dependencies:
- name: ast-grep
install: |
bin.install "codemap"
(pkgshare/"sg-rules").install Dir["sg-rules/*.yml"] if Dir.exist?("sg-rules")
caveats: |
The --deps mode uses ast-grep for code analysis.
test: |
system "#{bin}/codemap", "--help"

scoops:
- repository:
owner: JordanCoin
name: scoop-codemap
token: "{{ .Env.SCOOP_TOKEN }}"
ids:
- default
homepage: https://github.com/JordanCoin/codemap
description: Generate a brain map of your codebase for LLM context
license: MIT
depends:
- ast-grep

winget:
- name: codemap
ids:
- default
publisher: JordanCoin
short_description: Generate a brain map of your codebase for LLM context
license: MIT
publisher_url: https://github.com/JordanCoin
publisher_support_url: https://github.com/JordanCoin/codemap/issues
package_identifier: JordanCoin.codemap
homepage: https://github.com/JordanCoin/codemap
description: |
codemap generates a compact, structured "brain map" of your codebase
that LLMs can instantly understand. One command gives instant
architectural context without burning tokens.
license_url: https://github.com/JordanCoin/codemap/blob/main/LICENSE
tags:
- cli
- developer-tools
- ai
- llm
- code-analysis
repository:
owner: JordanCoin
name: winget-pkgs
branch: "JordanCoin.codemap-{{.Version}}"
token: "{{ .Env.SCOOP_TOKEN }}"
pull_request:
enabled: true
base:
owner: microsoft
name: winget-pkgs
branch: master
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ builds:

archives:
- id: default
ids:
- codemap
formats:
- tar.gz
format_overrides:
Expand Down
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,40 @@ scoop install codemap

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

## Tarball / CI Install

If you install `codemap` from a release tarball, also install `ast-grep` separately for `--deps`.
The tarball includes `codemap` and the bundled rules, but not the `ast-grep` executable.

Example for Alpine-based CI:

```bash
apk add --no-cache curl jq bash python3 py3-pip

ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; fi

CODEMAP_VERSION=$(curl -fsSL https://api.github.com/repos/JordanCoin/codemap/releases/latest | jq -r '.tag_name' | tr -d 'v')
curl -fsSL "https://github.com/JordanCoin/codemap/releases/download/v${CODEMAP_VERSION}/codemap_${CODEMAP_VERSION}_linux_${ARCH}.tar.gz" \
| tar xz -C /usr/local/bin/ codemap

python3 -m pip install --no-cache-dir ast-grep-cli
```

If you want a self-contained archive for CI/CD, use the `codemap-full` release artifact instead.
It includes `codemap`, `ast-grep`, and `sg` in one archive so `--deps` works after extraction.

```bash
apk add --no-cache curl jq bash

ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; fi

CODEMAP_VERSION=$(curl -fsSL https://api.github.com/repos/JordanCoin/codemap/releases/latest | jq -r '.tag_name' | tr -d 'v')
curl -fsSL "https://github.com/JordanCoin/codemap/releases/download/v${CODEMAP_VERSION}/codemap-full_${CODEMAP_VERSION}_linux_${ARCH}.tar.gz" \
| tar xz -C /usr/local/bin/ codemap ast-grep sg
```

## Recommended Setup (Hooks + Daemon + Config)

No repo clone is required for normal users.
Expand Down Expand Up @@ -182,6 +216,23 @@ Uses a shallow clone to a temp directory (fast, no history, auto-cleanup). If yo

> Powered by [ast-grep](https://ast-grep.github.io/). Install via `brew install ast-grep` for `--deps` mode.

## Blast Radius Bundle

If you want a compact review bundle for another LLM, combine the three high-signal views:

```bash
codemap --json --diff --ref main .
codemap --json --deps --diff --ref main .
codemap --json --importers path/to/file .
```

For a reusable wrapper that emits either Markdown or a single JSON object:

```bash
bash scripts/codemap-blast-radius.sh --markdown --ref main .
bash scripts/codemap-blast-radius.sh --json --ref main .
```

## Claude Integration

**Hooks (Recommended)** — Automatic context at session start, before/after edits, and more.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/modelcontextprotocol/go-sdk v1.1.0
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
golang.org/x/term v0.37.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -33,5 +34,4 @@ require (
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
79 changes: 55 additions & 24 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func main() {

// Importers mode - check file impact
if *importersMode != "" {
runImportersMode(absRoot, *importersMode)
runImportersMode(absRoot, *importersMode, *jsonMode)
return
}

Expand Down Expand Up @@ -407,13 +407,20 @@ func runDepsMode(absRoot, root string, jsonMode bool, diffRef string, changedFil
} else {
analyses, err = scanForDepsWithHint(root)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "The --deps feature requires ast-grep. Install it with:")
fmt.Fprintln(os.Stderr, " brew install ast-grep # macOS/Linux (installs as 'sg')")
fmt.Fprintln(os.Stderr, " cargo install ast-grep # via Rust (installs as 'ast-grep')")
fmt.Fprintln(os.Stderr, " pipx install ast-grep # via Python (installs as 'ast-grep')")
fmt.Fprintln(os.Stderr, "")
if errors.Is(err, scanner.ErrAstGrepNotFound) {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "The --deps feature requires ast-grep. Install it with:")
fmt.Fprintln(os.Stderr, " brew install ast-grep # macOS/Linux (installs as 'sg')")
fmt.Fprintln(os.Stderr, " cargo install ast-grep # installs as 'ast-grep'")
fmt.Fprintln(os.Stderr, " pipx install ast-grep # installs as 'ast-grep'")
fmt.Fprintln(os.Stderr, " python3 -m pip install ast-grep-cli")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Standard release tarballs ship codemap without the ast-grep binary.")
fmt.Fprintln(os.Stderr, "Use a codemap-full archive for self-contained CI installs, or install ast-grep separately.")
} else {
fmt.Fprintf(os.Stderr, "Error scanning dependencies: %v\n", err)
}
os.Exit(1)
}
externalDeps = scanner.ReadExternalDeps(absRoot)
Expand Down Expand Up @@ -529,11 +536,10 @@ func runWatchMode(root string, verbose bool) {
fmt.Printf(" Events logged: %d\n", len(events))
}

func runImportersMode(root, file string) {
func buildImportersReport(root, file string) (scanner.ImportersReport, error) {
fg, err := scanner.BuildFileGraph(root)
if err != nil {
fmt.Fprintf(os.Stderr, "Error building file graph: %v\n", err)
os.Exit(1)
return scanner.ImportersReport{}, err
}

// Handle absolute paths - convert to relative
Expand All @@ -544,8 +550,41 @@ func runImportersMode(root, file string) {
}

importers := fg.Importers[file]
imports := fg.Imports[file]
report := scanner.ImportersReport{
Root: root,
Mode: "importers",
File: file,
Importers: append([]string(nil), importers...),
Imports: append([]string(nil), imports...),
ImporterCount: len(importers),
IsHub: len(importers) >= 3,
}

for _, imp := range imports {
if fg.IsHub(imp) {
report.HubImports = append(report.HubImports, imp)
}
}

return report, nil
}

func runImportersMode(root, file string, jsonMode bool) {
report, err := buildImportersReport(root, file)
if err != nil {
fmt.Fprintf(os.Stderr, "Error building file graph: %v\n", err)
os.Exit(1)
}

if jsonMode {
_ = json.NewEncoder(os.Stdout).Encode(report)
return
}

importers := report.Importers
if len(importers) >= 3 {
fmt.Printf("⚠️ HUB FILE: %s\n", file)
fmt.Printf("⚠️ HUB FILE: %s\n", report.File)
fmt.Printf(" Imported by %d files - changes have wide impact!\n", len(importers))
fmt.Println()
fmt.Println(" Dependents:")
Expand All @@ -557,26 +596,18 @@ func runImportersMode(root, file string) {
fmt.Printf(" • %s\n", imp)
}
} else if len(importers) > 0 {
fmt.Printf("📍 File: %s\n", file)
fmt.Printf("📍 File: %s\n", report.File)
fmt.Printf(" Imported by %d file(s)\n", len(importers))
for _, imp := range importers {
fmt.Printf(" • %s\n", imp)
}
}

// Also check if this file imports any hubs
imports := fg.Imports[file]
var hubImports []string
for _, imp := range imports {
if fg.IsHub(imp) {
hubImports = append(hubImports, imp)
}
}
if len(hubImports) > 0 {
if len(report.HubImports) > 0 {
if len(importers) == 0 {
fmt.Printf("📍 File: %s\n", file)
fmt.Printf("📍 File: %s\n", report.File)
}
fmt.Printf(" Imports %d hub(s): %s\n", len(hubImports), strings.Join(hubImports, ", "))
fmt.Printf(" Imports %d hub(s): %s\n", len(report.HubImports), strings.Join(report.HubImports, ", "))
}
}

Expand Down
Loading
Loading