a9s-add-related-view — for Claude Code a9s-add-related-view, community, for Claude Code, ide skills, aws-cli, bubbletea, golang, infrastructure, terminal, RelatedDef

v1.0.0

이 스킬 정보

적합한 상황: Ideal for AI agents that need adding related views to a resource type. 현지화된 요약: Terminal UI AWS Resource Manager — browse, inspect, and manage 60+ AWS resource types # Adding Related Views to a Resource Type Two agents, two tracks. It covers aws, aws-cli, bubbletea workflows. This AI agent skill supports Claude Code, Cursor, and Windsurf workflows.

기능

Adding Related Views to a Resource Type
Two agents, two tracks. The architect scopes both tasks using the per-resource
research docs in docs/design/related-resources/{shortname}.md. Coder and QA
can run in parallel (pattern is rigid).
Infrastructure must be in place first. These must exist before any

# Core Topics

k2m30 k2m30
[5]
[1]
Updated: 3/30/2026

Killer-Skills Review

Decision support comes first. Repository text comes second.

Reference-Only Page Review Score: 10/11

This page remains useful for teams, but Killer-Skills treats it as reference material instead of a primary organic landing page.

Original recommendation layer Concrete use-case guidance Explicit limitations and caution Quality floor passed for review
Review Score
10/11
Quality Score
55
Canonical Locale
en
Detected Body Locale
en

적합한 상황: Ideal for AI agents that need adding related views to a resource type. 현지화된 요약: Terminal UI AWS Resource Manager — browse, inspect, and manage 60+ AWS resource types # Adding Related Views to a Resource Type Two agents, two tracks. It covers aws, aws-cli, bubbletea workflows. This AI agent skill supports Claude Code, Cursor, and Windsurf workflows.

이 스킬을 사용하는 이유

추천 설명: a9s-add-related-view helps agents adding related views to a resource type. Terminal UI AWS Resource Manager — browse, inspect, and manage 60+ AWS resource types # Adding Related Views to a Resource Type Two

최적의 용도

적합한 상황: Ideal for AI agents that need adding related views to a resource type.

실행 가능한 사용 사례 for a9s-add-related-view

사용 사례: Applying Adding Related Views to a Resource Type
사용 사례: Applying Two agents, two tracks. The architect scopes both tasks using the per-resource
사용 사례: Applying research docs in docs/design/related-resources/{shortname}.md. Coder and QA

! 보안 및 제한 사항

  • 제한 사항: Infrastructure must be in place first. These must exist before any
  • 제한 사항: If these don't exist, STOP. The infrastructure must land first.
  • 제한 사항: Architect Must Provide

Why this page is reference-only

  • - Current locale does not satisfy the locale-governance contract.

Source Boundary

The section below is imported from the upstream repository and should be treated as secondary evidence. Use the Killer-Skills review above as the primary layer for fit, risk, and installation decisions.

After The Review

Decide The Next Action Before You Keep Reading Repository Material

Killer-Skills should not stop at opening repository instructions. It should help you decide whether to install this skill, when to cross-check against trusted collections, and when to move into workflow rollout.

Labs Demo

Browser Sandbox Environment

⚡️ Ready to unleash?

Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.

Boot Container Sandbox

FAQ & Installation Steps

These questions and steps mirror the structured data on this page for better search understanding.

? Frequently Asked Questions

What is a9s-add-related-view?

적합한 상황: Ideal for AI agents that need adding related views to a resource type. 현지화된 요약: Terminal UI AWS Resource Manager — browse, inspect, and manage 60+ AWS resource types # Adding Related Views to a Resource Type Two agents, two tracks. It covers aws, aws-cli, bubbletea workflows. This AI agent skill supports Claude Code, Cursor, and Windsurf workflows.

How do I install a9s-add-related-view?

Run the command: npx killer-skills add k2m30/a9s/a9s-add-related-view. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.

What are the use cases for a9s-add-related-view?

Key use cases include: 사용 사례: Applying Adding Related Views to a Resource Type, 사용 사례: Applying Two agents, two tracks. The architect scopes both tasks using the per-resource, 사용 사례: Applying research docs in docs/design/related-resources/{shortname}.md. Coder and QA.

Which IDEs are compatible with a9s-add-related-view?

This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.

Are there any limitations for a9s-add-related-view?

제한 사항: Infrastructure must be in place first. These must exist before any. 제한 사항: If these don't exist, STOP. The infrastructure must land first.. 제한 사항: Architect Must Provide.

How To Install

  1. 1. Open your terminal

    Open the terminal or command line in your project directory.

  2. 2. Run the install command

    Run: npx killer-skills add k2m30/a9s/a9s-add-related-view. The CLI will automatically detect your IDE or AI agent and configure the skill.

  3. 3. Start using the skill

    The skill is now active. Your AI agent can use a9s-add-related-view immediately in the current project.

! Reference-Only Mode

This page remains useful for installation and reference, but Killer-Skills no longer treats it as a primary indexable landing page. Read the review above before relying on the upstream repository instructions.

Upstream Repository Material

The section below is imported from the upstream repository and should be treated as secondary evidence. Use the Killer-Skills review above as the primary layer for fit, risk, and installation decisions.

Upstream Source

a9s-add-related-view

Install a9s-add-related-view, an AI agent skill for AI agent workflows and automation. Review the use cases, limitations, and setup path before rollout.

SKILL.md
Readonly
Upstream Repository Material
The section below is imported from the upstream repository and should be treated as secondary evidence. Use the Killer-Skills review above as the primary layer for fit, risk, and installation decisions.
Supporting Evidence

Adding Related Views to a Resource Type

Two agents, two tracks. The architect scopes both tasks using the per-resource research docs in docs/design/related-resources/{shortname}.md. Coder and QA can run in parallel (pattern is rigid).

Prerequisites

Infrastructure must be in place first. These must exist before any per-resource related views can be added:

  • internal/resource/related.go -- types, registries (RelatedDef, NavigableField), helper constructors
  • RelatedCheckResultMsg and RelatedNavigateMsg in internal/tui/messages/messages.go
  • ToggleRelated binding in internal/tui/keys/keys.go
  • Two-column detail view in internal/tui/views/detail.go (field-list model with embedded rightColumnModel)
  • Handler code in internal/tui/app.go (main Update switch) and internal/tui/app_related.go for RelatedCheckResultMsg and RelatedNavigateMsg

If these don't exist, STOP. The infrastructure must land first. See docs/design/related-views-architecture.md Phases 1-8.

Architect Must Provide

You MUST have a scoped task from the architect with:

  • Source type ShortName (e.g., "ec2")
  • Left column (navigable fields):
    • For each navigable field: FieldPath, TargetType
  • Right column (related types):
    • For each related type:
      • Target ShortName (e.g., "sg", "vpc")
      • DisplayName override (if any)
      • For cache-based checks: which cache key to look up, how to match
      • For field-based checks: which Fields key to read
  • Exact files to create/modify with append points

If you don't have this, STOP. Reply with REJECTED and ask for architect scope.

Agent Ownership

StepsOwnerWrites to
1-7 (implementation)a9s-coderinternal/, cmd/
8-12 (tests)a9s-qatests/unit/

Coder MUST NOT write test files. QA MUST NOT write production code.

Relationship Patterns

Pattern F: Forward / Field-Based (cheap, count shown)

IDs are already in the source resource's Fields or RawStruct. No external API call needed. The checker reads from Fields or RawStruct directly.

go
1// Example: EC2 -> EBS volumes (read from RawStruct block device mappings) 2func checkEC2EBS(_ context.Context, _ interface{}, res resource.Resource, _ resource.ResourceCache) resource.RelatedCheckResult { 3 ids := ec2VolumeIDs(res) // reads from res.RawStruct 4 if len(ids) == 0 { 5 return resource.RelatedCheckResult{TargetType: "ebs", Count: 0} 6 } 7 ordered := make([]string, 0, len(ids)) 8 for id := range ids { 9 ordered = append(ordered, id) 10 } 11 sort.Strings(ordered) 12 return relatedResult("ebs", ordered) 13}

Pattern C: Cache-Based (reads from already-loaded resource lists)

Looks up related resources in the ResourceCache. Falls back to a live API call only when the cache doesn't contain the target type. All cache-based checkers follow this helper pattern:

go
1func check{Source}{Target}(ctx context.Context, clients interface{}, res resource.Resource, cache resource.ResourceCache) resource.RelatedCheckResult { 2 // 1. Extract identity from res (ID, relevant fields, or RawStruct) 3 sourceID, _ := extractSourceIdentity(res) 4 if sourceID == "" { 5 return resource.RelatedCheckResult{TargetType: "{target}", Count: 0} 6 } 7 8 // 2. Load target list from cache or live fetch 9 targetList, truncated, err := {source}RelatedResources(ctx, clients, cache, "{target}") 10 if err != nil { 11 return resource.RelatedCheckResult{TargetType: "{target}", Count: -1, Err: err} 12 } 13 if targetList == nil { 14 // Target fetcher not registered / nothing loaded yet — honest lower 15 // bound is zero, NOT an error. See "Count: -1 vs ApproximateZero" below. 16 return resource.ApproximateZero("{target}") 17 } 18 19 // 3. Match against source 20 var ids []string 21 for _, targetRes := range targetList { 22 // match by RawStruct fields first, fall back to resource.Fields 23 if matchesSource(targetRes, sourceID) { 24 ids = append(ids, targetRes.ID) 25 } 26 } 27 // Truncation guard: partial page with 0 matches → honest lower bound, not an error. 28 if len(ids) == 0 && truncated { 29 return resource.ApproximateZero("{target}") 30 } 31 return relatedResult("{target}", ids) 32}

The relatedResult helper (defined in ec2_related.go, copy-paste for other source types) deduplicates and sorts IDs:

go
1func relatedResult(target string, ids []string) resource.RelatedCheckResult { 2 if len(ids) == 0 { 3 return resource.RelatedCheckResult{TargetType: target, Count: 0} 4 } 5 // deduplicate and sort ids ... 6 return resource.RelatedCheckResult{ 7 TargetType: target, 8 Count: len(uniq), 9 ResourceIDs: uniq, 10 } 11}

Count semantics:

  • Count: 0 -- confirmed none (exhaustive scan produced no matches)
  • Count: N (N > 0) -- confirmed N found, ResourceIDs populated
  • Count: -1 -- error state. Something went wrong: AWS API returned an error, required client is nil, assertStruct failed, or we lack the identity needed to run the query. The UI renders this as "?" and it contributes to the per-resource truncation bucket.
  • resource.ApproximateZero("{target}") -- honest lower bound of zero. Not an error. Returned when the target list comes back nil (fetcher not registered, nothing loaded yet) or when a truncated target page produced zero matches so far. The UI renders this as "0+".

Return-value classifier for a checker -- load-bearing

A related checker must choose one of four return values per code path. Getting this wrong misleads the UI: Count: -1 renders as "?" and feeds the truncation bucket; ApproximateZero renders as "0+"; Count: 0 renders as plain "0". The operator reads those three differently. Classify every branch:

  1. Error path -- AWS call returned an error, required client is nil, an assertStruct failed, the source resource lacks the identity needed to form the query, or an intermediate helper returned a sentinel meaning "unknown, could not determine". Return resource.RelatedCheckResult{TargetType: "{target}", Count: -1, Err: err} (attach Err when a real error value is in hand). If an intermediate helper's documented contract is "nil return = error distinct from empty", propagate its nil as Count: -1; do NOT collapse it into ApproximateZero.

  2. Complete-answer API -- the checker makes a single bounded AWS call (not cache-backed, not paginated) that returns an exhaustive answer. out == nil && err == nil (or an empty output slice with no error) means the AWS service reported zero authoritatively. Return Count: 0. This is NOT ApproximateZero -- no lower bound is involved because no pagination cursor remains.

  3. Nil target list from the cache-backed helper -- the checker uses the standard {source}RelatedResources(ctx, clients, cache, "{target}") (or equivalent FetchRelatedTarget wrapper) and the returned list is nil because the target fetcher is not registered or nothing has been loaded yet. Return resource.ApproximateZero("{target}"). Zero registered targets is an honest lower bound of zero, not an error.

  4. Truncated target page with zero matches so far -- the cache-backed helper returned a partial page (truncated == true) and this checker's match loop produced no IDs. Return resource.ApproximateZero("{target}"). Later pages may produce matches; the current view is a lower bound.

Decision order when reviewing a new or rewritten checker: for each return statement, ask (in order) -- Is an AWS call or helper erroring? Is this a one-shot exhaustive API with out == nil && err == nil? Is this the if targetList == nil branch from a cache-backed helper? Is this the truncated-and-no-matches branch? The first "yes" determines the return. Returning Count: -1 on a nil-list-from-cache or truncated-empty branch is the regression this section exists to prevent.

Pattern N: Naming-Convention (reverse lookup by name pattern)

The target resource's ID or name follows a predictable naming convention that embeds the source resource's name. Search the target cache for matches.

go
1// Example: SFN → logs (log group name is /aws/vendedlogs/states/{sfn-name}) 2func checkSFNLogs(ctx context.Context, clients any, res resource.Resource, cache resource.ResourceCache) resource.RelatedCheckResult { 3 sfnName := res.ID 4 if sfnName == "" { 5 return resource.RelatedCheckResult{TargetType: "logs", Count: 0} 6 } 7 expectedPrefix := "/aws/vendedlogs/states/" + sfnName 8 9 logsList, truncated, err := sfnRelatedResources(ctx, clients, cache, "logs") 10 // ... standard cache lookup, match by strings.HasPrefix(logRes.ID, expectedPrefix) 11}

Known naming conventions:

  • Lambda → logs: /aws/lambda/{function-name}
  • CodeBuild → logs: /aws/codebuild/{project-name}
  • SFN → logs: /aws/vendedlogs/states/{state-machine-name}
  • EKS → logs: /aws/eks/{cluster-name}/...

Pattern D: Dimension-Based (reverse lookup by alarm dimensions)

Search the alarm cache for alarms whose Dimensions[] contain a matching dimension name/value. The source resource's ARN or name is the dimension value.

go
1// Example: SFN → alarm (alarm dimension StateMachineArn matches SFN ARN) 2func checkSFNAlarm(ctx context.Context, clients any, res resource.Resource, cache resource.ResourceCache) resource.RelatedCheckResult { 3 sfnARN := res.Fields["arn"] 4 if sfnARN == "" { 5 return resource.RelatedCheckResult{TargetType: "alarm", Count: -1} 6 } 7 alarmList, truncated, err := sfnRelatedResources(ctx, clients, cache, "alarm") 8 // ... iterate alarms, assert MetricAlarm, check Dimensions for StateMachineArn == sfnARN 9}

When Checker: nil Is Acceptable

A nil checker (unknown count, shows "?" in UI) is ONLY acceptable when ALL of the following are true:

  1. No forward fields: The source RawStruct has no fields referencing the target
  2. No reverse fields: No cached resource type has fields referencing this source
  3. No naming convention: The target doesn't follow a name pattern embedding the source name
  4. No dimension match: No alarm dimensions reference this source's ARN or name
  5. The relationship requires a separate API call not available from any cached data

Before marking a checker as nil, the architect MUST verify all five conditions. Checking the research doc (docs/design/related-resources/{shortname}.md) is mandatory — it lists viable lookup strategies for each relationship.

Common mistakes:

  • Marking alarm as nil when alarms have Dimensions[] referencing the source ARN
  • Marking logs as nil when a /aws/{service}/{name} log group convention exists
  • Marking a relationship as nil when OTHER cached resources have fields pointing back (e.g., SNS→alarm: alarm cache has AlarmActions[] containing topic ARNs)

CODER STEPS (1-7) -- a9s-coder agent only

1. Registration: add to internal/aws/{source}.go (or {source}_related.go)

IMPORTANT: Module path is github.com/k2m30/a9s/v3/... (the /v3 suffix is required).

Both RegisterRelated and RegisterNavigableFields calls belong in the same init() as the resource type registration. For large resources like EC2 the related checker functions live in a separate {source}_related.go file for readability, but the RegisterRelated calls stay in the main init().

go
1func init() { 2 // ... existing RegisterType / RegisterFetcher calls ... 3 4 // --- Right column: related resource definitions --- 5 resource.RegisterRelated("{source}", []resource.RelatedDef{ 6 // NeedsTargetCache: true for Pattern C (cache-based) checkers — triggers pre-fetch before checker runs. 7 // Omit (false) for Pattern A/B checkers that call AWS directly. 8 {TargetType: "{target1}", DisplayName: "{Display Name 1}", Checker: check{Source}{Target1}, NeedsTargetCache: true}, 9 {TargetType: "{target2}", DisplayName: "{Display Name 2}", Checker: check{Source}{Target2}, NeedsTargetCache: true}, 10 // Checker may be nil for stubs (shows as unknown count): 11 {TargetType: "{target3}", DisplayName: "{Display Name 3}", Checker: nil}, 12 }) 13 14 // --- Left column: navigable fields --- 15 resource.RegisterNavigableFields("{source}", []resource.NavigableField{ 16 {FieldPath: "{FieldName}", TargetType: "{target}"}, 17 {FieldPath: "{Section.FieldName}", TargetType: "{target}"}, 18 // ... one entry per navigable field in the detail view 19 }) 20}

RelatedDef fields:

  • TargetType string -- target resource short name (e.g., "tg", "alarm")
  • DisplayName string -- right-column row label (e.g., "Target Groups")
  • Checker RelatedChecker -- async checker function (nil for stubs)
  • NeedsTargetCache bool -- set true for Pattern C (cache-based) checkers; triggers coordinated pre-fetch before the checker runs. Omitting this on a cache-based checker causes cold-cache misses to return Count: -1 instead of the real count.

NavigableField fields:

  • FieldPath string -- matches a label rendered in the detail view (e.g., "VpcId")
  • TargetType string -- resource short name to navigate to

2. Checker functions: internal/aws/{source}_related.go (NEW FILE)

The RelatedChecker type signature is:

go
1type RelatedChecker func(ctx context.Context, clients interface{}, res resource.Resource, cache resource.ResourceCache) resource.RelatedCheckResult

Note: no error return -- errors are embedded in RelatedCheckResult.Err.

go
1package aws 2 3import ( 4 "context" 5 "sort" 6 7 // ... SDK type imports as needed ... 8 9 "github.com/k2m30/a9s/v3/internal/resource" 10) 11 12// check{Source}{Target1} checks the cache for {target1} resources related to this {source}. 13func check{Source}{Target1}(ctx context.Context, clients interface{}, res resource.Resource, cache resource.ResourceCache) resource.RelatedCheckResult { 14 sourceID := res.ID 15 if sourceID == "" { 16 return resource.RelatedCheckResult{TargetType: "{target1}", Count: 0} 17 } 18 19 targetList, truncated, err := {source}RelatedResources(ctx, clients, cache, "{target1}") 20 if err != nil { 21 return resource.RelatedCheckResult{TargetType: "{target1}", Count: -1, Err: err} 22 } 23 if targetList == nil { 24 // Honest zero — fetcher not registered or nothing loaded yet. 25 return resource.ApproximateZero("{target1}") 26 } 27 28 var ids []string 29 for _, r := range targetList { 30 // prefer RawStruct for accuracy, fall back to Fields 31 if r.Fields["{source_id_field}"] == sourceID { 32 ids = append(ids, r.ID) 33 } 34 } 35 // Truncation guard: partial page with 0 matches → honest lower bound. 36 if len(ids) == 0 && truncated { 37 return resource.ApproximateZero("{target1}") 38 } 39 return relatedResult("{target1}", ids) 40} 41 42// check{Source}{Target2} checks the cache for {target2} resources related to this {source}. 43func check{Source}{Target2}(ctx context.Context, clients interface{}, res resource.Resource, cache resource.ResourceCache) resource.RelatedCheckResult { 44 // ... similar pattern ... 45} 46 47// {source}RelatedResources returns the resource list for target from cache or 48// fetches the first page via the registered paginated fetcher. 49// Returns (resources, isTruncated, error). 50// isTruncated=true means the list is partial; callers MUST return Count=-1 51// when 0 matches are found in a truncated list. 52func {source}RelatedResources(ctx context.Context, clients interface{}, cache resource.ResourceCache, target string) ([]resource.Resource, bool, error) { 53 resources, isTruncated, err := FetchRelatedTarget(ctx, clients, cache, target) 54 // When AWS clients are not initialized (nil or wrong type), registered fetchers 55 // return "AWS clients not initialized". Treat as graceful no-op (Count=-1, no error). 56 if err != nil { 57 if _, ok := clients.(*ServiceClients); !ok { 58 return nil, false, nil 59 } 60 } 61 return resources, isTruncated, err 62} 63 64// Do NOT define a {source}RelatedResult function. Call the shared package-level 65// relatedResult(target, ids) from ec2_related.go — it lives in the same package 66// and handles deduplication and sorting for all resource types.

RelatedCheckResult fields:

  • TargetType string -- echoed from RelatedDef.TargetType
  • Count int -- -1 = unknown; 0 = confirmed none; N > 0 = confirmed N
  • ResourceIDs []string -- IDs of found related resources (empty when Count <= 0)
  • Err error -- non-nil on error

There is no Available bool field.

3. Interfaces: internal/aws/<service>_interfaces.go (APPEND if needed)

Only needed if the checker's live-fetch fallback calls an API not already covered by existing interfaces. Cache-only checkers that never call live APIs do not need new interfaces.

go
1// Only add if not already present: 2type {TypeName}{APICall}API interface { 3 {APICall}(ctx context.Context, params *{service}.{APICall}Input, optFns ...func(*{service}.Options)) (*{service}.{APICall}Output, error) 4}

4. Demo overrides

Register a demo checker so the related panel shows realistic data in demo mode.

Hybrid fixture pattern (014-demo-transport-mock). Demo mode has two layers: the legacy HTTP transport (internal/demo/transport.go + handlers.go) is the base for all services, and per-service typed fakes (internal/demo/fakes/<service>.go) override individual services. Currently only EC2 uses a typed fake.

  • Preferred (migrated services): add fixture data to internal/demo/fixtures/<service>.go and extend the matching fake in internal/demo/fakes/<service>.go.
  • Legacy (non-migrated services): add fixture data to the matching internal/demo/fixtures_*.go category file and (if needed) extend handlers in internal/demo/handlers.go.

When adding a new resource type, match the service's current layer. Do not mix layers for the same service.

No separate demo registry is needed. Related checkers run against the typed fakes automatically. Ensure the target resource type's fixtures contain IDs that match what the source resource's fields reference. For example, if EC2 instances reference vpc-prod-main in their VpcId field, the VPC fake's fixtures must include a VPC with that ID.

Add fixture data to internal/demo/fixtures/<service>.go for the target resource type. The related checker will find it via the standard prefetch + cache path.

Never amend tests if fixtures do not have related IDs/fields. Fix fixtures, not tests.

Use the resource short name in PascalCase as {SourceCamel} and {TargetCamel}: ec2EC2, rdsRDS, s3S3, tgTG, ebsEBS, etc. Add a numeric suffix only when a source has multiple related IDs of the same target type.

5. Verify parent resource Fields

Critical for left-column navigable fields. Verify that the source resource's regular fetcher populates the Fields keys that the NavigableField entries reference.

For example, if a NavigableField has FieldPath: "VpcId", verify that internal/aws/{source}.go populates a field with key "VpcId" or that the field appears in the detail view from RawStruct reflection.

If a required field is missing from Fields, add it to the regular fetcher.

6. Verify navigable field paths match detail view output

The FieldPath in NavigableField must match the actual key labels rendered in the detail view. A mismatch silently leaves the field non-highlighted across the entire resource type.

Automated check (required): Run the test added in QA step 9 (TestNavigableFields_{Source}_FieldPathsResolve) which verifies each registered FieldPath resolves to a non-empty value against the resource's demo fixture:

bash
1go test ./tests/unit/ -run "TestNavigableFields_{Source}_FieldPathsResolve" -v -count=1

Manual fallback: If no demo fixture exists yet, check .a9s/views_reference.yaml for the source resource — all field paths that appear in the detail view are listed there. Verify each FieldPath matches a key in that file (case-sensitive).

For nested fields like SecurityGroups.GroupId, the detail view renders these as indented sub-fields. The FieldPath must match the leaf label that appears in the rendered output.

7. Post-implementation verification

bash
1make test 2make lint 3make gofix 4make build

QA STEPS (8-12) -- a9s-qa agent only

8. Mocks: tests/unit/mocks_test.go (APPEND if needed)

Only needed if the checker's live-fetch fallback introduces NEW interfaces not already present. Cache-only checkers that never call live APIs do not need new mocks.

go
1// mock{TypeName}By{Filter}Client implements awsclient.{InterfaceName} for testing. 2type mock{TypeName}By{Filter}Client struct { 3 output *{service}.{APICall}Output 4 err error 5} 6 7func (m *mock{TypeName}By{Filter}Client) {APICall}( 8 ctx context.Context, 9 params *{service}.{APICall}Input, 10 optFns ...func(*{service}.Options), 11) (*{service}.{APICall}Output, error) { 12 return m.output, m.err 13}

Write tests covering each checker and navigable field registration. Checkers receive a resource.ResourceCache -- populate it with test data to simulate the cache-hit path. Test the cache-miss path by passing an empty or nil cache.

go
1package unit_test 2 3import ( 4 "context" 5 "testing" 6 7 // ... SDK type imports as needed ... 8 9 "github.com/k2m30/a9s/v3/internal/demo" 10 "github.com/k2m30/a9s/v3/internal/fieldpath" 11 "github.com/k2m30/a9s/v3/internal/resource" 12) 13 14func {source}CheckerByTarget(t *testing.T, target string) resource.RelatedChecker { 15 t.Helper() 16 for _, def := range resource.GetRelated("{source}") { 17 if def.TargetType == target { 18 if def.Checker == nil { 19 t.Fatalf("{source} related checker for %s is nil", target) 20 } 21 return def.Checker 22 } 23 } 24 t.Fatalf("{source} related checker for %s not found", target) 25 return nil 26} 27 28// --- Navigable Field Registration Tests --- 29 30func TestNavigableFields_{Source}_Registered(t *testing.T) { 31 fields := resource.GetNavigableFields("{source}") 32 if len(fields) == 0 { 33 t.Fatal("no navigable fields registered for {source}") 34 } 35 36 expected := map[string]string{ 37 "{FieldPath1}": "{target1}", 38 "{FieldPath2}": "{target2}", 39 } 40 for path, targetType := range expected { 41 nav := resource.IsFieldNavigable("{source}", path) 42 if nav == nil { 43 t.Errorf("expected navigable field %q not found", path) 44 continue 45 } 46 if nav.TargetType != targetType { 47 t.Errorf("field %q: TargetType = %q, want %q", path, nav.TargetType, targetType) 48 } 49 } 50} 51 52// TestNavigableFields_{Source}_FieldPathsResolve verifies that each registered 53// NavigableField.FieldPath resolves to a non-empty value against the demo fixture. 54// A mismatch here means the field will silently never be highlighted in the detail view. 55func TestNavigableFields_{Source}_FieldPathsResolve(t *testing.T) { 56 // Get demo resource for this source type (must be populated by the demo fixture) 57 resources, ok := demo.GetResources("{source}") 58 if !ok { 59 t.Skip("no demo fixture registered for {source}") 60 } 61 if len(resources) == 0 { 62 t.Skip("demo fixture returned no resources for {source}") 63 } 64 r := resources[0] 65 66 fields := resource.GetNavigableFields("{source}") 67 if len(fields) == 0 { 68 t.Fatal("no navigable fields registered for {source}") 69 } 70 71 for _, nav := range fields { 72 items := fieldpath.ExtractFieldList(r.RawStruct, []string{nav.FieldPath}, r.Fields, nil) 73 found := false 74 for _, item := range items { 75 if item.Value != "" && item.Value != "-" { 76 found = true 77 break 78 } 79 } 80 if !found { 81 t.Errorf("NavigableField.FieldPath %q resolved to empty/missing value in demo fixture — check FieldPath spelling or add field to fetcher", nav.FieldPath) 82 } 83 } 84} 85 86// --- Checker Tests --- 87 88func TestRelated_{Source}_{Target}_Found(t *testing.T) { 89 // Build a fake target resource that should match the source 90 fakeTarget := resource.Resource{ 91 ID: "target-id-1", 92 Fields: map[string]string{ 93 "{source_id_field}": "source-id-1", 94 }, 95 } 96 cache := resource.ResourceCache{ 97 "{target}": resource.ResourceCacheEntry{Resources: []resource.Resource{fakeTarget}}, 98 } 99 source := resource.Resource{ID: "source-id-1"} 100 101 checker := {source}CheckerByTarget(t, "{target}") 102 result := checker(context.Background(), nil, source, cache) 103 104 if result.Count != 1 { 105 t.Errorf("Count = %d, want 1", result.Count) 106 } 107 if len(result.ResourceIDs) != 1 || result.ResourceIDs[0] != "target-id-1" { 108 t.Errorf("ResourceIDs = %v, want [target-id-1]", result.ResourceIDs) 109 } 110 if result.Err != nil { 111 t.Errorf("unexpected error: %v", result.Err) 112 } 113} 114 115func TestRelated_{Source}_{Target}_NotFound(t *testing.T) { 116 fakeTarget := resource.Resource{ 117 ID: "target-id-2", 118 Fields: map[string]string{ 119 "{source_id_field}": "other-source-id", 120 }, 121 } 122 cache := resource.ResourceCache{ 123 "{target}": resource.ResourceCacheEntry{Resources: []resource.Resource{fakeTarget}}, 124 } 125 source := resource.Resource{ID: "source-id-1"} 126 127 checker := {source}CheckerByTarget(t, "{target}") 128 result := checker(context.Background(), nil, source, cache) 129 130 if result.Count != 0 { 131 t.Errorf("Count = %d, want 0", result.Count) 132 } 133 if len(result.ResourceIDs) != 0 { 134 t.Errorf("ResourceIDs = %v, want []", result.ResourceIDs) 135 } 136} 137 138func TestRelated_{Source}_{Target}_CacheMissNoClients(t *testing.T) { 139 // Empty cache + nil clients -> unknown (-1), no error 140 source := resource.Resource{ID: "source-id-1"} 141 checker := {source}CheckerByTarget(t, "{target}") 142 result := checker(context.Background(), nil, source, resource.ResourceCache{}) 143 144 if result.Count != -1 { 145 t.Errorf("Count = %d, want -1 (unknown)", result.Count) 146 } 147} 148 149func TestRelated_{Source}_{Target}_EmptySourceID(t *testing.T) { 150 source := resource.Resource{ID: ""} 151 checker := {source}CheckerByTarget(t, "{target}") 152 result := checker(context.Background(), nil, source, resource.ResourceCache{}) 153 154 if result.Count != 0 { 155 t.Errorf("Count = %d, want 0 for empty source ID", result.Count) 156 } 157}

10. Cold-cache checker tests (MANDATORY for every registered source type)

Add to the same tests/unit/aws_{source}_related_test.go file. Tests drive the real checker through the typed fakes via the cold-cache harness — no demo registry needed:

go
1func TestRelated_{Source}_ColdCacheChecker(t *testing.T) { 2 m := newDemoColdCacheApp(t) 3 // Size, inject clients, navigate to source, fetch, open detail 4 // Assert related check produces non-empty results via the real checker path 5}

11. Registry tests: tests/unit/related_registry_test.go (APPEND)

Verify the registration was successful:

go
1func TestRelated_{Source}_Registered(t *testing.T) { 2 defs := resource.GetRelated("{source}") 3 if len(defs) == 0 { 4 t.Fatal("no related defs registered for {source}") 5 } 6 7 // Verify expected target types are present 8 expected := []string{"{target1}", "{target2}", "{target3}"} 9 for _, exp := range expected { 10 found := false 11 for _, def := range defs { 12 if def.TargetType == exp { 13 found = true 14 break 15 } 16 } 17 if !found { 18 t.Errorf("expected related def for target %q not found", exp) 19 } 20 } 21 22 // Verify non-stub checkers exist 23 for _, def := range defs { 24 if def.Checker == nil { 25 continue // stub entry, intentional 26 } 27 // Verify checker is callable (non-nil is sufficient for registration check) 28 } 29}

12. Post-test verification

bash
1go test ./tests/unit/ -count=1 -timeout 120s -run "Related_{Source}" 2go test ./tests/unit/ -count=1 -timeout 120s -run "NavigableFields_{Source}" 3make test 4make lint 5make gofix

What You Do NOT Need to Change (per resource)

  • detail.go -- the two-column detail view renders from registries generically
  • app.go -- generic handlers dispatch to registry
  • messages.go -- generic message types carry strings
  • keys.go -- r toggle and Tab switching are generic
  • app_related.go -- handleRelatedCheckStarted() and handleRelatedNavigate() are generic

Research Reference

Each resource's related relationships are documented in: docs/design/related-resources/{shortname}.md

These docs contain:

  • Real-world use cases (why engineers need this relationship)
  • Which other resource types reference or are referenced by this resource
  • Which Fields/RawStruct paths to use for matching

Forward relationships and navigable field paths come from the resource's own API response fields, documented in .a9s/views_reference.yaml.

Architect Handoff Format

When the architect scopes related views for resource X, the handoff uses:

## RELATED VIEWS: {Source Display Name} ({source_shortname})

### Left Column -- Navigable Fields:
| Field Path | Target Type | Notes |
|------------|-------------|-------|
| {FieldPath} | {target} | {optional notes} |
| {Section.Field} | {target} | {optional notes} |

### Right Column -- Related Definitions:
| Target | DisplayName | Match Strategy | Cache Key | Notes |
|--------|------------|----------------|-----------|-------|
| {target} | {Display Name} | field: {field_key} == sourceID | {cache_key} | {notes} |
| {target} | {Display Name} | rawstruct: {field_path} | {cache_key} | {notes} |
| {target} | {Display Name} | nil (stub) | n/a | |

### CODER TASK:
Files to create:
  internal/aws/{source}_related.go -- checker functions + cache helper (reuse shared relatedResult and assertStruct from ec2_related.go — do NOT redefine)
Files to modify:
  internal/aws/{source}.go -- append RegisterRelated + RegisterNavigableFields in init()
  internal/demo/fixtures/<service>.go -- ensure target resource fixtures contain matching IDs
  internal/aws/{service}_interfaces.go -- append new interfaces (only if live-fetch fallback needs them)
    Append point: after last narrow interface, before the aggregate {Service}API
Context files (read-only):
  internal/aws/{source}.go -- verify Fields keys exist
  internal/aws/ec2_related.go -- canonical checker pattern
  internal/resource/related.go -- type definitions
  docs/design/related-resources/{source}.md -- relationship details
  .a9s/views_reference.yaml -- verify field paths

### QA TASK:
Test files to create:
  tests/unit/aws_{source}_related_test.go -- checker + navigable field + demo tests
Test files to modify:
  tests/unit/mocks_test.go -- append mocks (only if live-fetch fallback needs new interfaces)
    Append point: last mock in file
  tests/unit/related_registry_test.go -- append registration tests
    Append point: last TestRelated_ function
What to test:
  - Navigable field registration: all expected fields registered with correct target types
  - Checkers: found / not found / cache-miss-no-clients / empty-source-ID
  - Cold-cache checker: drives real checker through typed fakes via newDemoColdCacheApp
  - Registry: all expected defs registered
Context files (read-only):
  internal/aws/{source}_related.go -- function signatures
  internal/resource/related.go -- type definitions

관련 스킬

Looking for an alternative to a9s-add-related-view or another community skill for your workflow? Explore these related open-source skills.

모두 보기

openclaw-release-maintainer

Logo of openclaw
openclaw

Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞

333.8k
0
인공지능

widget-generator

Logo of f
f

Generate customizable widget plugins for the prompts.chat feed system

149.6k
0
인공지능

flags

Logo of vercel
vercel

The React Framework

138.4k
0
브라우저

pr-review

Logo of pytorch
pytorch

Tensors and Dynamic neural networks in Python with strong GPU acceleration

98.6k
0
개발자