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
3 changes: 2 additions & 1 deletion cmd/context_evidence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"codemap/analysis"
"codemap/internal/projectpath"
"codemap/scanner"
"codemap/watch"
)
Expand Down Expand Up @@ -252,7 +253,7 @@ func TestHookPromptSubmitDoesNotClaimRiskFromCachedGraph(t *testing.T) {
}
})

status, err := os.ReadFile(filepath.Join(root, ".codemap", "status"))
status, err := os.ReadFile(filepath.Join(projectpath.ProjectRuntimeDir(root), "status"))
if err != nil {
t.Fatal(err)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"codemap/internal/projectpath"
"codemap/watch"
)

Expand Down Expand Up @@ -92,7 +93,7 @@ func TestBuildContextEnvelopeFallsBackToConfiguredScanForLegacyState(t *testing.
if err != nil {
t.Fatal(err)
}
mustWriteFile(t, filepath.Join(root, ".codemap", "state.json"), string(legacyState))
mustWriteFile(t, filepath.Join(projectpath.ProjectRuntimeDir(root), "state.json"), string(legacyState))

envelope := buildContextEnvelope(root, "", true)

Expand Down
8 changes: 4 additions & 4 deletions cmd/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ func showLightweightDiffVsMain(root string) {

// getLastSessionEvents reads events.log for previous session context
func getLastSessionEvents(root string) []string {
eventsFile := filepath.Join(projectpath.RuntimeCodemapDir(root), "events.log")
eventsFile := filepath.Join(projectpath.ProjectRuntimeDir(root), "events.log")
f, err := os.Open(eventsFile)
if err != nil {
return nil
Expand Down Expand Up @@ -887,7 +887,7 @@ func hookPromptSubmit(root string) error {

// writeStatuslineState writes a tiny file for the statusline to read.
func writeStatuslineState(root string, intent TaskIntent) {
codemapDir := projectpath.RuntimeCodemapDir(root)
codemapDir := projectpath.ProjectRuntimeDir(root)
status := intent.Category
if intent.RiskLevel != "low" {
status += " " + intent.RiskLevel
Expand Down Expand Up @@ -1379,7 +1379,7 @@ func showSessionProgress(root, sessionID string) {

// hookPreCompact saves hub state before context compaction
func hookPreCompact(root string) error {
codemapDir := projectpath.RuntimeCodemapDir(root)
codemapDir := projectpath.ProjectRuntimeDir(root)
if err := os.MkdirAll(codemapDir, 0755); err != nil {
return err
}
Expand Down Expand Up @@ -1694,7 +1694,7 @@ func updateSessionLease(root, sessionID string, active bool, now time.Time, acti
}
return nil
}
codemapDir := projectpath.RuntimeCodemapDir(root)
codemapDir := projectpath.ProjectRuntimeDir(root)
if err := os.MkdirAll(codemapDir, 0o755); err != nil {
return err
}
Expand Down
30 changes: 18 additions & 12 deletions cmd/hooks_more_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func mustJSONInput(t *testing.T, v any) string {
func writeProjectConfig(t *testing.T, root string, cfg config.ProjectConfig) {
t.Helper()

if err := os.MkdirAll(filepath.Join(root, ".codemap"), 0o755); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(root), 0o755); err != nil {
t.Fatal(err)
}
data, err := json.Marshal(cfg)
Expand All @@ -144,14 +144,14 @@ func writeProjectConfig(t *testing.T, root string, cfg config.ProjectConfig) {
func writeStateOnly(t *testing.T, root string, state watch.State) {
t.Helper()

if err := os.MkdirAll(filepath.Join(root, ".codemap"), 0o755); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(root), 0o755); err != nil {
t.Fatal(err)
}
data, err := json.Marshal(state)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, ".codemap", "state.json"), data, 0o644); err != nil {
if err := os.WriteFile(filepath.Join(projectpath.ProjectRuntimeDir(root), "state.json"), data, 0o644); err != nil {
t.Fatal(err)
}
}
Expand Down Expand Up @@ -180,12 +180,12 @@ func TestWaitForDaemonState(t *testing.T) {
go func() {
defer close(done)
time.Sleep(150 * time.Millisecond)
_ = os.MkdirAll(filepath.Join(root, ".codemap"), 0o755)
_ = os.MkdirAll(projectpath.ProjectRuntimeDir(root), 0o755)
data, _ := json.Marshal(watch.State{
UpdatedAt: time.Now(),
FileCount: 7,
})
_ = os.WriteFile(filepath.Join(root, ".codemap", "state.json"), data, 0o644)
_ = os.WriteFile(filepath.Join(projectpath.ProjectRuntimeDir(root), "state.json"), data, 0o644)
}()

state := waitForDaemonState(root, time.Second)
Expand Down Expand Up @@ -890,7 +890,7 @@ func TestHookFilesUseSetupRoot(t *testing.T) {
setupRoot := t.TempDir()
projectpath.SetSetupRoot(setupRoot)
t.Cleanup(projectpath.ResetSetupRoot)
codemapDir := filepath.Join(setupRoot, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(projectRoot)
if err := os.MkdirAll(codemapDir, 0o755); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -921,23 +921,29 @@ func TestAutomaticLinkedWorktreeUsesLocalHookState(t *testing.T) {
if err := os.MkdirAll(gitDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(primary, ".codemap"), 0o755); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(primary), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(primary, ".codemap", "events.log"), []byte("primary event\n"), 0o644); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(primary), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(projectpath.ProjectRuntimeDir(primary), "events.log"), []byte("primary event\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(gitDir, "commondir"), []byte("../..\n"), 0o644); err != nil {
t.Fatal(err)
}
linked := filepath.Join(t.TempDir(), "linked")
if err := os.MkdirAll(filepath.Join(linked, ".codemap"), 0o755); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(linked), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(linked, ".git"), []byte("gitdir: "+gitDir+"\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(linked, ".codemap", "events.log"), []byte("linked event\n"), 0o644); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(linked), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(projectpath.ProjectRuntimeDir(linked), "events.log"), []byte("linked event\n"), 0o644); err != nil {
t.Fatal(err)
}
linked, _ = filepath.EvalSymlinks(linked)
Expand All @@ -947,7 +953,7 @@ func TestAutomaticLinkedWorktreeUsesLocalHookState(t *testing.T) {
t.Fatalf("getLastSessionEvents() = %#v, want linked event", events)
}
writeStatuslineState(linked, TaskIntent{Category: "feature", RiskLevel: "low"})
if _, err := os.Stat(filepath.Join(linked, ".codemap", "status")); err != nil {
if _, err := os.Stat(filepath.Join(projectpath.ProjectRuntimeDir(linked), "status")); err != nil {
t.Fatalf("linked status missing: %v", err)
}
if _, err := os.Stat(filepath.Join(primary, ".codemap", "status")); !os.IsNotExist(err) {
Expand All @@ -956,7 +962,7 @@ func TestAutomaticLinkedWorktreeUsesLocalHookState(t *testing.T) {
if err := updateSessionLease(linked, "session-1", true, time.Now(), nil); err != nil {
t.Fatalf("updateSessionLease() error: %v", err)
}
entries, err := os.ReadDir(filepath.Join(linked, ".codemap", "sessions"))
entries, err := os.ReadDir(filepath.Join(projectpath.ProjectRuntimeDir(linked), "sessions"))
if err != nil || len(entries) != 1 {
t.Fatalf("linked session lease entries = %d, err = %v", len(entries), err)
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/hooks_provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"codemap/internal/projectpath"
"codemap/watch"
)

Expand All @@ -29,10 +30,10 @@ func writeProvenanceState(t *testing.T, root string, paths []string) {
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(root, ".codemap"), 0o755); err != nil {
if err := os.MkdirAll(projectpath.ProjectRuntimeDir(root), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, ".codemap", "state.json"), data, 0o644); err != nil {
if err := os.WriteFile(filepath.Join(projectpath.ProjectRuntimeDir(root), "state.json"), data, 0o644); err != nil {
t.Fatal(err)
}
}
Expand Down
19 changes: 10 additions & 9 deletions cmd/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"codemap/config"
"codemap/handoff"
"codemap/internal/projectpath"
"codemap/limits"
"codemap/watch"
)
Expand Down Expand Up @@ -243,7 +244,7 @@ func TestShouldRestartDaemon(t *testing.T) {
t.Run("running without state returns true", func(t *testing.T) {
withOwnedDaemonProcess(t, func(string) bool { return true })
root := t.TempDir()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
if err := os.MkdirAll(codemapDir, 0755); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -802,7 +803,7 @@ func captureOutput(f func()) string {
// and a PID file pointing to the current process so IsRunning returns true.
func writeWatchState(t *testing.T, root string, state watch.State) {
t.Helper()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
if err := os.MkdirAll(codemapDir, 0755); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -830,7 +831,7 @@ func TestGetLastSessionEvents(t *testing.T) {

t.Run("empty file returns nil", func(t *testing.T) {
root := t.TempDir()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
if err := os.MkdirAll(codemapDir, 0755); err != nil {
t.Fatal(err)
}
Expand All @@ -844,7 +845,7 @@ func TestGetLastSessionEvents(t *testing.T) {

t.Run("returns all lines when fewer than 20", func(t *testing.T) {
root := t.TempDir()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
os.MkdirAll(codemapDir, 0755)
lines := "ts|WRITE|a.go\nts|WRITE|b.go\nts|WRITE|c.go"
os.WriteFile(filepath.Join(codemapDir, "events.log"), []byte(lines), 0644)
Expand All @@ -857,7 +858,7 @@ func TestGetLastSessionEvents(t *testing.T) {

t.Run("caps at 20 lines for large log (context bloat protection)", func(t *testing.T) {
root := t.TempDir()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
os.MkdirAll(codemapDir, 0755)

var sb strings.Builder
Expand All @@ -881,7 +882,7 @@ func TestGetLastSessionEvents(t *testing.T) {

t.Run("reads only tail of huge event log and still returns latest 20", func(t *testing.T) {
root := t.TempDir()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
os.MkdirAll(codemapDir, 0755)

var sb strings.Builder
Expand All @@ -904,7 +905,7 @@ func TestGetLastSessionEvents(t *testing.T) {

t.Run("skips blank lines when counting", func(t *testing.T) {
root := t.TempDir()
codemapDir := filepath.Join(root, ".codemap")
codemapDir := projectpath.ProjectRuntimeDir(root)
os.MkdirAll(codemapDir, 0755)
// 5 real entries surrounded by blank lines
content := "\nts|WRITE|a.go\n\nts|WRITE|b.go\n\n\nts|WRITE|c.go\n\nts|WRITE|d.go\nts|WRITE|e.go\n"
Expand Down Expand Up @@ -1141,7 +1142,7 @@ func TestHookPreCompact(t *testing.T) {
if out != "" {
t.Errorf("expected no output when no hubs, got %q", out)
}
hubsFile := filepath.Join(root, ".codemap", "hubs.txt")
hubsFile := filepath.Join(projectpath.ProjectRuntimeDir(root), "hubs.txt")
if _, err := os.Stat(hubsFile); !os.IsNotExist(err) {
t.Error("expected no hubs.txt when hub list is empty")
}
Expand Down Expand Up @@ -1173,7 +1174,7 @@ func TestHookPreCompact(t *testing.T) {
t.Errorf("expected hubs.txt mention, got %q", out)
}

hubsFile := filepath.Join(root, ".codemap", "hubs.txt")
hubsFile := filepath.Join(projectpath.ProjectRuntimeDir(root), "hubs.txt")
content, err := os.ReadFile(hubsFile)
if err != nil {
t.Fatalf("expected hubs.txt to be created: %v", err)
Expand Down
6 changes: 4 additions & 2 deletions cmd/setup_review_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"strings"
"testing"
"time"

"codemap/internal/projectpath"
)

func TestEnsureClaudeMCPPreservesLargeIntegersAndUnknownKeys(t *testing.T) {
Expand Down Expand Up @@ -219,7 +221,7 @@ func TestUpdateSessionLeaseTracksActiveSessionsAndPrunesStale(t *testing.T) {
}

// Age session-b's lease past the TTL; the next update must reclaim it.
leaseDir := filepath.Join(root, ".codemap", "sessions")
leaseDir := filepath.Join(projectpath.ProjectRuntimeDir(root), "sessions")
entries, err := os.ReadDir(leaseDir)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -247,7 +249,7 @@ func TestUpdateSessionLeaseTracksActiveSessionsAndPrunesStale(t *testing.T) {

func TestUpdateSessionLeaseReclaimsStaleLock(t *testing.T) {
root := t.TempDir()
lockPath := filepath.Join(root, ".codemap", "sessions.lock")
lockPath := filepath.Join(projectpath.ProjectRuntimeDir(root), "sessions.lock")
if err := os.MkdirAll(lockPath, 0o700); err != nil {
t.Fatal(err)
}
Expand Down
52 changes: 52 additions & 0 deletions handoff/handoff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"codemap/internal/projectpath"
"codemap/watch"
)

Expand Down Expand Up @@ -331,3 +332,54 @@ func TestMetricsLogCapped(t *testing.T) {
t.Fatalf("expected %d metrics lines after cap, got %d", maxMetricsLines, len(lines))
}
}

func TestStoragePathsUseSetupRoot(t *testing.T) {
projectRoot := t.TempDir()
setupRoot := t.TempDir()
projectpath.SetSetupRoot(setupRoot)
t.Cleanup(projectpath.ResetSetupRoot)

want := filepath.Join(projectpath.ProjectRuntimeDir(projectRoot), latestFilename)
if got := LatestPath(projectRoot); got != want {
t.Fatalf("LatestPath() = %q, want %q", got, want)
}
}

func TestAutomaticLinkedWorktreesUseDistinctHandoffStorage(t *testing.T) {
projectpath.ResetSetupRoot()
t.Cleanup(projectpath.ResetSetupRoot)
primary := t.TempDir()
if err := os.MkdirAll(filepath.Join(primary, ".codemap"), 0o755); err != nil {
t.Fatal(err)
}
makeLinked := func(name string) string {
gitDir := filepath.Join(primary, ".git", "worktrees", name)
if err := os.MkdirAll(gitDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(gitDir, "commondir"), []byte("../..\n"), 0o644); err != nil {
t.Fatal(err)
}
linked := filepath.Join(t.TempDir(), name)
if err := os.MkdirAll(linked, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(linked, ".git"), []byte("gitdir: "+gitDir+"\n"), 0o644); err != nil {
t.Fatal(err)
}
linked, _ = filepath.EvalSymlinks(linked)
return linked
}
linkedA := makeLinked("a")
linkedB := makeLinked("b")

if got, want := LatestPath(linkedA), filepath.Join(projectpath.ProjectRuntimeDir(linkedA), latestFilename); got != want {
t.Fatalf("LatestPath(A) = %q, want %q", got, want)
}
if got, want := LatestPath(linkedB), filepath.Join(projectpath.ProjectRuntimeDir(linkedB), latestFilename); got != want {
t.Fatalf("LatestPath(B) = %q, want %q", got, want)
}
if LatestPath(linkedA) == LatestPath(linkedB) {
t.Fatal("automatic linked worktrees unexpectedly share handoff storage")
}
}
Loading
Loading