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
69 changes: 69 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# AGENTS.md

Guidance for coding agents working in this repository.

## Repo Snapshot

- Project: `completely` (Ruby gem that generates Bash completion scripts from YAML).
- Key generation code:
- `lib/completely/pattern.rb`
- `lib/completely/templates/template.erb`
- Core behavior tests:
- `spec/completely/integration_spec.rb`
- `spec/completely/commands/generate_spec.rb`

## Working Rules

- Keep changes minimal and localized, especially in:
- completion-word serialization (`Pattern`)
- generated script runtime behavior (`template.erb`)
- Do not change generated approvals.
- Do not run approval prompts interactively on behalf of the developer.
- If an approval spec changes, stop and ask the developer to review/approve manually.
- Prefer adding regression coverage in integration fixtures for completion behavior changes.

## Fast Validation Loop

Run these first after edits:

```bash
respec tagged script_quality
respec only integration
```

If touching quoting/escaping or dynamic completions, also run:

```bash
respec only pattern
respec only completions
```

## Formatting and Linting Notes

- `shellcheck` and `shfmt` requirements are enforced by specs tagged `:script_quality` in `spec/completely/commands/generate_spec.rb`.
- `shfmt` uses flags:
- `shfmt -d -i 2 -ci completely.bash`
- Small whitespace differences in heredoc/redirect forms (like `<<<"$x"` vs `<<< "$x"`) can fail shfmt.

## Approval Specs

- Some specs use `rspec_approvals` and may prompt interactively if output changes.
- In non-interactive runs this can fail with `Errno::ENOTTY`.
- Approval decisions are always developer-owned. Agents should not approve/update snapshots.

## Completion Semantics to Preserve

- Literal YAML words with spaces/quotes must complete correctly.
- Dynamic `$(...)` entries must produce multiple completion candidates when command output contains multiple words.
- `<file>`, `<directory>`, and other `<...>` entries map to `compgen -A ...` actions and should remain unaffected by `-W` serialization changes.

## Manual Repro Pattern

Useful local sanity check:

```bash
cd dev
ruby -I../lib ../bin/completely test "cli "
```

Expected: sensible mixed output for dynamic values and quoted/spaced literals.
16 changes: 13 additions & 3 deletions lib/completely/pattern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,22 @@ def compgen
def compgen!
result = []
result << actions.join(' ').to_s if actions.any?
result << %[-W "$(#{function_name} #{quoted_words.join ' '})"] if words.any?
result << %[-W "$(#{function_name} #{serialized_words.join ' '})"] if words.any?
result.any? ? result.join(' ') : nil
end

def quoted_words
@quoted_words ||= words.map { |word| %("#{escape_for_double_quotes word}") }
def serialized_words
@serialized_words ||= words.map { |word| serialize_word(word) }
end

def serialize_word(word)
return word if dynamic_word?(word)

%("#{escape_for_double_quotes word}")
end

def dynamic_word?(word)
word.match?(/\A\$\(.+\)\z/)
end

def escape_for_double_quotes(word)
Expand Down
1 change: 1 addition & 0 deletions lib/completely/templates/template.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
% patterns.each do |pattern|
% next if pattern.empty?
<%= pattern.case_string %>)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen <%= pattern.compgen %> -- "$cur")
;;

Expand Down
2 changes: 1 addition & 1 deletion lib/completely/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Completely
VERSION = '0.7.4'
VERSION = '0.8.0.rc2'
end
25 changes: 18 additions & 7 deletions spec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@

## Running tests

You can run specs with `rspec` as usual.

We recommend using [`respec`][2], which wraps common spec workflows:

```bash
$ rspec
rspec
# or
$ run spec
# or, to run just tests in a given file
$ run spec zsh
# or, to run just specs tagged with :focus
$ run spec :focus
respec
```

You might need to prefix the commands with `bundle exec`, depending on the way
Ruby is installed.

Useful helper shortcuts:

```bash
# script quality checks (shellcheck + shfmt generated script tests)
respec tagged script_quality

# integration behavior suite
respec only integration
```

## Interactive Approvals

Some tests may prompt you for an interactive approval of changes. This
Expand All @@ -29,4 +39,5 @@ ZSH compatibility test is done by running the completely tester script inside a
zsh container. This is all done automatically by `spec/completely/zsh_spec.rb`.


[1]: https://github.com/dannyben/rspec_approvals
[1]: https://github.com/dannyben/rspec_approvals
[2]: https://github.com/DannyBen/respec
9 changes: 7 additions & 2 deletions spec/approvals/cli/generated-script
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,27 @@ _mygit_completions() {

case "$compline" in
'status'*'--branch')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*'-b')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
;;

*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
;;

Expand Down
9 changes: 7 additions & 2 deletions spec/approvals/cli/generated-script-alt
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,27 @@ _mycomps() {

case "$compline" in
'status'*'--branch')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*'-b')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mycomps_filter "--bare")" -- "$cur")
;;

*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
;;

Expand Down
9 changes: 7 additions & 2 deletions spec/approvals/cli/generated-wrapped-script
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,27 @@ give_comps() {
echo $''
echo $' case "$compline" in'
echo $' \'status\'*\'--branch\')'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format=\'%(refname:short)\' 2>/dev/null)")" -- "$cur")'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format=\'%(refname:short)\' 2>/dev/null))" -- "$cur")'
echo $' ;;'
echo $''
echo $' \'status\'*\'-b\')'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format=\'%(refname:short)\' 2>/dev/null)")" -- "$cur")'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format=\'%(refname:short)\' 2>/dev/null))" -- "$cur")'
echo $' ;;'
echo $''
echo $' \'status\'*)'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")'
echo $' ;;'
echo $''
echo $' \'init\'*)'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")'
echo $' ;;'
echo $''
echo $' *)'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")'
echo $' ;;'
echo $''
Expand Down
9 changes: 7 additions & 2 deletions spec/approvals/cli/test/completely-tester-1.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,27 @@ _mygit_completions() {

case "$compline" in
'status'*'--branch')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*'-b')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
;;

*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
;;

Expand Down
9 changes: 7 additions & 2 deletions spec/approvals/cli/test/completely-tester-2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,27 @@ _mygit_completions() {

case "$compline" in
'status'*'--branch')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*'-b')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
;;

*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
;;

Expand Down
9 changes: 7 additions & 2 deletions spec/approvals/cli/test/completely-tester.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,27 @@ _mygit_completions() {

case "$compline" in
'status'*'--branch')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*'-b')
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter $(git branch --format='%(refname:short)' 2>/dev/null))" -- "$cur")
;;

'status'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
;;

*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
;;

Expand Down
3 changes: 3 additions & 0 deletions spec/approvals/completions/function
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ send_completions() {
echo $''
echo $' case "$compline" in'
echo $' \'generate\'*)'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help" "--force")" -- "$cur")'
echo $' ;;'
echo $''
echo $' \'init\'*)'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help")" -- "$cur")'
echo $' ;;'
echo $''
echo $' *)'
echo $' # shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions'
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help" "--version" "init" "generate")" -- "$cur")'
echo $' ;;'
echo $''
Expand Down
3 changes: 3 additions & 0 deletions spec/approvals/completions/script
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,17 @@ _completely_completions() {

case "$compline" in
'generate'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help" "--force")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help")" -- "$cur")
;;

*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help" "--version" "init" "generate")" -- "$cur")
;;

Expand Down
1 change: 1 addition & 0 deletions spec/approvals/completions/script-complete-options
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ _mygit_completions() {

case "$compline" in
*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "status" "commit")" -- "$cur")
;;

Expand Down
2 changes: 2 additions & 0 deletions spec/approvals/completions/script-only-spaces
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ _completely_completions() {

case "$compline" in
'generate'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_completely_completions_filter "--help" "--force")" -- "$cur")
;;

'init'*)
# shellcheck disable=SC2046 # intentional splitting for dynamic $(...) completions
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_completely_completions_filter "--help")" -- "$cur")
;;

Expand Down
Loading