## 1. Project Scaffolding

- [x] 1.1 Create `pyproject.toml` with package metadata: name=`aaws`, requires-python=`>=3.11`, entry point `aaws = aaws.cli:app`
- [x] 1.2 Add runtime dependencies to `pyproject.toml`: `typer`, `rich`, `boto3`, `openai`, `pyyaml`, `pydantic`, `platformdirs`
- [x] 1.3 Add dev dependencies: `pytest`, `pytest-mock`, `ruff`, `mypy`
- [x] 1.4 Create package directory structure: `src/aaws/__init__.py`, `cli.py`, `providers/`, `safety/`, `executor.py`, `formatter.py`, `session.py`, `config.py`, `errors.py`
- [x] 1.5 Create GitHub Actions workflow `.github/workflows/publish.yml` that runs tests on push and publishes to PyPI on version tag

## 2. Configuration Layer

- [x] 2.1 Define Pydantic model `AawsConfig` covering `llm` (provider, model, api_key, temperature, timeout), `aws` (default_profile, default_region), `safety` (auto_execute_tier, protected_profiles), `output` (format, raw, color)
- [x] 2.2 Implement `load_config()` that reads YAML from `platformdirs.user_config_dir("aaws")/config.yaml`, applies `AAWS_`-prefixed env var overrides, and returns a validated `AawsConfig`
- [x] 2.3 Implement `${ENV_VAR}` syntax resolution in config string values (e.g., `api_key: ${OPENAI_API_KEY}`)
- [x] 2.4 Implement `aaws config init` command that runs an interactive first-run wizard (prompt for provider, model, API key) and writes `config.yaml`
- [x] 2.5 Detect missing config on `aaws` startup and print "No configuration found. Run `aaws config init` to set up." then exit

## 3. LLM Provider Layer

- [x] 3.1 Define `LLMProvider` Protocol with `complete(messages, tool_schema) -> LLMResponse` where `LLMResponse` holds `command`, `explanation`, `risk_tier`, `clarification`
- [x] 3.2 Implement `BedrockProvider`: uses `boto3` with the active AWS profile, invokes Claude via `bedrock-runtime converse` API with tool use, defaults to `anthropic.claude-3-5-haiku-20241022-v1:0`
- [x] 3.3 Implement `OpenAIProvider`: uses `openai` SDK with function calling, resolves API key from config/env
- [x] 3.4 Define tool/function calling schema: `aws_command` tool with properties `command` (string), `explanation` (string), `risk_tier` (integer, enum 0â€?), `clarification` (string, nullable)
- [x] 3.5 Implement fallback JSON parsing (strip markdown code fences, parse JSON) for providers without tool calling support
- [x] 3.6 Implement `get_provider(config: AawsConfig) -> LLMProvider` factory that returns the correct provider based on config
- [x] 3.7 Detect `AccessDeniedException` from Bedrock and surface: "Model access not enabled. Go to AWS Console â†?Bedrock â†?Model access to enable `<model>`."

## 4. NL-to-Command Translation

- [x] 4.1 Write the system prompt string: instructs the LLM to return only valid `aws` CLI commands via tool call, with AWS profile/region as context, and to return `clarification` instead of a command when the input is ambiguous
- [x] 4.2 Implement `translate(nl_input: str, profile: str, region: str, history: list, provider: LLMProvider) -> LLMResponse` that builds prompt with context and calls the provider
- [x] 4.3 Implement command validation: assert `response.command.startswith("aws ")`, retry once with corrective instruction on failure, raise `TranslationError` if retry also fails
- [x] 4.4 Handle clarification responses: if `response.clarification` is non-null, print the clarification prompt to the user and exit (one-shot mode) or re-prompt (session mode)

## 5. Safety Classification Layer

- [x] 5.1 Create `safety/tier_table.py` with a static dict mapping AWS CLI command prefixes (e.g., `aws s3 rb`, `aws ec2 terminate-instances`) to tier integers 0â€?
- [x] 5.2 Implement `classify(command: str, llm_tier: int) -> int` that looks up the static table first and falls back to `llm_tier` if not found
- [x] 5.3 Implement `is_protected_profile(profile: str, patterns: list[str]) -> bool` using `fnmatch.fnmatch` for glob pattern matching
- [x] 5.4 Implement `apply_safety_gate(command: str, tier: int, profile: str, config: AawsConfig) -> bool` that enforces confirmation flows per tier and raises `ProtectedProfileError` for write operations on protected profiles
- [x] 5.5 Implement tier 0 auto-execute (no prompt)
- [x] 5.6 Implement tier 1 `[y/n]` confirmation prompt using Rich
- [x] 5.7 Implement tier 2 warning panel + `type "yes"` prompt + optional `--dry-run` offer for EC2 commands
- [x] 5.8 Implement tier 3 refusal message; allow execution only if `--i-accept-responsibility` flag is set (falls through to tier 2 flow)

## 6. Subprocess Execution

- [x] 6.1 Implement `execute(command: str) -> ExecutionResult` using `subprocess.run(shlex.split(command), capture_output=True, text=True)` â€?never `shell=True`
- [x] 6.2 Implement `aws` CLI presence check at startup: `shutil.which("aws")` â€?if None, print install instructions and exit with code 1
- [x] 6.3 Return `ExecutionResult(stdout, stderr, exit_code)` and propagate to formatter and error recovery layers

## 7. Output Formatting

- [x] 7.1 Implement shape detection: inspect parsed JSON for top-level list value (â†?table), singular dict value (â†?card), or neither (â†?syntax-highlighted JSON via Rich)
- [x] 7.2 Implement `render_table(data: list[dict], resource_type: str)` using Rich `Table`; use a `COLUMN_HINTS` dict for preferred columns per resource type, fall back to first N keys
- [x] 7.3 Implement `render_card(data: dict)` using Rich `Panel` with key-value rows
- [x] 7.4 Implement `render_error(stderr: str, suggestion: str | None)` using a red Rich `Panel`
- [x] 7.5 Implement empty response detection: if parsed result is empty list or empty dict, print "No results."
- [x] 7.6 Implement `--raw` flag bypass: when set, write `result.stdout` directly to `sys.stdout` with no formatting

## 8. Error Recovery

- [x] 8.1 Implement `classify_error(stderr: str) -> ErrorType` using regex pattern matching for: `ExpiredTokenException`, `Unable to locate credentials`, `NoCredentialsError`, `AccessDeniedException`, `BucketNotEmpty`, `NoSuchBucket`, and generic 4xx resource errors
- [x] 8.2 Implement hardcoded actionable messages for credential and permission errors (no LLM call needed)
- [x] 8.3 Implement `interpret_error(command: str, stderr: str, provider: LLMProvider) -> str` that sends the failed command + error to the LLM for plain-English interpretation and next-step suggestions
- [x] 8.4 Route errors: credential/auth errors â†?hardcoded message; resource errors â†?LLM interpretation; unknown errors â†?styled error panel with raw stderr
- [x] 8.5 Implement timeout detection for LLM calls (`asyncio.timeout` or `requests` timeout) and display "Request timed out. Check your network connection and try again."

## 9. Core CLI Commands

- [x] 9.1 Implement root `aaws "<request>"` command: load config â†?translate â†?classify â†?safety gate â†?execute â†?format output
- [x] 9.2 Add `--profile` and `--region` flags to override AWS context per invocation
- [x] 9.3 Add `--raw` flag to bypass output formatting
- [x] 9.4 Add `--dry-run` flag to show the generated command without executing it
- [x] 9.5 Add `--i-accept-responsibility` flag to override tier 3 refusal
- [x] 9.6 Implement `aaws explain "<aws command>"` subcommand: send the existing command to the LLM for explanation (no execution)
- [x] 9.7 Implement `aaws config init` wizard (see task 2.4)
- [x] 9.8 Implement `aaws config show` that prints the resolved effective config (with secrets masked)

## 10. Interactive Session Mode

- [x] 10.1 Implement `aaws session` subcommand that starts a Rich-styled REPL loop
- [x] 10.2 Display session header panel showing active profile, region, and "type 'exit' to quit"
- [x] 10.3 Maintain `history: list[dict]` in-process, append each `{role: user/assistant, content: ...}` exchange
- [x] 10.4 Implement bounded history: trim to last 10 exchanges before each LLM call
- [x] 10.5 Handle `exit`/`quit` input to break the loop cleanly
- [x] 10.6 Handle `KeyboardInterrupt` (Ctrl+C) to exit with a goodbye message and no stack trace

## 11. Tests

- [x] 11.1 Unit test `classify()` for known tier 0, 1, 2, 3 command prefixes and LLM fallback
- [x] 11.2 Unit test `is_protected_profile()` with exact matches and glob patterns (e.g., `prod-*`)
- [x] 11.3 Unit test command validation: assert `.startswith("aws ")` passes and fails correctly
- [x] 11.4 Unit test `classify_error()` regex patterns for all named error types
- [x] 11.5 Unit test config loading: env var overrides, `${ENV_VAR}` resolution, missing config defaults
- [x] 11.6 Unit test output shape detection: list â†?table, dict â†?card, empty â†?"No results."
- [x] 11.7 Integration test: mock LLM provider + mock subprocess, run the full `aaws "list s3 buckets"` pipeline end-to-end
