awsquery 1.0.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {awsquery-1.0.0 → awsquery-1.2.0}/.github/workflows/quality.yml +5 -1
  2. {awsquery-1.0.0 → awsquery-1.2.0}/.gitignore +8 -1
  3. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/CONTEXT.md +79 -0
  4. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/EXECUTION.md +147 -0
  5. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/ISSUE.md +72 -0
  6. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/PLAN.html +320 -0
  7. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/PLAN.md +773 -0
  8. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/RESEARCH.html +382 -0
  9. awsquery-1.2.0/.issues/ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action/RESEARCH.md +625 -0
  10. awsquery-1.2.0/AGENTS.md +62 -0
  11. {awsquery-1.0.0 → awsquery-1.2.0}/Makefile +35 -0
  12. {awsquery-1.0.0 → awsquery-1.2.0}/PKG-INFO +446 -18
  13. {awsquery-1.0.0 → awsquery-1.2.0}/README.md +440 -12
  14. awsquery-1.2.0/llms-full.txt +390 -0
  15. awsquery-1.2.0/llms.txt +21 -0
  16. {awsquery-1.0.0 → awsquery-1.2.0}/pyproject.toml +13 -7
  17. awsquery-1.2.0/src/awsquery/agent-reference.md +390 -0
  18. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/cli.py +496 -211
  19. awsquery-1.2.0/src/awsquery/config.py +160 -0
  20. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/core.py +84 -64
  21. awsquery-1.2.0/src/awsquery/data_fields.yaml +41 -0
  22. awsquery-1.2.0/src/awsquery/default_actions.yaml +54 -0
  23. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/default_filters.yaml +351 -602
  24. awsquery-1.2.0/src/awsquery/errors.py +245 -0
  25. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/filter_validator.py +2 -2
  26. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/formatters.py +191 -99
  27. awsquery-1.2.0/src/awsquery/introspection.py +222 -0
  28. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/security.py +34 -2
  29. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/shapes.py +170 -28
  30. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/utils.py +8 -1
  31. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery.egg-info/PKG-INFO +446 -18
  32. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery.egg-info/SOURCES.txt +19 -0
  33. {awsquery-1.0.0 → awsquery-1.2.0}/tests/conftest.py +18 -0
  34. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_end_to_end.py +28 -17
  35. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_feature_combinations.py +37 -31
  36. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_keys_behavior.py +30 -12
  37. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_multi_level.py +4 -4
  38. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_session_management.py +69 -4
  39. awsquery-1.2.0/tests/unit/test_agent_reference.py +201 -0
  40. awsquery-1.2.0/tests/unit/test_cli_flags.py +1197 -0
  41. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_cli_help_output.py +28 -0
  42. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_cli_parser.py +78 -1
  43. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_cli_session_args.py +3 -1
  44. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_config_loading_simple.py +34 -2
  45. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_core.py +116 -8
  46. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_default_column_filters.py +319 -7
  47. awsquery-1.2.0/tests/unit/test_errors.py +500 -0
  48. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_formatters.py +562 -23
  49. awsquery-1.2.0/tests/unit/test_introspection.py +393 -0
  50. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_keys_display_fix.py +4 -8
  51. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_security.py +14 -5
  52. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_security_edge_cases.py +2 -1
  53. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_shapes.py +209 -10
  54. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_tags_transformation.py +5 -8
  55. awsquery-1.2.0/uv.lock +3395 -0
  56. awsquery-1.0.0/src/awsquery/config.py +0 -80
  57. awsquery-1.0.0/tests/unit/test_cli_flags.py +0 -279
  58. {awsquery-1.0.0 → awsquery-1.2.0}/.claude/agents/code-reviewer.md +0 -0
  59. {awsquery-1.0.0 → awsquery-1.2.0}/.claude/agents/makefile-optimizer.md +0 -0
  60. {awsquery-1.0.0 → awsquery-1.2.0}/.claude/agents/python-infra-automator.md +0 -0
  61. {awsquery-1.0.0 → awsquery-1.2.0}/.claude/agents/test-writer.md +0 -0
  62. {awsquery-1.0.0 → awsquery-1.2.0}/.claude/settings.local.json +0 -0
  63. {awsquery-1.0.0 → awsquery-1.2.0}/.coveragerc +0 -0
  64. {awsquery-1.0.0 → awsquery-1.2.0}/.devcontainer/devcontainer.json +0 -0
  65. {awsquery-1.0.0 → awsquery-1.2.0}/.dockerignore +0 -0
  66. {awsquery-1.0.0 → awsquery-1.2.0}/.flake8 +0 -0
  67. {awsquery-1.0.0 → awsquery-1.2.0}/.github/workflows/release.yml +0 -0
  68. {awsquery-1.0.0 → awsquery-1.2.0}/.github/workflows/test.yml +0 -0
  69. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/e1bzl-fix-remaining-15-broken-suffix-entries-surfaced-by-audit/EXECUTION.html +0 -0
  70. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/e1bzl-fix-remaining-15-broken-suffix-entries-surfaced-by-audit/EXECUTION.md +0 -0
  71. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/e1bzl-fix-remaining-15-broken-suffix-entries-surfaced-by-audit/ISSUE.md +0 -0
  72. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/e1bzl-fix-remaining-15-broken-suffix-entries-surfaced-by-audit/PLAN.md +0 -0
  73. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/CONTEXT.md +0 -0
  74. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/EXECUTION.html +0 -0
  75. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/EXECUTION.md +0 -0
  76. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/ISSUE.md +0 -0
  77. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/PLAN.html +0 -0
  78. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/PLAN.md +0 -0
  79. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/RESEARCH.html +0 -0
  80. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/RESEARCH.md +0 -0
  81. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/research/codebase.md +0 -0
  82. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/research/ecosystem.md +0 -0
  83. {awsquery-1.0.0 → awsquery-1.2.0}/.issues/archive/sue5t-fix-tags-default-filters-and-add-additive-column-syntax/research/pitfalls.md +0 -0
  84. {awsquery-1.0.0 → awsquery-1.2.0}/CLAUDE.md +0 -0
  85. {awsquery-1.0.0 → awsquery-1.2.0}/Dockerfile +0 -0
  86. {awsquery-1.0.0 → awsquery-1.2.0}/Dockerfile.prod +0 -0
  87. {awsquery-1.0.0 → awsquery-1.2.0}/LICENSE +0 -0
  88. {awsquery-1.0.0 → awsquery-1.2.0}/compose.yaml +0 -0
  89. {awsquery-1.0.0 → awsquery-1.2.0}/coverage.json +0 -0
  90. {awsquery-1.0.0 → awsquery-1.2.0}/debug-output.txt +0 -0
  91. {awsquery-1.0.0 → awsquery-1.2.0}/docs/DOCKER.md +0 -0
  92. {awsquery-1.0.0 → awsquery-1.2.0}/docs/mutation-testing.md +0 -0
  93. {awsquery-1.0.0 → awsquery-1.2.0}/policy.json +0 -0
  94. {awsquery-1.0.0 → awsquery-1.2.0}/pytest.ini +0 -0
  95. {awsquery-1.0.0 → awsquery-1.2.0}/requirements.txt +0 -0
  96. {awsquery-1.0.0 → awsquery-1.2.0}/scripts/README.md +0 -0
  97. {awsquery-1.0.0 → awsquery-1.2.0}/scripts/audit_default_filters.py +0 -0
  98. {awsquery-1.0.0 → awsquery-1.2.0}/scripts/validate-awsquery.sh +0 -0
  99. {awsquery-1.0.0 → awsquery-1.2.0}/setup.cfg +0 -0
  100. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/__init__.py +0 -0
  101. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/auto_filters.py +0 -0
  102. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/case_utils.py +0 -0
  103. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/filters.py +0 -0
  104. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery/policy.json +0 -0
  105. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery.egg-info/dependency_links.txt +0 -0
  106. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery.egg-info/entry_points.txt +0 -0
  107. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery.egg-info/requires.txt +0 -0
  108. {awsquery-1.0.0 → awsquery-1.2.0}/src/awsquery.egg-info/top_level.txt +0 -0
  109. {awsquery-1.0.0 → awsquery-1.2.0}/tests/__init__.py +0 -0
  110. {awsquery-1.0.0 → awsquery-1.2.0}/tests/fixtures/__init__.py +0 -0
  111. {awsquery-1.0.0 → awsquery-1.2.0}/tests/fixtures/aws_responses.py +0 -0
  112. {awsquery-1.0.0 → awsquery-1.2.0}/tests/fixtures/policy_samples.py +0 -0
  113. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/__init__.py +0 -0
  114. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_autocomplete_resilience.py +0 -0
  115. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_multi_level_calls.py +0 -0
  116. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_parameter_option_integration.py +0 -0
  117. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_parameter_type_auto_wrapping.py +0 -0
  118. {awsquery-1.0.0 → awsquery-1.2.0}/tests/integration/test_shape_aware_integration.py +0 -0
  119. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/__init__.py +0 -0
  120. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_auto_filters.py +0 -0
  121. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_case_utils.py +0 -0
  122. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_cli_arg_processing.py +0 -0
  123. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_column_ordering.py +0 -0
  124. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_core_inference.py +0 -0
  125. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_core_string_handling.py +0 -0
  126. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_critical_mutations.py +0 -0
  127. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_cross_service_hints.py +0 -0
  128. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_filter_edge_cases.py +0 -0
  129. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_filter_implementation.py +0 -0
  130. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_filter_parameter_extraction.py +0 -0
  131. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_filter_validator.py +0 -0
  132. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_formatter_edge_cases.py +0 -0
  133. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_formatters_data_processing.py +0 -0
  134. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_input_hint_parameter.py +0 -0
  135. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_messaging_enhancements.py +0 -0
  136. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_parameter_option.py +0 -0
  137. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_parameter_type_validation.py +0 -0
  138. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_preemptive_multi_step.py +0 -0
  139. {awsquery-1.0.0 → awsquery-1.2.0}/tests/unit/test_smart_prefix_matching.py +0 -0
@@ -45,4 +45,8 @@ jobs:
45
45
 
46
46
  - name: Run security checks
47
47
  run: |
48
- make security-check
48
+ make security-check
49
+
50
+ - name: Check agent reference mirror
51
+ run: |
52
+ make llms-check
@@ -215,4 +215,11 @@ __marimo__/
215
215
 
216
216
  sample-responses
217
217
  test_env
218
- awsinfo
218
+ awsinfo
219
+ # Codebase graph output (local tooling)
220
+ graphify-out/
221
+
222
+ # Local working directories and captured command output
223
+ .development/
224
+ .worktrees/
225
+ output.txt
@@ -0,0 +1,79 @@
1
+ # CONTEXT — ty5dx
2
+
3
+ Design decisions captured before research/planning. The researcher and planner
4
+ must treat the "Decisions" section as locked.
5
+
6
+ ## Decisions (locked — research/planner must follow)
7
+
8
+ - **Config location: a new `src/awsquery/default_actions.yaml`**, flat
9
+ `service: action` map (`ec2: describe_instances`). Not a key inside
10
+ `default_filters.yaml`.
11
+ **Why:** two walkers assume the strict `service -> action -> {columns: [...]}`
12
+ shape — `scripts/audit_default_filters.py:71` (`opcfg.get("columns", [])`) and
13
+ `tests/unit/test_default_column_filters.py:353`. A sibling string key would
14
+ raise `AttributeError: 'str' object has no attribute 'get'` in both. A separate
15
+ file leaves every existing walker untouched.
16
+ Must be added to the package data list in `pyproject.toml:82` alongside
17
+ `policy.json` and `default_filters.yaml`, or it will not ship in the wheel.
18
+
19
+ - **Loader mirrors the existing config module.** Add `load_default_actions()`
20
+ (`@lru_cache(maxsize=1)`, same FileNotFoundError / YAMLError / generic
21
+ fallbacks returning `{}`) and `get_default_action(service)` to
22
+ `src/awsquery/config.py`, shaped exactly like `load_default_filters()` /
23
+ `get_default_columns()`: lowercased lookup, `debug_print` on both hit and
24
+ miss, `None` when unset.
25
+
26
+ - **Action names only — no default parameters.** The map value is the operation
27
+ name and nothing else. Output shaping stays with `default_filters.yaml`
28
+ columns.
29
+
30
+ - **Defaults are restricted to operations with no required parameters.** A
31
+ service whose natural listing needs an argument (e.g. `logs
32
+ describe-log-streams` needs `LogGroupName`) gets no default at all rather
33
+ than a default that fans out. `awsquery <service>` must always be a single
34
+ immediate call — no multi-level parameter resolution triggered by a default.
35
+
36
+ - **Coverage: broad sweep (~50+ services).** Cover every service already in
37
+ `default_filters.yaml` that has an obvious parameterless list/describe entry
38
+ point. Skip any service where the entry point needs parameters (per the rule
39
+ above) or where no single operation is clearly "the" default.
40
+
41
+ - **Announcement: debug only.** A `debug_print` records which default was
42
+ chosen. No hint on stderr during normal runs — stdout and stderr stay clean
43
+ for piping.
44
+
45
+ - **Fallback when a service has no default:** print that service's available
46
+ actions to stderr, exit non-zero. `awsquery` with no service at all keeps
47
+ today's behavior (list services on stdout, exit 0).
48
+
49
+ - **Resolved action takes the normal code path.** Once substituted it flows
50
+ through `validate_readonly`, default column filters, and any user `--`
51
+ filters/flags unchanged. `awsquery ec2 -- InstanceId` must equal
52
+ `awsquery ec2 describe-instances -- InstanceId`.
53
+
54
+ ## Claude's Discretion (research should explore options)
55
+
56
+ - Exact insertion point in `main()` for the substitution — the current dead end
57
+ is the `if not args.service or not args.action` branch at
58
+ `src/awsquery/cli.py:830`, but `_build_filter_argv` (`cli.py:303`) also
59
+ branches on `args.action` and may need the resolved value.
60
+ - Whether the fallback action listing reuses `get_service_operations()` /
61
+ `get_service_valid_operations()` (already in `cli.py`, used by
62
+ `action_completer`) or needs a shared helper.
63
+ - Naming: `get_default_action` vs. something clearer; the surrounding module
64
+ favours `get_default_*`.
65
+ - How the curated map is validated: a test asserting every entry is a real
66
+ botocore operation, is ReadOnly per `policy.json`, and has no required
67
+ parameters is the obvious guard — shape and location are the researcher's
68
+ call.
69
+ - Whether kebab-case (`describe-instances`) or snake_case
70
+ (`describe_instances`) is the canonical value in the YAML;
71
+ `normalize_action_name` (`utils.py`) already exists for conversion.
72
+
73
+ ## Deferred (out of scope for this issue)
74
+
75
+ - Botocore heuristic inference for services with no curated entry — curated map
76
+ only in this issue.
77
+ - Default parameters (`MaxResults`, filters) attached to a default action.
78
+ - User-level override of the curated map (`~/.awsquery/` config).
79
+ - Refreshing the stale `.issues/MAP.md` (99 days old at time of writing).
@@ -0,0 +1,147 @@
1
+ # Execution: Add per-service default actions so `awsquery ec2` runs without an action
2
+
3
+ **Started:** 2026-09-08T13:11:12Z
4
+ **Status:** complete
5
+ **Branch:** ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action
6
+
7
+ ## Baseline
8
+
9
+ `python3 -m pytest tests/ -q` before any changes: 1346 passed.
10
+
11
+ ## Execution Log
12
+
13
+ - [x] Task 1: Add the curated default_actions.yaml and register it for packaging — commit 1366550
14
+ - [x] Task 2: Add load_default_actions() and get_default_action() to config.py — commit 71056cf
15
+ - [x] Task 3: Pre-parse argv injection in cli.py plus the no-default stderr fallback — commit e440aea
16
+ - Deviation: [Rule 1 - Bug] `test_argparse_system_exit_handling` in
17
+ tests/integration/test_end_to_end.py asserted the old dead-end behavior (unrecognized
18
+ `--unknown-option value` argv left `args.service='value'`, `args.action=None`, and the old
19
+ combined branch printed the service list and exited 0). The new split branch correctly
20
+ treats a service with no action as "no default configured", prints that service's actions
21
+ on stderr, and exits 1 for the now-nonsensical positional `value`. Changed the test's argv
22
+ to `["awsquery", "--unknown-option"]` (no stray positional) so it still exercises the
23
+ SystemExit(0)-with-unrecognized-flag path it was written for, without asserting on the
24
+ accidental "value"-as-service artifact. Included in commit e440aea.
25
+ - [x] Task 4: Curated-map validation, injector, flag pass-through and casing tests — commit 8055ead
26
+ - Deviation: [Rule 1 - Bug] `_inject_default_action`'s `debug_print("Using default action...")`
27
+ was silently swallowed on every real invocation: it runs during the *first*
28
+ `parser.parse_known_args()` call, before `utils.set_debug_enabled(args.debug)` is reached
29
+ later in `main()`, so the global debug flag was still `False` at the moment the injector's
30
+ debug_print executed — the line could never appear on stderr for `awsquery -d ec2`,
31
+ contradicting Task 4's own required test ("`-d ec2` emits the `Using default action` line
32
+ on stderr") and the plan's success criterion that a debug_print announces the chosen
33
+ default. Fixed by pre-enabling debug mode from a lightweight scan of raw `sys.argv[1:]`
34
+ for `-d`/`--debug` immediately before the injection call; the later
35
+ `utils.set_debug_enabled(args.debug)` (now with its redundant `from . import utils`
36
+ removed) still re-applies the authoritative parsed value. Included in commit 8055ead.
37
+ - [x] Task 5: Rewrite the vacuously-green integration test at test_end_to_end.py:889 — commit ff45e3b
38
+ - [x] Task 6: Document the default-action shortcut in the README — commit 848bb9c
39
+
40
+ ## Verification Results
41
+
42
+ **`make test`:** 1420 passed (baseline before this issue: 1346 passed; net +74 new/rewritten
43
+ tests across the six tasks). Full output tail:
44
+ ```
45
+ ============================ 1420 passed in 11.76s =============================
46
+ ```
47
+
48
+ **`make lint`:** clean.
49
+ ```
50
+ flake8 src/ tests/ --count --statistics --show-source
51
+ 0
52
+ pylint src/awsquery
53
+ Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
54
+ ```
55
+
56
+ **`make format-check`:** clean.
57
+ ```
58
+ black --check --diff src/ tests/
59
+ All done! (62 files would be left unchanged)
60
+ isort --check-only --diff src/ tests/
61
+ (no output — clean)
62
+ ```
63
+
64
+ **Coverage gate:** `pytest.ini`/`Makefile` do not actually pass `--cov-fail-under` to `make
65
+ test` (coverage is opt-in via `make coverage`); nothing to fail on. `make test` completed with
66
+ 0 non-coverage failures either way.
67
+
68
+ **Packaging spot-check** (plan-mandated, not coverable by unit tests):
69
+ ```
70
+ python3 -m build
71
+ unzip -l dist/*.whl | grep default_actions.yaml
72
+ 1604 ... awsquery/default_actions.yaml
73
+ ```
74
+ Confirms `default_actions.yaml` ships inside the built wheel. `dist/`, `build/`, and
75
+ `src/awsquery.egg-info/` were removed after the check (build artifacts, not part of the repo).
76
+
77
+ **Manual injector spot-check** (matches README examples):
78
+ ```
79
+ _inject_default_action(['ec2']) == ['ec2', 'describe-instances']
80
+ _inject_default_action(['s3', '--', 'Name']) == ['s3', 'list-buckets', '--', 'Name']
81
+ ```
82
+
83
+ ## Deviations from Plan
84
+
85
+ ### Auto-fixed (Rules 1-3)
86
+
87
+ 1. **[Rule 1 - Bug] `test_argparse_system_exit_handling` asserted the old dead-end branch
88
+ behavior** (Task 3, commit e440aea)
89
+ - Found during: Task 3 full-suite verification (`python3 -m pytest tests/ -q`)
90
+ - Issue: `sys.argv = ["awsquery", "--unknown-option", "value"]` left argparse assigning
91
+ `args.service = "value"` (positional match) and `args.action = None`. Before this issue,
92
+ the combined `if not args.service or not args.action:` branch printed the service list and
93
+ exited 0 for this case — the test asserted `exit code == 0`. After splitting the branch
94
+ (per Task 3's own spec), a service with no action but no configured default now correctly
95
+ prints that service's actions on stderr and exits 1, so `"value"` (not a real service)
96
+ produced `ERROR: Unknown service 'value'` and exit 1, failing the old assertion.
97
+ - Fix: Changed the test's argv to `["awsquery", "--unknown-option"]` (no stray positional),
98
+ which still leaves `args.service = None` and exercises the same "unrecognized flag doesn't
99
+ crash the parser, still exits 0" intent the test was written for, without depending on the
100
+ accidental "value"-as-service artifact that the new behavior correctly stops tolerating.
101
+ - Files: `tests/integration/test_end_to_end.py`
102
+
103
+ 2. **[Rule 1 - Bug] `_inject_default_action`'s debug_print was silently swallowed on every real
104
+ invocation** (Task 4, commit 8055ead)
105
+ - Found during: Task 4, writing the required test that `-d ec2` emits the `Using default
106
+ action` line on stderr
107
+ - Issue: the injector's `debug_print(...)` executes during the *first*
108
+ `parser.parse_known_args()` call, which is chronologically before
109
+ `utils.set_debug_enabled(args.debug)` runs later in `main()`. The global debug flag was
110
+ therefore still `False` at the moment the injector's debug_print fired, so the line could
111
+ never appear on stderr for `awsquery -d ec2` — contradicting both Task 4's required test
112
+ and the plan's stated success criterion ("Only a debug_print announces the chosen
113
+ default").
114
+ - Fix: pre-enable debug mode from a lightweight scan of raw `sys.argv[1:]` for `-d`/`--debug`
115
+ immediately before the injection call; the later `utils.set_debug_enabled(args.debug)`
116
+ (with its now-redundant `from . import utils` removed) still re-applies the authoritative
117
+ parsed value, so final debug state is unaffected — only the injector's own debug line was
118
+ previously lost.
119
+ - Files: `src/awsquery/cli.py`
120
+
121
+ ### Blocked (Rule 4)
122
+
123
+ None.
124
+
125
+ ## Discovered Issues
126
+
127
+ None found outside the scope of this issue.
128
+
129
+ ## Self-Check
130
+
131
+ - [x] All files from plan exist (verified: `src/awsquery/default_actions.yaml`,
132
+ `src/awsquery/config.py`, `src/awsquery/cli.py`, `pyproject.toml`, all five test files,
133
+ `README.md`)
134
+ - [x] All 6 commit hashes exist on branch (`git log --oneline` confirmed:
135
+ 1366550, 71056cf, e440aea, 8055ead, ff45e3b, 848bb9c)
136
+ - [x] Full verification suite passes for real: `make test` (1420 passed), `make lint` (clean),
137
+ `make format-check` (clean), plus the plan's packaging spot-check
138
+ - [x] No stubs/TODOs/placeholders introduced (`grep -iE "TODO|FIXME|HACK|XXX|PLACEHOLDER|coming
139
+ soon|not implemented"` over the diff: no matches)
140
+ - [x] No leftover debug code (`grep` over the diff for `console.log`/`debugger`/`binding.pry`/
141
+ `breakpoint()` bare prints: no matches; all `print()`/`debug_print()` calls are intentional
142
+ CLI output or the existing debug-logging convention)
143
+ - **Result:** PASSED
144
+
145
+ **Completed:** 2026-09-08T13:21:59Z
146
+ **Duration:** ~11 minutes
147
+ **Commits:** 6
@@ -0,0 +1,72 @@
1
+ ---
2
+ id: ty5dx
3
+ title: Add per-service default actions so awsquery ec2 runs without an action
4
+ status: done
5
+ ship_state: pr_open
6
+ priority: medium
7
+ labels:
8
+ - enhancement
9
+ - cli
10
+ remote:
11
+ - source: github
12
+ id: '18'
13
+ url: https://github.com/flomotlik/awsquery/issues/18
14
+ ---
15
+
16
+ ## Context
17
+
18
+ Today `awsquery ec2` (service given, no action) falls into the `if not args.service or not args.action` branch in `src/awsquery/cli.py:830` and prints the full list of AWS services — which ignores the service the user just typed and is not useful.
19
+
20
+ `awsinfo` treats the bare service name as a shortcut for its most common read operation. We want the same: `awsquery ec2` should behave as `awsquery ec2 describe-instances`, `awsquery s3` as `awsquery s3 list-buckets`, and so on.
21
+
22
+ The pieces to build on already exist: `src/awsquery/config.py` loads and caches `src/awsquery/default_filters.yaml` (`load_default_filters`), and `get_default_columns(service, action)` already resolves per-service/per-action output columns from it. A default *action* is the missing counterpart to the default *columns*.
23
+
24
+ ## Scope
25
+
26
+ **1. Curated YAML map for default actions**
27
+
28
+ - Add a per-service `default_action:` key to the existing `default_filters.yaml` structure (sibling of the per-action entries), e.g.:
29
+
30
+ ```yaml
31
+ ec2:
32
+ default_action: describe_instances
33
+ describe_instances:
34
+ columns:
35
+ - InstanceId$
36
+ ...
37
+ ```
38
+
39
+ - Hand-curate the mapping for the common services (ec2, s3, rds, lambda, ecs, eks, iam, cloudformation, dynamodb, sqs, sns, elbv2, autoscaling, cloudwatch, logs, ssm, secretsmanager, route53, acm, …). Curated only — no botocore heuristic inference in this issue.
40
+ - Add a `get_default_action(service)` accessor in `src/awsquery/config.py`, matching the existing `get_default_columns` shape (normalized/lowercased lookup, `debug_print` on hit and miss, returns `None` when unset).
41
+
42
+ **2. CLI wiring**
43
+
44
+ - In `main()` (`src/awsquery/cli.py`), when `args.service` is set and `args.action` is empty, resolve the default action via `get_default_action(service)` and continue as if the user had typed it.
45
+ - The resolved action must go through the normal path unchanged: `validate_readonly` security validation, parameter requirements / multi-level resolution, default column filters, and any user-supplied `--` filters and flags. `awsquery ec2 -- InstanceId` must work exactly like `awsquery ec2 describe-instances -- InstanceId`.
46
+ - Emit a `debug_print` recording which default action was chosen.
47
+ - The default action name carries **no default parameters** — it is only the operation name. Output shaping stays with the existing `default_filters.yaml` columns.
48
+
49
+ **3. Fallback when no default is configured**
50
+
51
+ - `awsquery <service>` for a service with no `default_action:` prints the available actions **for that service** to stderr and exits non-zero — replacing today's full-service-list behavior for this case.
52
+ - `awsquery` with no service at all keeps its current behavior (print available services, exit 0).
53
+
54
+ **4. Docs and tests**
55
+
56
+ - Update README/help text to document the shortcut and how to configure `default_action:`.
57
+ - Tests exercise the real implementation with only boto3 mocked, per the repo's testing rules — no pytest markers, no over-mocking.
58
+
59
+ ## Acceptance Criteria
60
+
61
+ - [ ] `default_filters.yaml` carries a `default_action:` entry for each curated service, and the file still parses cleanly via `load_default_filters()`
62
+ - [ ] `config.get_default_action(service)` returns the configured action, is case/format-insensitive in the same way as `get_default_columns`, and returns `None` for unconfigured services
63
+ - [ ] `awsquery ec2` produces the same output as `awsquery ec2 describe-instances`
64
+ - [ ] `awsquery s3` produces the same output as `awsquery s3 list-buckets`
65
+ - [ ] User filters and flags apply to the defaulted action (`awsquery ec2 -- InstanceId` == `awsquery ec2 describe-instances -- InstanceId`)
66
+ - [ ] The defaulted action passes through `validate_readonly` — a non-ReadOnly default would be rejected like any other action
67
+ - [ ] Default column filters from `default_filters.yaml` still apply to the defaulted action
68
+ - [ ] `awsquery <service-without-default>` lists that service's actions on stderr and exits non-zero
69
+ - [ ] `awsquery` with no arguments still lists available services and exits 0
70
+ - [ ] Tests cover: default resolution, unconfigured-service fallback, filter/flag pass-through, and readonly validation of the defaulted action
71
+ - [ ] README documents the shortcut and the `default_action:` config key
72
+ - [ ] `make test`, `make lint`, and `make format-check` pass