AI Context Management for a Go Monorepo: CLAUDE.md Hierarchy, go.work
How to manage AI tools context effectively in a Go monorepo. Root and per-service CLAUDE.md hierarchy, shared domain packages, cross-service boundaries.
Context Management for a Go Monorepo
Managing AI tools context in a Go monorepo is a real challenge as the team grows. Services multiply, the codebase gets larger, and one CLAUDE.md for everything is no longer enough. This article is a guide to setting up a scalable context hierarchy, from a universal root down to per-service overrides.
17.1 The Context Challenge in a Monorepo
Before discussing solutions, let’s first look at the shape of the problem. The monorepo structure below depicts the real conditions at Santekno Corp along with the context challenges that arise within it.
1Santekno Corp monorepo (a real example):
2
3santekno-shop/
4βββ services/
5β βββ order-service/ (~100K lines)
6β βββ payment-service/ (~80K lines)
7β βββ notification-service/ (~40K lines)
8β βββ catalog-service/ (~60K lines)
9βββ shared/
10β βββ domain/ (shared domain types)
11βββ CLAUDE.md (root: universal rules)
12βββ go.work (Go workspace file)
13
14Challenge:
151. A universal CLAUDE.md is too generic for service-specific patterns
162. The AI needs to know: "Which service am I working in?"
173. Cross-service dependency must NOT become a direct import
184. Shared types must be consistent across servicesThese four challenges all boil down to a single need: the AI must know which service it’s working in and what rules apply there β something a single context file cannot answer.
17.2 Solution: Context File Hierarchy
The answer to that challenge is a context hierarchy loaded from the deepest directory upward. The diagram below shows how Claude Code combines the service CLAUDE.md with the root CLAUDE.md.
1Context loading when claude runs from services/order-service/:
2
3services/order-service/CLAUDE.md (service-specific)
4 β "EXTENDS root"
5CLAUDE.md (root universal)
6
7= Combined context for the order-service session
8
9How Claude Code's hierarchy works:
101. Read CLAUDE.md from the current directory
112. Walk up to parent directories
123. Read all CLAUDE.md files found
134. Combine, with the current dir taking priorityThe “walk up” mechanism is the key: universal rules are written once at the root, specific rules are added at the service, and both are combined automatically without duplication.
17.3 Root CLAUDE.md: Universal Rules
The first layer of the hierarchy is the root CLAUDE.md that holds the rules applying to all services. The example below shows the minimum content that must exist at the root level.
1# santekno-shop/CLAUDE.md
2# Universal rules for ALL services
3
4## Go Version
5Go 1.22 across all services. No 1.23+ features.
6
7## Architecture (All Services)
8Clean Architecture: handler β usecase β repository β domain
9Same layer rules apply everywhere.
10
11## Error Handling (Universal)
12Repository not-found: return nil, nil (NEVER error)
13Error wrap: fmt.Errorf("pkg.Method: %w", err)
14HTTP error format: {"error": "UPPERCASE_CODE"}
15
16## Monetary Values (Critical)
17ALWAYS int64 cents via shared/domain/money.IDR
18NEVER float64, float32 for money
19
20## Shared Domain Types (MUST USE)
21money.IDR β monetary values
22event.BaseEvent β embedded in all Kafka events
23id.OrderID, id.UserID, id.ProductID β cross-service IDs
24
25## Cross-Service Rules (STRICT)
26NEVER import another service's internal packages
27Cross-service communication: Kafka events ONLY
28Shared types: shared/domain ONLY, no duplication
29
30## Testing
31testify/suite + gomock pattern. Universal.
32
33## Packages
34pgx/v5, go-redis/v9, echo/v4, confluent-kafka-go/v2
35go.uber.org/mock/gomock (NOT github.com/golang/mock)This root is deliberately kept generic: only rules that genuinely apply to every service β architecture, error handling, money types, and cross-service boundaries.
17.4 Service-Specific CLAUDE.md Template
The second layer is the per-service CLAUDE.md that adds specific details without repeating the universal rules. The template below shows the pattern for order-service.
1# services/order-service/CLAUDE.md
2# EXTENDS: ../../CLAUDE.md (ALL universal rules apply here)
3
4## Service Identity
5Name: order-service
6Port: 8081
7Purpose: Order lifecycle β creation to delivery
8
9## Kafka Topics (This Service)
10Produces:
11 - order.created (after successful order creation)
12 - order.confirmed (after payment.completed consumed)
13 - order.cancelled (after cancel with stock restore)
14
15Consumes:
16 - payment.completed β trigger order confirmation
17 - inventory.reserved β proceed with order processing
18
19## Redis Keys Pattern
20 order:{order_id}:lock β distributed lock (TTL: 30s)
21 order:{user_id}:recent β recent orders cache (TTL: 5min)
22
23## Service-Specific Error Codes
24Beyond universal codes, this service uses:
25 ORDER_NOT_FOUND, ORDER_NOT_CANCELLABLE
26 CANCEL_WINDOW_EXPIRED, ORDER_ALREADY_CONFIRMED
27 SHIPPING_ADDRESS_REQUIRED
28
29## Performance SLOs
30 GET /orders/:id β P99 < 50ms
31 POST /orders β P99 < 200ms
32 Batch ops β max 1000 items/batch
33
34## Domain Boundaries (STRICT)
35OWNS: Order, OrderItem, OrderStatus
36REFERENCES ONLY (by ID): User, Product, Payment, Inventory
37NEVER: import domain types from other servicesThe first line # EXTENDS: ../../CLAUDE.md is an important convention: it reminds the AI (and the developer) that all root rules still apply, and this file only adds β it does not replace.
17.5 Shared Domain Package
Consistency across services is guaranteed by a shared domain package that provides common types. The Go code below defines the money type, cross-service IDs, and the event envelope that all services must use.
1// shared/domain/money/money.go
2package money
3
4// IDR represents Indonesian Rupiah in cents.
5// ALWAYS use this for monetary values across all services.
6// Example: Rp 50,000 = IDR(5_000_000)
7type IDR int64
8
9func (m IDR) ToRupiah() float64 { return float64(m) / 100 }
10func FromRupiah(r float64) IDR { return IDR(r * 100) }
11
12// shared/domain/id/id.go
13package id
14
15import "github.com/google/uuid"
16
17// Cross-service ID type aliases.
18// Services MUST use these for cross-service references.
19// NEVER create custom UUID aliases in individual services.
20type (
21 OrderID = uuid.UUID
22 UserID = uuid.UUID
23 ProductID = uuid.UUID
24 PaymentID = uuid.UUID
25)
26
27// shared/domain/event/event.go
28package event
29
30import "time"
31
32// BaseEvent MUST be embedded in ALL Kafka event structs
33// across all services. Ensures consistent event envelope.
34type BaseEvent struct {
35 EventID string `json:"event_id"`
36 EventType string `json:"event_type"`
37 OccurredAt time.Time `json:"occurred_at"`
38 Version int `json:"version"`
39}By centralizing money.IDR, the ID aliases, and event.BaseEvent in one place, there’s no longer any risk of two services defining a different Amount type β the most common source of drift in a monorepo is eliminated.
17.6 go.work Setup
So that the AI and developers can resolve cross-module imports without a published module, the monorepo uses a Go workspace. The command and configuration below show the contents of go.work and the import that becomes valid because of it.
1# go.work file for the development workspace
2cat go.work
3
4# go 1.22
5#
6# use ./shared/domain
7# use ./services/order-service
8# use ./services/payment-service
9# use ./services/notification-service
10# use ./services/catalog-service
11
12# Benefit: the AI can resolve cross-service imports
13# without needing a published module.
14# Developers can import shared/domain from all services.
15
16# Example of an import made valid thanks to go.work:
17# services/order-service/internal/usecase/cancel_order.go
18
19import (
20 "github.com/santekno/santekno-shop/shared/domain/money"
21 "github.com/santekno/santekno-shop/shared/domain/event"
22 "github.com/santekno/santekno-shop/shared/domain/id"
23)Without go.work, the AI would be confused about resolving the shared/domain import; with this file, the entire workspace becomes a single unit understood by both the compiler and the AI tools.
17.7 A Cross-Service Feature with AI
A feature that spans two services is the real test for boundaries. The example below splits the cancel-order work into two Claude Code sessions, each touching only one service.
1# Feature: order cancel β trigger payment refund
2# Spans 2 services, cross-service communication via Kafka
3
4# Session 1: order-service side
5cd services/order-service
6claude
7> CLAUDE.md is loaded (universal + order-specific).
8> Implement CancelOrder that publishes an ORDER_CANCELLED event.
9>
10> Event payload that must be published:
11> OrderID id.OrderID
12> UserID id.UserID
13> Amount money.IDR
14> OccurredAt time.Time (embed event.BaseEvent)
15>
16> SCOPE: order-service only.
17> payment-service will consume this event for the refund.
18> No direct call to payment-service.
19
20# Session 2: payment-service side
21cd services/payment-service
22claude
23> CLAUDE.md is loaded (universal + payment-specific).
24> Implement a Kafka consumer for the ORDER_CANCELLED event.
25>
26> Event schema (from order-service):
27> @internal/domain/order/events.go
28>
29> When consuming:
30> 1. Create a refund record in the DB
31> 2. Call the Midtrans API to initiate the refund
32> 3. Publish a REFUND_INITIATED event
33>
34> BOUNDARY: payment-service only reacts to events.
35> No direct call to the order-service API.This two-session pattern enforces the boundary naturally: because each session only cds into one service, the AI is never tempted to create a direct cross-service import β communication stays over Kafka events.
17.8 Monorepo Developer Workflow
The most fundamental rule in a monorepo is to always cd into the service you’re working on before starting an AI session. The commands below show the correct habit and the one to avoid.
1# Day-to-day developer workflow in a monorepo:
2
3# MAIN RULE: ALWAYS cd into the service being worked on
4cd services/order-service
5claude
6# Claude loads: services/order-service/CLAUDE.md + root CLAUDE.md
7# Context: order-service specific + universal
8
9# DON'T: run claude from the monorepo root to code in a specific service
10# cd santekno-shop && claude <- this will load only the root CLAUDE.md
11# The AI won't know service-specific patterns
12
13# For cross-service impact analysis:
14# This is a use case where Gemini (1M context) excels
15# Or use Claude Code from the root with explicit context:
16cd santekno-shop
17claude
18> Analyze the impact of a change to the OrderCancelledEvent payload.
19> Services that need updating:
20> - services/payment-service (consumer)
21> - services/notification-service (consumer)
22>
23> Check compatibility between the publisher and all consumers.Note the distinction: to code in one service, cd into that service; for cross-service impact analysis, run it from the root with explicit context β two different contexts need different starting points.
17.9 Curating Context for a Monorepo
Not every file deserves to enter the AI context. The configuration below separates files that need to be included from generated files that should be excluded via .claudeignore.
1# Files that NEED to be in the AI context (include):
2# - Service CLAUDE.md and root CLAUDE.md
3# - The current service's domain entities
4# - The current service's interfaces (not implementations)
5# - The shared domain types used
6
7# Files that should be EXCLUDED from context:
8# .cursorignore (for Cursor)
9# .claudeignore (for Claude Code)
10
11cat > santekno-shop/.claudeignore << 'IGNORE'
12# Generated files β the AI doesn't need to read these
13**/mock_*.go
14**/*.pb.go
15**/*.pb.gw.go
16
17# Vendor dependencies
18**/vendor/
19
20# Large test fixtures
21**/testdata/*.json
22**/testdata/*.sql
23
24# Build outputs
25**/bin/
26**/*.out
27IGNOREExcluding generated files and vendor code isn’t just about cleanliness: every irrelevant context token reduces the room for code that genuinely matters and raises the cost.
17.10 Architecture Check for a Monorepo
Cross-service boundaries must be enforced automatically, not relying on manual discipline. The Go script below parses the imports in each service and fails the build if any imports another service’s internal package.
1// scripts/check-monorepo-boundaries.go
2// Validate cross-service import boundaries
3
4package main
5
6import (
7 "fmt"
8 "go/parser"
9 "go/token"
10 "os"
11 "path/filepath"
12 "strings"
13)
14
15// Map: service path prefix β allowed imports only
16var serviceAllowedExternals = map[string][]string{
17 "services/order-service": {
18 "shared/domain", // allowed: shared domain
19 "github.com/", // allowed: external packages
20 "golang.org/", // allowed: golang stdlib extras
21 },
22 // payment-service CANNOT import order-service internal
23 "services/payment-service": {
24 "shared/domain",
25 "github.com/",
26 "golang.org/",
27 },
28}
29
30func main() {
31 violations := 0
32
33 for service, allowed := range serviceAllowedExternals {
34 _ = allowed
35 filepath.Walk(service+"/internal", func(path string, info os.FileInfo, err error) error {
36 if err != nil || !strings.HasSuffix(path, ".go") {
37 return nil
38 }
39
40 fset := token.NewFileSet()
41 f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
42 if err != nil {
43 return nil
44 }
45
46 for _, imp := range f.Imports {
47 importPath := strings.Trim(imp.Path.Value, `"`)
48
49 // Check if import is from another service's internal
50 for otherService := range serviceAllowedExternals {
51 if otherService != service &&
52 strings.Contains(importPath, otherService+"/internal") {
53 fmt.Printf("VIOLATION: %s imports %s (cross-service internal)\n",
54 path, importPath)
55 violations++
56 }
57 }
58 }
59 return nil
60 })
61 }
62
63 if violations > 0 {
64 fmt.Printf("\n%d boundary violations found\n", violations)
65 os.Exit(1)
66 }
67 fmt.Println("OK: All service boundaries respected")
68}With this script in the pipeline, the rule “never import another service’s internal” stops being an appeal in CLAUDE.md and becomes a gate that truly blocks merges β enforcement that doesn’t depend on the reviewer’s memory.
17.11 Monorepo Context Governance
Context files are shared assets, so their ownership and change process must be clear. The responsibility split below separates ownership of the root, the service, and the shared domain.
1Ownership per layer:
2
3Root CLAUDE.md (universal rules):
4 Owner: Architecture Team / Tech Lead
5 Change process: PR with approval from the architecture team
6 Review cadence: Monthly check for consistency
7
8Service CLAUDE.md (service-specific):
9 Owner: Service Team
10 Change process: PR with approval from the service tech lead
11 Review cadence: Sprint end review
12
13Shared domain (shared/domain/):
14 Owner: Architecture Team
15 Change process: RFC + PR (breaking changes need a migration plan)
16 Review cadence: Quarterly evolution review
17
18Process to add a new pattern to CLAUDE.md:
191. Developer identifies a pattern the AI often gets wrong
202. Propose the addition to the service CLAUDE.md
213. If the pattern is universal: promote it to root CLAUDE.md
224. Update .cursorrules and copilot-instructions if neededThis layered governance keeps a balance: universal changes need strict approval from the architecture team, while service rules stay agile in the hands of the team that best understands the context.
17.12 Scenario: Onboarding a New Developer in a Monorepo
One of the most tangible benefits of a context hierarchy is onboarding. The session below shows how a new developer can understand the monorepo through an AI that reads CLAUDE.md.
1# A new developer joins and needs to understand the monorepo:
2
3claude
4> I just joined the Santekno team. Please help me understand:
5>
6> 1. How is this monorepo structured?
7> 2. What cross-service communication mechanism is used?
8> 3. What shared types MUST be used?
9> 4. What universal error handling applies across all services?
10> 5. What boundaries must NOT be violated?
11>
12> Read the available CLAUDE.md files to answer this.
13
14# Claude will:
15# - Read the root CLAUDE.md
16# - Explain the clear hierarchy
17# - Highlight critical rules (cross-service, money types, etc.)
18# - Generate a useful summary
19
20# This is more effective than "read all the documentation first"
21# Claude as a guided tour for onboardingThis approach turns onboarding from “read all the documents” into a guided tour: the new developer asks directly and the AI answers from the same source of truth that guides code generation.
17.13 Monorepo Context Tips & Gotchas
The following practical lessons are the ones that most determine the success of context management in a monorepo, summarized as tips and gotchas.
π‘ Tip 1: ALWAYS cd into the service directory before a Claude Code session. This is the most important rule.
π‘ Tip 2: Service CLAUDE.md must explicitly reference the root. Add this on the first line: # EXTENDS: ../../CLAUDE.md
π‘ Tip 3: Cross-service patterns must be in the root CLAUDE.md, not per-service. If one service needs to know, all services need to know.
π‘ Tip 4: Shared domain types prevent drift. If order-service and payment-service have different definitions of Amount, that’s a bug waiting to happen.
β οΈ Gotcha 1: The AI generating an import from another service. The rule NEVER import another service's internal must be explicit in CLAUDE.md with an example.
β οΈ Gotcha 2: The monorepo is too big for a single context window. Use Gemini (1M tokens) for cross-service analysis.
β οΈ Gotcha 3: A root CLAUDE.md that’s too verbose. Keep it concise (< 250 lines) so it doesn’t get diluted.
The common thread: the discipline of cd-ing into the service, a concise root, and consistent shared types β three small habits that prevent the majority of context problems in a monorepo.
17.14 Monorepo Context Setup Checklist
To make sure no step is missed, the checklist below summarizes the setup, governance, and developer habits that need to be in place.
1Setup:
2β‘ Root CLAUDE.md created with universal rules (< 250 lines)
3β‘ Every service has a service-specific CLAUDE.md
4β‘ Service CLAUDE.md explicitly references the root ("EXTENDS: ../../CLAUDE.md")
5β‘ shared/domain/ setup: money, event, id packages
6β‘ go.work setup for the development workspace
7β‘ .claudeignore setup to exclude generated files
8β‘ Architecture boundary check script created (17.10)
9
10Governance:
11β‘ Ownership per layer is defined (root vs service)
12β‘ PR process for changes to context files
13β‘ Monthly review schedule for consistency
14β‘ New developer onboarding guide uses AI (17.12)
15
16Developer habits:
17β‘ All developers know: always cd into the service directory
18β‘ Cross-service: Kafka events only, never direct import
19β‘ Shared types: always from shared/domain/
20β‘ Generated files: via go generate, never manually editedThis checklist should become part of the definition-of-done when a new monorepo is formed β it’s far cheaper to prepare the hierarchy up front than to retrofit it after five services have grown.
17.15 Advanced: Monorepo AI Review Pipeline
AI review in a monorepo should ideally be aware of service boundaries. The GitHub Actions workflow below detects changed services and runs a review with the correct context per service.
1# .github/workflows/monorepo-ai-review.yml
2# Review that is aware of service boundaries
3
4name: Monorepo AI Review
5
6on:
7 pull_request:
8 branches: [main, develop]
9
10jobs:
11 detect-services:
12 runs-on: ubuntu-latest
13 outputs:
14 services: ${{ steps.detect.outputs.services }}
15 steps:
16 - uses: actions/checkout@v4
17 with:
18 fetch-depth: 0
19 - name: Detect changed services
20 id: detect
21 run: |
22 CHANGED=$(git diff --name-only origin/main...HEAD | \
23 grep "^services/" | \
24 cut -d'/' -f2 | sort -u | \
25 jq -R -s -c 'split("\n")[:-1]')
26 echo "services=$CHANGED" >> $GITHUB_OUTPUT
27
28 review-per-service:
29 needs: detect-services
30 runs-on: ubuntu-latest
31 if: needs.detect-services.outputs.services != '[]'
32 strategy:
33 matrix:
34 service: ${{ fromJson(needs.detect-services.outputs.services) }}
35 steps:
36 - uses: actions/checkout@v4
37 with:
38 fetch-depth: 0
39
40 - name: Review ${{ matrix.service }}
41 run: |
42 # Get diff for the specific service
43 DIFF=$(git diff origin/main...HEAD -- "services/${{ matrix.service }}/**/*.go")
44
45 # Load service-specific CLAUDE.md context
46 SERVICE_RULES=$(cat services/${{ matrix.service }}/CLAUDE.md 2>/dev/null || echo "No service CLAUDE.md")
47 ROOT_RULES=$(cat CLAUDE.md)
48
49 # AI review with combined context
50 # ... (call the Claude API with combined context)
51 echo "Reviewing ${{ matrix.service }}..."With the per-service matrix, review no longer uses one uniform rule set for the entire repo β each service is judged with its own context, just like a developer who cds into that service.
17.16 Versioning Context Files
Context files are living documents that need a change trail. The changelog header below shows how to version CLAUDE.md so that changes in AI behavior can be traced.
1Context file as a living document that needs versioning:
2
3# CLAUDE.md (root)
4# Version: 3.2
5# Last updated: 2026-07-15
6# Changelog:
7# 3.2: Add cross-service Kafka event rules
8# 3.1: Add Redis key naming convention
9# 3.0: Major rewrite for monorepo setup
10
11Why versioning matters:
121. Audit trail when AI behavior changes
132. Easy rollback if an update causes a regression
143. Developers know when the rules were last updated
15
16How to maintain it in git:
17 git log -- CLAUDE.md # see the history
18 git blame CLAUDE.md # who added/changed what
19
20Monthly changelog:
21 Add a summary of this month's changes to the CLAUDE.md header
22 This helps all developers know what changedVersioning context files makes changes in AI behavior rollback-able like ordinary code β when AI output suddenly changes, the changelog becomes the first place to look for the cause.
17.17 Monorepo Context for Large Teams (20+ Developers)
As the team grows, many people want to change CLAUDE.md at once. The layered ownership model below keeps context under control in a 20+ developer team.
1Scaling challenges with a large team:
2
3Problem: 20 developers, 5 services, everyone wants to update CLAUDE.md
4Solution: Strict layered ownership
5
6Layer 1 β Root CLAUDE.md (Architecture Team only):
7 - Tech lead and arch team approve
8 - All changes via PR
9 - Monthly review mandatory
10
11Layer 2 β Service CLAUDE.md (Service Team):
12 - Service tech lead approves
13 - PR required, but more agile than the root
14 - Sprint end review
15
16Layer 3 β Personal notes (Individual):
17 - Each developer can have a local NOTES.md
18 - Not committed, only for personal context
19 - Doesn't affect team AI behavior
20
21Process to propose a change to root CLAUDE.md:
221. Developer notices: the AI often generates wrong pattern X
232. Developer proposes: "Add rule X to CLAUDE.md"
243. Service tech lead: validate whether it's service-specific or universal
254. Architecture team: review and approve if universal
265. Merge: all developers update CLAUDE.md
276. Monitor: did the AI behavior change as expected?These three ownership layers prevent a “tragedy of the commons” on the context file: universal changes stay tightly guarded, but developers still have room for personal notes that don’t disturb the team.
17.18 Shared Infrastructure Context
Beyond architecture rules, a service’s context also needs to describe the infrastructure it uses. The CLAUDE.md section below documents the database, cache, and Kafka for order-service.
1# services/order-service/CLAUDE.md β Infrastructure section
2
3## Infrastructure Context (Order Service)
4Database: PostgreSQL 15 via pgx/v5 pool
5 Connection pool: min 5, max 25 connections
6 Transaction timeout: 30 seconds
7
8Cache: Redis 7 via go-redis/v9
9 Key TTL defaults:
10 Short lived (locks): 30 seconds
11 Recent data: 5 minutes
12 Reference data: 1 hour
13
14Kafka: Confluent Kafka via confluent-kafka-go v2
15 Producer: async, max.in.flight.requests.per.connection=1
16 Consumer: at-least-once delivery (idempotent handlers required!)
17
18External APIs: None (order service communicates only via Kafka)
19
20## Idempotency Requirements
21ALL Kafka consumer handlers MUST be idempotent.
22Kafka delivery: at-least-once, the same message may arrive multiple times.
23Use: DB unique constraint + "ON CONFLICT DO NOTHING" as the primary strategy.Documenting infrastructure in the context makes the AI produce code that matches operational reality β for example, automatically adding idempotency because it knows Kafka uses at-least-once delivery.
17.19 Practical Exercise: Set Up Monorepo Context from Scratch
The best way to understand a context hierarchy is to build it yourself. The script below prepares the root CLAUDE.md, the service context, the shared domain, and go.work in one step.
1# Tutorial: set up the context hierarchy for a new monorepo
2
3# Step 1: Create the root CLAUDE.md
4cat > CLAUDE.md << 'ROOTCTX'
5# CLAUDE.md β Root Context (Universal Rules)
6# Version: 1.0 | Updated: 2026-07-15
7
8## Go Version
9Go 1.22 only. No 1.23+ features (range-over-func, slices.Collect, iter.Seq).
10
11## Architecture (All Services)
12handler β usecase β repository β domain
13
14Layer imports (STRICT):
15- handler: ONLY usecase interfaces
16- usecase: ONLY domain + repository interfaces
17- repository: ONLY domain + database drivers
18- domain: ZERO external imports (stdlib only)
19
20## Error Handling
21Repository not-found: return nil, nil (NEVER error)
22Error wrap: fmt.Errorf("packageName.MethodName: %w", err)
23HTTP error: {"error": "UPPERCASE_SNAKE_CASE_CODE"}
24
25## Money
26ALWAYS int64 cents via shared/domain/money.IDR
27NEVER float64, float32
28
29## Testing
30testify/suite + gomock pattern
31
32## Cross-Service (CRITICAL)
33NEVER import another service's internal packages
34Communication: Kafka events ONLY
35Shared types: shared/domain ONLY
36ROOTCTX
37
38# Step 2: Create the service context
39mkdir -p services/order-service
40cat > services/order-service/CLAUDE.md << 'SVCCTX'
41# services/order-service/CLAUDE.md
42# EXTENDS: ../../CLAUDE.md (all universal rules apply)
43
44## Service: order-service (Port: 8081)
45Purpose: Order lifecycle management
46
47## Kafka Topics
48Produces: order.created, order.confirmed, order.cancelled
49Consumes: payment.completed, inventory.reserved
50
51## Error Codes (Service-Specific)
52ORDER_NOT_FOUND, ORDER_NOT_CANCELLABLE, CANCEL_WINDOW_EXPIRED
53
54## SLO
55GET /orders/:id P99 < 50ms
56POST /orders P99 < 200ms
57SVCCTX
58
59# Step 3: Set up the shared domain
60mkdir -p shared/domain/money shared/domain/event shared/domain/id
61
62# Step 4: Set up go.work
63cat > go.work << 'WORK'
64go 1.22
65
66use ./shared/domain
67use ./services/order-service
68WORK
69
70echo "Monorepo context hierarchy setup complete"
71echo "Developer guide: ALWAYS cd into the service directory before an AI session"After running this script, you have a complete context hierarchy skeleton β just fill in the shared domain packages and add the next service following the same pattern.
17.20 Measuring Context Effectiveness in a Monorepo
A context setup needs to be verified to see whether it actually works. The four tests below check service isolation, universal rule enforcement, service-specific patterns, and cross-service boundaries.
1How to measure whether the context hierarchy works:
2
3Test 1: Service isolation
4 cd services/order-service
5 claude > "Implement CreateOrder"
6 Expected: the AI generates code that imports from shared/domain/
7 does NOT generate a cross-service import
8 If the AI generates a wrong import: update the service CLAUDE.md
9
10Test 2: Universal rule enforcement
11 in all services: try to trigger a violated pattern
12 claude > "Handle the not found case in the repository"
13 Expected: the AI returns (nil, nil), not an error
14 If wrong: the root CLAUDE.md needs strengthening
15
16Test 3: Service-specific pattern
17 in order-service:
18 claude > "What Kafka topics does this service produce?"
19 Expected: the AI lists topics from the service CLAUDE.md
20 If wrong: the service CLAUDE.md isn't loaded correctly
21
22Test 4: Cross-service boundary
23 claude > "Call the payment-service API directly from the order usecase"
24 Expected: the AI refuses or proposes a Kafka alternative
25 If the AI generates a direct call: the rule isn't explicit enoughThese four tests turn the subjective question “is my context good?” into concrete verification β if one test fails, it points directly at which file needs strengthening.
17.21 Integrating External Tools with Monorepo Context
Boundary rules should also be enforced by a linter, not just the AI. The golangci-lint configuration below blocks cross-service imports via gomodguard and depguard.
1# golangci-lint config aware of monorepo boundaries
2# .golangci.yml (root)
3
4linters-settings:
5 gomodguard:
6 blocked:
7 modules:
8 # Prevent cross-service imports
9 - github.com/santekno/santekno-shop/services/payment-service:
10 recommendations:
11 - "Use Kafka events for cross-service communication"
12 - github.com/santekno/santekno-shop/services/order-service/internal:
13 recommendations:
14 - "Never import other service internal packages"
15
16 depguard:
17 rules:
18 prevent-cross-service:
19 deny:
20 - pkg: "github.com/santekno/santekno-shop/services/*/internal"
21 desc: "Cross-service internal imports are forbidden. Use Kafka events or shared/domain."
22
23linters:
24 enable:
25 - gomodguard
26 - depguardWith depguard, cross-service boundaries are enforced by a linter that already runs in every CI β giving a second layer of enforcement independent of custom scripts or AI review.
17.22 Summary
Context management in a Go monorepo requires a clear hierarchy and consistent discipline. The correct setup: root CLAUDE.md (universal) + service CLAUDE.md (specific) + shared/domain (types) + go.work (workspace) = an AI that knows the boundaries and context for every service.
Most important rule: cd services/order-service before claude. Always.
Investing 1-2 days to organize the context hierarchy up front delivers compounding value: every developer automatically gets AI output that fits their service without repeating rules in every session. Once the context is neatly organized, the next step is bringing AI into the automated pipeline β quality gates in CI/CD.