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
22 changes: 20 additions & 2 deletions .claude/commands/implement-feature.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
Implement a feature for the unic TUI tool.

## Input
$ARGUMENTS — feature description or GitHub issue number (e.g., "CloudWatch Logs browser" or "#29")
$ARGUMENTS — feature description or GitHub issue number (e.g., "CloudWatch Logs browser" or "#29"). Can be empty.

## Phase 0: Auto-Suggest (when no argument is provided)

If `$ARGUMENTS` is empty or blank:

1. Read `PLAN.md` and identify milestones/features that are **not** marked ✅ (complete).
2. Run `gh issue list --state open --limit 30` to fetch open GitHub issues.
3. Cross-reference: find open issues whose title or body matches an incomplete PLAN.md item. Also note incomplete PLAN.md items that have no issue yet.
4. Rank candidates by priority:
- Items in the earliest incomplete milestone come first (M3 before M4, M4 before M5, etc.)
- Within a milestone, prefer items that already have an open GitHub issue
- Within the same milestone, prefer items with fewer dependencies or prerequisites
5. Present the top 3–5 candidates to the user using AskUserQuestion, showing for each:
- The PLAN.md item name and milestone (e.g., "M3.6 — Route 53 Phase 2")
- The linked GitHub issue number if one exists, or "(no issue yet)" otherwise
- A one-line summary of what's involved
6. After the user picks one, if no GitHub issue exists for it, remind the user that one should be created (per project workflow rules) before proceeding.
7. Continue to Phase 1 with the selected feature.

## Phase 1: Gather Context

1. If the argument is a GitHub issue number, fetch it with `gh issue view <number>`. Read the full body, checklist, and comments.
2. If the argument is a description, search for a matching issue with `gh issue list --search "<description>"` and read it if found.
3. Read `PLAN.md` to find the relevant milestone and any design decisions.
3. Read `PLAN.md` (if not already read in Phase 0) to find the relevant milestone and any design decisions.
4. Read `.kiro/docs/architecture-en.md` if it exists for architectural context.
5. Explore the codebase to understand existing patterns:
- `internal/domain/model.go` and `catalog.go` for service/feature registration
Expand Down
93 changes: 93 additions & 0 deletions .claude/commands/senior-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Act as a Senior Software Engineer conducting a thorough code quality review of this codebase. Focus on Clean Code principles, Go idioms, and actionable refactoring advice.

## Input
$ARGUMENTS — optional: specific focus area or concern (e.g., "screen_route53.go is getting big"). If empty, review the entire codebase.

## Phase 1: LoC Inventory

1. Run `find internal/ cmd/ -name '*.go' | xargs wc -l | sort -rn` to get line counts for all Go source files.
2. Run `find internal/ cmd/ -name '*_test.go' | xargs wc -l | sort -rn` separately for test files.
3. Produce a **LoC Breakdown Table** sorted by line count descending:
- Columns: File, Lines, Status
- Status: flag files > 300 lines as `[LONG]`, > 500 as `[CRITICAL]`
- Include a total line count and average

## Phase 2: Deep Review

Use Explore agents to read and analyze source files. For each file, evaluate against these categories:

### Category 1: File Size & Function Length
- Files exceeding 300 LoC — suggest extraction/splitting
- Functions exceeding 50 LoC — suggest decomposition
- Deeply nested blocks (3+ levels) — suggest early returns or extraction

### Category 2: Naming & Readability
- Go idioms: short names in small scopes, descriptive names in exported APIs
- Receiver names: single-letter lowercase matching the type (Go convention)
- Misleading or ambiguous names
- Unnecessary or stale comments (code should be self-documenting)
- Magic numbers or string literals that should be constants

### Category 3: SOLID / Abstraction / DRY
- Single Responsibility: does each file/struct have one clear purpose?
- Duplicate code: repeated logic across files that should be extracted
- Interface segregation: are interfaces minimal or bloated?
- Premature abstraction: abstractions that serve only one call site
- Missing abstraction: repeated patterns that warrant a shared helper

### Category 4: Error Handling & Edge Cases
- Swallowed errors (err ignored or logged but not returned)
- Inconsistent error wrapping (`fmt.Errorf` with `%w` vs without)
- Missing nil checks on pointers before dereference
- Boundary validation at system edges (user input, AWS API responses)

### Category 5: Go-Specific Idioms
- Proper use of `context.Context` (passed through, not stored in structs)
- Correct error type assertions vs string matching
- Goroutine leaks or missing cancellation
- Proper use of `defer` for cleanup
- Struct field ordering (exported before unexported, logical grouping)

## Phase 3: Report

Produce a structured report with the following sections:

### 1. Executive Summary
2-3 sentences: overall code health, biggest concern, biggest strength.

### 2. LoC Breakdown Table
From Phase 1.

### 3. Findings

For each finding, use this format:

```
[SEVERITY] Category — Short title
File: path/to/file.go:LINE
Issue: What is wrong and why it matters.
Suggestion: Concrete refactoring advice (what to extract, rename, restructure).
```

Severity levels:
- **[CRITICAL]** — Likely bug, maintainability blocker, or violated Go best practice that will cause issues at scale
- **[WARNING]** — Code smell that hurts readability or maintainability but works correctly today
- **[NIT]** — Style preference or minor improvement, low priority

### 4. Refactoring Priorities
A ranked list of the top 5 most impactful refactorings, each with:
- What to change
- Why it matters
- Estimated scope (which files, rough effort)

### 5. Positive Patterns
Call out 2-3 things the codebase does well — patterns worth preserving or extending.

## Guidelines

- Be direct and opinionated. A senior engineer gives clear recommendations, not vague suggestions.
- Every finding must include a concrete `Suggestion:` — don't just point out problems.
- Respect existing patterns. If the codebase consistently does X, don't flag individual instances of X — flag the pattern itself if it's problematic.
- Don't flag test files for naming or length unless they have actual quality issues (copy-paste test logic, missing edge cases).
- Don't suggest changes that would break the existing architecture without a strong justification.
- If $ARGUMENTS specifies a focus area, still produce the full LoC table but prioritize findings in that area.
63 changes: 63 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (
screenRoute53ZoneList
screenRoute53RecordList
screenRoute53RecordDetail
screenRoute53RecordCreate
screenRoute53RecordEdit
screenRoute53RecordDeleteConfirm
screenSecretList
screenSecretDetail
screenSecurityGroupList
Expand Down Expand Up @@ -110,6 +113,17 @@ type Model struct {
route53RecordFilterActive bool
selectedRoute53Record *awsservice.DNSRecord

// Route53 mutation state
route53Action string // "create", "edit", "delete"
route53ConfirmInput string // type-to-confirm for delete
route53EditField int // current form field index
route53EditValues map[string]string // accumulated form values
route53EditInput string // current field text input
route53EditSelectIdx int // index for select-type fields (record type)
route53ChangeID string // for status polling
route53ChangeStatus string // "PENDING" / "INSYNC"
route53Polling bool // polling active

// IAM credentials state
iamKeys []awsservice.AccessKey
iamKeyIdx int
Expand Down Expand Up @@ -290,6 +304,43 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.screen = screenRoute53RecordList
return m, nil

case route53ActionDoneMsg:
if msg.err != nil {
m.errMsg = msg.err.Error()
m.screen = screenError
return m, nil
}
m.route53ChangeID = msg.changeID
m.route53ChangeStatus = "PENDING"
m.route53Polling = true
// Reload records and start polling change status
if m.selectedRoute53Zone != nil {
return m, tea.Batch(
m.loadRoute53Records(m.selectedRoute53Zone.ID),
m.pollRoute53ChangeStatus(),
)
}
m.screen = screenRoute53RecordList
return m, nil

case route53ChangeStatusMsg:
if msg.err != nil {
m.route53Polling = false
return m, nil
}
m.route53ChangeStatus = msg.status
if msg.status == "INSYNC" {
m.route53Polling = false
return m, nil
}
return m, m.tickRoute53Poll()

case route53PollTickMsg:
if m.route53Polling {
return m, m.pollRoute53ChangeStatus()
}
return m, nil

case rdsInstancesLoadedMsg:
m.rdsInstances = msg.instances
m.filteredRDS = msg.instances
Expand Down Expand Up @@ -529,6 +580,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.updateRoute53RecordList(msg)
case screenRoute53RecordDetail:
return m.updateRoute53RecordDetail(msg)
case screenRoute53RecordCreate:
return m.updateRoute53RecordCreate(msg)
case screenRoute53RecordEdit:
return m.updateRoute53RecordEdit(msg)
case screenRoute53RecordDeleteConfirm:
return m.updateRoute53RecordDeleteConfirm(msg)
case screenSecretList:
return m.updateSecretList(msg)
case screenSecretDetail:
Expand Down Expand Up @@ -678,6 +735,12 @@ func (m Model) View() string {
v = m.viewRoute53RecordList()
case screenRoute53RecordDetail:
v = m.viewRoute53RecordDetail()
case screenRoute53RecordCreate:
v = m.viewRoute53RecordCreate()
case screenRoute53RecordEdit:
v = m.viewRoute53RecordEdit()
case screenRoute53RecordDeleteConfirm:
v = m.viewRoute53RecordDeleteConfirm()
case screenSecretList:
v = m.viewSecretList()
case screenSecretDetail:
Expand Down
13 changes: 13 additions & 0 deletions internal/app/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ type route53RecordsLoadedMsg struct {
records []awsservice.DNSRecord
}

type route53ActionDoneMsg struct {
action string
changeID string
err error
}

type route53ChangeStatusMsg struct {
status string
err error
}

type route53PollTickMsg struct{}

type secretsLoadedMsg struct {
secrets []awsservice.Secret
}
Expand Down
Loading
Loading