You are implementing Task 6 of the birdseye CLI implementation plan.

## Working Environment

- **Worktree path:** `/Users/steve.briggs/projects/operations/.claude/worktrees/birdseye`
- **Git branch:** `worktree-birdseye`
- Recent commits: Task 4 + lint (`67c8b68`, `67e6e70`), Task 5 (`03e7d39`).
- Use absolute paths or `cd` into the worktree.
- **NEVER push.** **NEVER use `git add .` / `git add -A`.**

## Context

This task creates a NEW package `internal/registry` in `apps/birdseye/`. The package will be the seam between birdseye and the Crow service client: it owns "talk to crow + cache" so the diff/TUI layers never touch the network. Tasks 7-8 add the disk cache and the `Registry.Fetch`/`FetchPair` methods.

Your job is **only** the `Cache` interface and an in-memory implementation. The interface is small (2 methods) and the implementation is straightforward (a sync.RWMutex-protected map). Production will use a disk-backed cache from Task 7.

Strict TDD: write the failing test, run it, then implement.

## Task Description (Verbatim)

### Task 6: `internal/registry` — in-memory cache + interface

**Files:**
- Create: `apps/birdseye/internal/registry/cache.go`
- Create: `apps/birdseye/internal/registry/cache_test.go`

- [ ] **Step 1: Write failing test** at `apps/birdseye/internal/registry/cache_test.go`:

```go
package registry

import (
	"testing"

	"github.com/immuta/operations/services/crow/pkg/api"
)

func mod(version string) api.Module {
	return api.Module{
		ModuleIdentifier: api.ModuleIdentifier{Namespace: "iac", Name: "comply", Provider: "aws"},
		Version:          version,
	}
}

func TestMemCacheRoundTrip(t *testing.T) {
	c := NewMemCache()
	if _, ok, err := c.Get(mod("1.0.0")); err != nil || ok {
		t.Fatalf("expected miss; got ok=%v err=%v", ok, err)
	}
	if err := c.Put(mod("1.0.0"), []byte("payload")); err != nil {
		t.Fatalf("Put: %v", err)
	}
	got, ok, err := c.Get(mod("1.0.0"))
	if err != nil || !ok {
		t.Fatalf("expected hit; got ok=%v err=%v", ok, err)
	}
	if string(got) != "payload" {
		t.Errorf("body = %q", got)
	}
}
```

- [ ] **Step 2: Run, expect FAIL** (`undefined: NewMemCache`)

Run: `cd /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye && go test ./internal/registry/... -v`
Expected: compile error.

- [ ] **Step 3: Implement `apps/birdseye/internal/registry/cache.go`**

```go
// Package registry talks to a Crow server through its typed client and
// caches module tarballs on disk so repeated diffs against the same
// versions stay fast.
package registry

import (
	"sync"

	"github.com/immuta/operations/services/crow/pkg/api"
)

// Cache is the storage seam used by Registry. Production uses DiskCache;
// tests inject MemCache.
type Cache interface {
	Get(api.Module) ([]byte, bool, error)
	Put(api.Module, []byte) error
}

// MemCache is a thread-safe in-memory Cache implementation.
type MemCache struct {
	mu    sync.RWMutex
	items map[string][]byte
}

// NewMemCache returns an empty MemCache.
func NewMemCache() *MemCache {
	return &MemCache{items: make(map[string][]byte)}
}

func memKey(m api.Module) string {
	return m.Namespace + "/" + m.Name + "/" + m.Provider + "/" + m.Version
}

func (c *MemCache) Get(m api.Module) ([]byte, bool, error) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	b, ok := c.items[memKey(m)]
	if !ok {
		return nil, false, nil
	}
	return b, true, nil
}

func (c *MemCache) Put(m api.Module, body []byte) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[memKey(m)] = body
	return nil
}
```

- [ ] **Step 4: Run, expect PASS**

Run: `cd /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye && go test -race ./internal/registry/... -v`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
cd /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye && git add apps/birdseye/internal/registry/cache.go apps/birdseye/internal/registry/cache_test.go && git commit -m "feat(birdseye): registry cache interface and in-memory impl"
```

## Notes & Style

- The package doc comment in `cache.go` is the FIRST package doc — keep it accurate.
- Note that the Cache returns `(body, hit, error)` not `(body, error)` — the boolean disambiguates "not present" from "present but error". Keep that signature.
- `mod(version)` helper in the test file is reused by Tasks 7 and 8 — do NOT rename it.

## Code Organization

Single file `cache.go` is enough for now. Task 7 will add `DiskCache` to the same `cache.go` file.

## Self-Review Checklist

- [ ] TDD followed: test written, run, observed fail; then implementation
- [ ] `TestMemCacheRoundTrip` passes under `-race`
- [ ] `MemCache` is concurrency-safe (uses `sync.RWMutex`)
- [ ] `Cache` interface has exactly 2 methods with the signatures shown
- [ ] `NewMemCache` returns a `*MemCache` with `items` pre-initialized to empty map
- [ ] `memKey` uses the four module fields separated by `/`
- [ ] Commit message exactly: `feat(birdseye): registry cache interface and in-memory impl`
- [ ] Only the two `apps/birdseye/internal/registry/` files staged

## Report Format

- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
- Files committed
- Commit SHA
- Test result
- Anything unexpected


- migrate-flux-app: Use when migrating a Flux CD application from the infrastructure/kubernetes/application-deployments repo to the application-deployment repo. Covers copying app folders, updating kustomizations, committing, pushing, and creating merge requests.
- app-infra-generate: Generate an application-infrastructure Terraform module from a YAML spec produced by app-infra-spec. Reads pattern files and a versions manifest to produce main.tf, variables.tf, outputs.tf, overlay guidance, and a triage list.
- app-infra-spec: Structured questionnaire to gather requirements for an application-infrastructure Terraform module. Produces a YAML spec for the app-infra-generate skill.
- obsidian-load: Load context from Obsidian notes in the `Claude/` directory into the current conversation.
- obsidian-save: Save current conversation context to an Obsidian note under the `Claude/` directory.
- mind:search: Search memories for specific content or patterns
- mind:stats: Show memory statistics and storage information
- mind:recent: Show recent memories and activity timeline
- mind:ask: Ask questions about memories and get context-aware answers
- superpowers:using-git-worktrees: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback
- superpowers:receiving-code-review: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation
- superpowers:finishing-a-development-branch: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
- superpowers:dispatching-parallel-agents: Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
- superpowers:writing-plans: Use when you have a spec or requirements for a multi-step task, before touching code
- superpowers:using-superpowers: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
- superpowers:writing-skills: Use when creating new skills, editing existing skills, or verifying skills work before deployment
- superpowers:requesting-code-review: Use when completing tasks, implementing major features, or before merging to verify work meets requirements
- superpowers:executing-plans: Use when you have a written implementation plan to execute in a separate session with review checkpoints
- superpowers:verification-before-completion: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
- superpowers:brainstorming: You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.
- superpowers:systematic-debugging: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
- superpowers:test-driven-development: Use when implementing any feature or bugfix, before writing implementation code
- superpowers:subagent-driven-development: Use when executing implementation plans with independent tasks in the current session
- mind:memory: Claude Mind - Search and manage Claude's persistent memory stored in a single portable .mv2 file
- mind:mind: Claude Mind - Search and manage Claude's persistent memory stored in a single portable .mv2 file
- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- simplify: Review changed code for reuse, quality, and efficiency, then fix any issues found.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled remote agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring remote agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run ("run this once at 3pm", "remind me to check X tomorrow").
- claude-api: Build, debug, and optimize Claude API / Anthropic SDK apps. Apps built with this skill should include prompt caching. Also handles migrating existing Claude API code between Claude model versions (4.5 → 4.6, 4.6 → 4.7, retired-model replacements).
TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`; user asks for the Claude API, Anthropic SDK, or Managed Agents; user adds/modifies/tunes a Claude feature (caching, thinking, compaction, tool use, batch, files, citations, memory) or model (Opus/Sonnet/Haiku) in a file; questions about prompt caching / cache hit rate in an Anthropic SDK project.
SKIP: file imports `openai`/other-provider SDK, filename like `*-openai.py`/`*-generic.py`, provider-neutral code, general programming/ML.
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a pull request
- security-review: Complete a security review of the pending changes on the current branch


I'll implement Task 6 following strict TDD. Let me start by checking the existing structure.


Bash

ls /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye/internal/ 2>/dev/null || echo "Directory not found"

Check existing internal directory structure


find /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye -name "go.mod" | head -5

Find go.mod for the birdseye app


drwxr-xr-x steve.briggs staff 288 B Tue May 12 23:12:50 2026 ..
drwxr-xr-x steve.briggs staff  96 B Tue May 12 23:12:50 2026 .
drwxr-xr-x steve.briggs staff 128 B Tue May 12 23:28:06 2026 archive


Check archive package structure


drwxr-xr-x steve.briggs staff  96 B  Tue May 12 23:12:50 2026 ..
.rw-r--r-- steve.briggs staff 2.7 KB Tue May 12 23:25:49 2026 extract.go
.rw-r--r-- steve.briggs staff 3.7 KB Tue May 12 23:28:06 2026 extract_test.go
drwxr-xr-x steve.briggs staff 128 B  Tue May 12 23:28:06 2026 .


1	module github.com/immuta/operations/apps/birdseye
2
3	go 1.25.5
4
5	require github.com/spf13/cobra v1.9.1
6
7	require (
8		github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect
9		github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
10		github.com/aws/aws-sdk-go-v2/config v1.32.9 // indirect
11		github.com/aws/aws-sdk-go-v2/credentials v1.19.9 // indirect
12		github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect
13		github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
14		github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
15		github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
16		github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect
17		github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
18		github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect
19		github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect
20		github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect
21		github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 // indirect
22		github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect
23		github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 // indirect
24		github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect
25		github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
26		github.com/aws/smithy-go v1.24.1 // indirect
27		github.com/davecgh/go-spew v1.1.1 // indirect
28		github.com/immuta/operations/services/crow v0.0.0-20260512210412-d8eb33139c1a // indirect
29		github.com/inconshreveable/mousetrap v1.1.0 // indirect
30		github.com/labstack/echo/v4 v4.15.0 // indirect
31		github.com/labstack/gommon v0.4.2 // indirect
32		github.com/mattn/go-colorable v0.1.14 // indirect
33		github.com/mattn/go-isatty v0.0.20 // indirect
34		github.com/pmezard/go-difflib v1.0.0 // indirect
35		github.com/spf13/pflag v1.0.6 // indirect
36		github.com/stretchr/objx v0.5.3 // indirect
37		github.com/stretchr/testify v1.11.1 // indirect
38		github.com/valyala/bytebufferpool v1.0.0 // indirect
39		github.com/valyala/fasttemplate v1.2.2 // indirect
40		golang.org/x/crypto v0.48.0 // indirect
41		golang.org/x/net v0.50.0 // indirect
42		golang.org/x/sys v0.41.0 // indirect
43		golang.org/x/text v0.34.0 // indirect
44		golang.org/x/time v0.14.0 // indirect
45		gopkg.in/yaml.v3 v3.0.1 // indirect
46	)
47
48	replace github.com/immuta/operations/services/crow => ../../services/crow
49
50	replace github.com/immuta/operations/libs/clog => ../../libs/clog
51


Good. The crow package is already in go.mod. Now let me verify the crow API types exist.


find /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/services/crow/pkg/api -name "*.go" | head -10

Check crow API package


grep -n "Module\|ModuleIdentifier" /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/services/crow/pkg/api/types.go | head -20

Check Module and ModuleIdentifier types


7:type ModuleIdentifier struct {
13:type Module struct {
14:	ModuleIdentifier
22:type ModuleVersions struct {
27:	Modules []ModuleVersions `json:"modules"`
39:	ModulesV1 string `json:"modules.v1"` //nolint:tagliatelle // external wire format


The types exist. Now let me create the registry directory and write the failing test first (TDD).

**Step 1: Write the failing test**


Create the registry package directory


(Bash completed with no output)


Write

package registry

import (
	"testing"

	"github.com/immuta/operations/services/crow/pkg/api"
)

func mod(version string) api.Module {
	return api.Module{
		ModuleIdentifier: api.ModuleIdentifier{Namespace: "iac", Name: "comply", Provider: "aws"},
		Version:          version,
	}
}

func TestMemCacheRoundTrip(t *testing.T) {
	c := NewMemCache()
	if _, ok, err := c.Get(mod("1.0.0")); err != nil || ok {
		t.Fatalf("expected miss; got ok=%v err=%v", ok, err)
	}
	if err := c.Put(mod("1.0.0"), []byte("payload")); err != nil {
		t.Fatalf("Put: %v", err)
	}
	got, ok, err := c.Get(mod("1.0.0"))
	if err != nil || !ok {
		t.Fatalf("expected hit; got ok=%v err=%v", ok, err)
	}
	if string(got) != "payload" {
		t.Errorf("body = %q", got)
	}
}


File created successfully at: /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye/internal/registry/cache_test.go (file state is current in your context — no need to Read it back)


**Step 2: Run, expect FAIL**


Bash

cd /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye && go test ./internal/registry/... -v 2>&1

Run test expecting compile failure


Error: Exit code 1
# github.com/immuta/operations/apps/birdseye/internal/registry [github.com/immuta/operations/apps/birdseye/internal/registry.test]
internal/registry/cache_test.go:17:7: undefined: NewMemCache
FAIL	github.com/immuta/operations/apps/birdseye/internal/registry [build failed]
FAIL


Compile error confirmed — `undefined: NewMemCache`. TDD step verified.

**Step 3: Implement `cache.go`**


Write

// Package registry talks to a Crow server through its typed client and
// caches module tarballs on disk so repeated diffs against the same
// versions stay fast.
package registry

import (
	"sync"

	"github.com/immuta/operations/services/crow/pkg/api"
)

// Cache is the storage seam used by Registry. Production uses DiskCache;
// tests inject MemCache.
type Cache interface {
	Get(api.Module) ([]byte, bool, error)
	Put(api.Module, []byte) error
}

// MemCache is a thread-safe in-memory Cache implementation.
type MemCache struct {
	mu    sync.RWMutex
	items map[string][]byte
}

// NewMemCache returns an empty MemCache.
func NewMemCache() *MemCache {
	return &MemCache{items: make(map[string][]byte)}
}

func memKey(m api.Module) string {
	return m.Namespace + "/" + m.Name + "/" + m.Provider + "/" + m.Version
}

func (c *MemCache) Get(m api.Module) ([]byte, bool, error) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	b, ok := c.items[memKey(m)]
	if !ok {
		return nil, false, nil
	}
	return b, true, nil
}

func (c *MemCache) Put(m api.Module, body []byte) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[memKey(m)] = body
	return nil
}


File created successfully at: /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye/internal/registry/cache.go (file state is current in your context — no need to Read it back)


**Step 4: Run, expect PASS**


Bash

cd /Users/steve.briggs/projects/operations/.claude/worktrees/birdseye/apps/birdseye && go test -race ./internal/registry/... -v 2>&1

Run tests with race detector expecting PASS


=== RUN   TestMemCacheRoundTrip
--- PASS: TestMemCacheRoundTrip (0.00s)
PASS
ok  	github.com/immuta/operations/apps/birdseye/internal/registry	1.585s


Tests pass with the race detector. Now commit.

**Step 5: Commit**


git add apps/birdseye/internal/registry/cache.go apps/birdseye/internal/registry/cache_test.go && git commit -m "feat(birdseye): registry cache interface and in-memory impl"

Stage and commit the two registry files


[worktree-birdseye 51ae45c] feat(birdseye): registry cache interface and in-memory impl
 2 files changed, 80 insertions(+)
 create mode 100644 apps/birdseye/internal/registry/cache.go
 create mode 100644 apps/birdseye/internal/registry/cache_test.go


---

**Status:** DONE

**Files committed:**
- `apps/birdseye/internal/registry/cache.go`
- `apps/birdseye/internal/registry/cache_test.go`

**Commit SHA:** `51ae45c`

**Test result:** `PASS` — `TestMemCacheRoundTrip` passes under `-race` in 1.585s.

**Nothing unexpected.** The crow `api.Module` / `api.ModuleIdentifier` types were already present in the go.mod replace directive and the types matched the test's usage exactly. TDD sequence was followed: test written → compile error confirmed (`undefined: NewMemCache`) → implementation added → green with race detector.