This matrix records the behavior boundaries for the Base shell dogfooding work
tracked by #2122 and
#2127. It is an
implementation guide, not a change to the public basectl contract.
The inventory was reconciled with Base on 2026-09-09.
- All 15 production callers of
base_arg_parsecurrently route through the shared helper. The rows below preserve the contract that future changes must protect; they do not authorize another parser rewrite. - There are 21 direct production calls to
base_std_command_pathin the subcommands listed below. No productioncommand -v,type -P,type -p, orwhichcall remains in that directory. - No production subcommand currently calls
base_std_assert_command_exists. It remains available for callers that want its aggregate fatal error, but it is not interchangeable with a quiet path lookup. - Command lookup in BATS helpers, runtime probes, and secret-scanner fixtures is intentional test infrastructure and is outside the production migration scope.
base_arg_parse accepts exact flag and value-option specifications, supports
--option=value for value options, stops option parsing at --, and returns
usage status 2 for malformed or unknown options. A later occurrence of a
non-repeatable option replaces its earlier value; repeatable options must use
the repeatable output-array contract. A value supplied in space-separated form
must not be another recognized option. Callers remain responsible for their
own help precedence, positional grammar, forwarded-argument boundary, and
command-specific validation.
Every migration or parser-adjacent change must record:
- accepted space-separated and equals-separated forms;
- duplicate-option behavior and whether options may be combined;
- positional interpretation and
--handling; - status codes and whether usage goes to stdout or stderr;
- help precedence, including help with invalid or extra arguments;
- arguments forwarded to the Python or declared project command unchanged.
| Caller | Bash-owned argv and boundary | Errors, output, and logging | Focused regression coverage | Disposition |
|---|---|---|---|---|
devcontainer.sh |
Optional project positional; -v, --workspace <path>, --format <format>, and --write. No forwarded-argument boundary. |
Help is immediate. Parser and positional failures print usage/error text and return 2. Formats are validated locally; normal results are delegated to the setup layer. | cli/bash/commands/basectl/tests/devcontainer.bats |
Migrated; preserve the parser seam and workspace/project validation. |
devenv_report.sh |
Optional project positional; -v, --workspace <path>, and --format <format>. No forwarded-argument boundary. |
Help is immediate. Parser, positional, format, and workspace/project failures return 2 with usage/error text; report output is delegated. | cli/bash/commands/basectl/tests/devenv-report.bats |
Migrated; preserve the parser seam and workspace/project validation. |
export_context.sh |
Optional project positional; -v, --workspace <path>, --format <markdown|zip>, --output <path>, --print, and --list-files. No forwarded-argument boundary. |
Help is immediate. Invalid parser, positional, format, or project context returns 2 with usage/error text. Export content belongs to the delegated layer. | cli/bash/commands/basectl/tests/export-context.bats |
Migrated; preserve output-mode and project-context validation. |
repo.sh (repo clone) |
Exactly one repository name positional; --owner <owner>, --path <path>, --dry-run, and -v. No forwarded-argument boundary; the top-level wrapper rejects equals-form options before delegation. |
-h, --help, and help are immediate. Missing values, unknown options, and duplicate positionals return 2 with the existing usage/error text; duplicate values keep the last value and duplicate flags remain accepted. Clone URL/path resolution and gh repo clone delegation remain local. |
cli/bash/commands/basectl/tests/repo.bats clone cases |
Migrated in the #2144 leaf; preserve owner inference, destination safety, dry-run output, and GitHub delegation. |
repo.sh (repo check) |
At most one repository path positional; --agent-guidance, --agent-ready, --release, --format <text|json>, and -v. No forwarded-argument boundary; the top-level wrapper rejects equals-form options before delegation. |
Help remains immediate. Missing format values, unsupported formats, unknown options, and duplicate paths return 2 with the existing usage/error text; duplicate format values keep the last value and duplicate flags remain accepted. Baseline inspection, JSON envelopes, and status propagation remain local. | cli/bash/commands/basectl/tests/repo.bats check cases |
Migrated in the #2144 leaf; preserve path resolution, format-aware usage errors, and repository inspection behavior. |
repo.sh (repo configure) |
At most one repository path positional; --repo, --project, --project-owner, --project-schema, --initiative-option, and --copy-project-fields-from take values; --dry-run, --no-protect-default-branch, --replace-project, --no-project, --release, and -v are flags. No forwarded-argument boundary; the pre-parser preserves legacy help, missing-value, unknown-option, and equals-form behavior. |
Duplicate scalar values keep the last value; repeatable initiative options preserve order. Path/repository inference, dry-run mutation boundaries, GitHub settings, release support files, and Project metadata remain local. | cli/bash/commands/basectl/tests/repo.bats configure cases |
Migrated in the #2144 leaf; preserve repository configuration safety and Project metadata handoff. |
repo_agent_guidance.sh (agent-guidance) |
At most one repository path positional; --repo, --issue, --category, --repo-name, --default-branch, and --validation-command take values; --pr, --dry-run, and -v are flags. No forwarded-argument boundary; the pre-parser preserves option-looking values and legacy help/error behavior before shared parsing. |
Missing values, invalid issue/category/name values, and PR preconditions retain command-specific status and guidance. Duplicate scalar values keep the last value; path resolution, generated-file safety, and optional PR workflow remain local. | cli/bash/commands/basectl/tests/repo.bats agent-guidance cases |
Migrated in the #2144 leaf; preserve generated guidance content and PR/worktree safety boundaries. |
repo_init.sh (repo init) |
Exactly one repository name positional; --path, --repo, --issue, --category, --language, --description, --license, --project, --project-owner, --project-schema, --initiative-option, and --copy-project-fields-from take values; --pr, --agent-ready, --release, visibility/configuration/protection flags, --no-project, --dry-run, and -v are flags. No forwarded-argument boundary; the pre-parser preserves legacy value/error behavior before shared parsing. |
Duplicate scalar values keep the last value; repeatable language and initiative options preserve occurrence order. Language normalization, supported-language validation/deduplication, visibility conflicts, repository validation, baseline generation, and optional PR/configuration boundaries remain local. | cli/bash/commands/basectl/tests/repo.bats init cases |
Migrated in the #2144 leaf; preserve generated baseline content, language metadata, visibility safety, and PR/worktree boundaries. |
repo_installer_template.sh (repo installer-template) |
At most one installer path positional; --repo, --issue, and --category take values; --print/--stdout, --pr, --dry-run, and -v are flags. No forwarded-argument boundary; the pre-parser preserves legacy help, missing-value, unknown-option, and equals-form behavior. |
Duplicate scalar values keep the last value; --print conflicts with --pr and with an explicit path as before. Generated template content, executable-file writes, and optional PR/worktree boundaries remain local. |
cli/bash/commands/basectl/tests/repo.bats installer-template cases |
Migrated in the #2144 leaf; preserve stdout/path safety and generated installer PR behavior. |
gh.sh (issue create) |
No positionals; --category, --repo, --title, --assignee, --body, --project, --project-owner, and --size take values; --no-assignee, --no-project, and --allow-cross-repo are flags. No forwarded-argument boundary; the pre-parser preserves legacy help and unknown-option errors before shared parsing. |
Missing values and unknown options retain the existing usage text and status 2. Duplicate values keep the last value; duplicate flags remain accepted. Category, size, repository inference, GitHub issue creation, and optional Project metadata updates remain local. | cli/bash/commands/basectl/tests/gh.bats issue-create cases |
Migrated in the #2144 leaf; preserve issue labels, assignee defaults, cross-repository Project guard, and GitHub delegation. |
setup_diagnostics_fallback.sh (record-check, project-venv JSON, check/doctor JSON) |
Internal fallback leaves own value options for project/status/timestamps/paths, project-venv fields, and normalized repeated check/result/embedded records. No public forwarded-argument boundary. | Missing or unsupported fallback arguments remain fatal. Duplicate scalar values keep the last value; repeated records preserve their per-kind order. JSON payload, record persistence, and aggregate status remain local to the fallback. | cli/bash/commands/basectl/tests/diagnostics-fallback.bats |
Migrated in the #2144 leaf; preserve Python-unavailable diagnostic rendering and record-write boundaries. |
gh.sh (auth status / auth refresh) |
auth status owns --hostname <host>. auth refresh owns --hostname <host>, repeatable --scope <scope>, comma-separated --scopes <scope,...>, and --clipboard; the pre-parser normalizes both scope spellings into the shared repeatable option. No forwarded-argument boundary. |
Help remains command-scoped. Missing values and unknown options return 2 with the existing usage/error text; duplicate hostname values keep the last value; scope order is preserved. Unsupported equals-form options remain rejected, matching the legacy contract. GitHub CLI output and delegated status remain unchanged. | cli/bash/commands/basectl/tests/gh.bats auth cases |
Migrated in the #2144 leaf; preserve hostname handling, scope ordering, and credential safety boundaries. |
gh_branch_worktree.sh (branch stale) |
No positionals; --days <days> and --format <text|json>. No forwarded-argument boundary; the pre-parser preserves the legacy space-separated value forms before shared parsing. |
Help is immediate. Duplicate values keep the last value. Invalid days and unsupported formats return 2; valid JSON format selection keeps usage and upstream failures inside the inspection envelope. Unsupported equals-form options remain rejected. | cli/bash/commands/basectl/tests/gh-branch-worktree.bats and inspection-json.bats |
Migrated in the #2144 leaf; preserve stale-branch age validation, JSON envelope/status, and Git reference inspection boundaries. |
gh_branch_worktree.sh (branch prune) |
No positionals; --dry-run, --yes, --remote, and --closed-unmerged. No forwarded-argument boundary. |
Help and unknown options remain immediate. Duplicate flags remain accepted. --dry-run and --yes remain mutually exclusive; dry-run is the default. Branch/worktree eligibility, GitHub verification, output, and deletion status remain local to the pruning layer. |
cli/bash/commands/basectl/tests/gh-branch-worktree.bats prune cases |
Migrated in the #2144 leaf; preserve default dry-run and all deletion safety boundaries. |
gh_branch_worktree.sh (worktree prune) |
No positionals; --dry-run, --yes, and --closed-unmerged. No forwarded-argument boundary; --remote remains rejected because remote cleanup belongs to branch prune. |
Help, unknown-option, and remote-cleanup errors remain immediate. Duplicate flags remain accepted; --dry-run and --yes remain mutually exclusive; dry-run is the default. Worktree eligibility, GitHub verification, output, and deletion status remain local to the pruning layer. |
cli/bash/commands/basectl/tests/gh-branch-worktree.bats worktree prune cases |
Migrated in the #2144 leaf; preserve default dry-run and local-only cleanup boundaries. |
prompt.sh |
Exactly one list or prompt-name positional; -v and --output <path>. The legacy parser accepts an option-looking output value; the pre-parser encodes it as --output=<value>. No forwarded-argument boundary. |
Help is prompt-sensitive and remains available before the Python environment is required. Unknown options and missing output paths return 2; rendered prompt content is delegated. | cli/bash/commands/basectl/tests/prompt.bats |
Migrated; preserve help precedence and option-looking output paths. |
docs.sh |
No positionals; --show-url. A standalone -- and all other options are rejected by the command-specific preflight. No forwarded-argument boundary. |
-h, --help, and help print help and return 0. Invalid arguments return 2. --show-url prints the URL; browser-open failure returns 1 with a remediation message. |
cli/bash/commands/basectl/tests/docs.bats |
Migrated; preserve preflight error text and browser fallback behavior. |
update_profile.sh |
No positionals; --defaults, --no-defaults, --remove, --dry-run, and -v. |
Help and unknown-option precedence is handled before shared parsing. Conflicting combinations return 2; debug uses the existing logging path; profile mutation remains local to this command. | cli/bash/commands/basectl/tests/update-profile.bats |
Migrated; preserve duplicate compatible flags and conflict validation. |
test.sh |
Optional project positional; -v, --workspace <path>, --project <name>, and --dry-run; arguments after -- are forwarded byte-for-byte to the declared test command. |
Help and owned-option errors return 2 without running the project command. Dry-run output is shell-quoted; delegated command status is preserved. | cli/bash/commands/basectl/tests/test.bats |
Migrated; preserve the explicit forwarding boundary and runner behavior. |
build.sh |
Project/target positionals; -v, --workspace <path>, --project <name>, --dry-run, --list, and --format <text|csv|tsv|yaml|json>; arguments after -- are forwarded to each declared build command. |
Help and owned-option errors return 2. List/dry-run/explicit-project combinations are validated locally; target and delegated-command output/status remain unchanged. | cli/bash/commands/basectl/tests/build.bats |
Migrated; preserve target interpretation and forwarding boundary. |
demo.sh |
Optional project positional; -v, --workspace <path>, --project <name>, and --dry-run; arguments after -- are forwarded to the declared demo script. |
Help and owned-option errors return 2. Positional/explicit-project conflicts are local usage errors; dry-run and delegated demo status/output remain unchanged. | cli/bash/commands/basectl/tests/demo.bats |
Migrated; preserve runner selection and forwarding boundary. |
run.sh |
Project/command positionals; -v, --workspace <path>, --project <name>, --dry-run, --list, and --format <text|csv|tsv|yaml|json>; arguments after -- are forwarded to the declared command. |
Help and owned-option errors return 2. List/dry-run/operand combinations are validated locally; dry-run and delegated command status/output remain unchanged. | cli/bash/commands/basectl/tests/run.bats |
Migrated; preserve command interpretation and forwarding boundary. |
clean.sh |
Shell owns -v, --older-than <age>, and --keep-last <count>, including equals forms. --dry-run, --yes, unknown options, positionals, and -- remain in the complete Python argv. |
Help is local. Missing values, missing cleanup criteria, and shared-parser failures return 2 with usage/error text. The Python cleanup layer owns remaining validation and output. | cli/bash/commands/basectl/tests/clean.bats |
Migrated with a filtered boundary; preserve the complete Python argv. |
logs.sh |
Shell owns -v, --command <names>, --limit <count>, --latest, --tail, --open, --lines <count>, and --format <format>, including equals forms. The command token last-failed, unknown options, and -- remain in the Python argv. |
Help is selected for the recent or last-failed scope before parsing. Missing values and shared-parser failures return 2; log listing, redaction, and delegated status/output remain Python-owned. |
cli/bash/commands/basectl/tests/logs.bats |
Migrated with a filtered boundary; preserve command-scoped help and the complete Python argv. |
projects.sh |
Bash owns only -v after the required list leaf. The list leaf's workspace, format, and all other arguments are forwarded to the Python layer, including --. |
Area/leaf help is local. Unknown project commands and shared-parser failures return 2 with usage/error text. The command uses the Base venv when present and a source-checkout Python fallback before reporting setup guidance. | cli/bash/commands/basectl/tests/projects.bats |
Migrated with a filtered boundary; preserve Python option ownership and source fallback. |
gh_issue_readiness.sh |
Exactly one numeric issue positional followed by --repo <owner/name>, --project-owner <owner>, --project-number <number>, and --format <text|json>. No forwarded-argument boundary. |
Help and format-aware usage errors are local. Invalid/missing values and incomplete Project selectors return 2; text/JSON output schemas and upstream GitHub failure handling remain unchanged. | cli/bash/commands/basectl/tests/gh.bats readiness cases |
Migrated in the #2144 leaf; preserve runtime-help and option-looking-value coverage. |
The current parser rows are evidence for future changes, not a request to
reopen all parser migrations. The nested parser leaves identified for
#2122/#2144 are now migrated. Remaining while loops are intentional
compatibility normalizers, pass-through scanners, or stateful top-level
dispatch/JSON processing; they must not be converted without a new behavior
baseline.
base_std_command_path <result-variable> <command> resolves an executable
through the current PATH using Bash's external-command lookup semantics. It
is quiet on an ordinary miss, does not exit the caller, and returns a path for
callers that need to execute the command. It does not replace a Bash-function
check; callers that intentionally support functions must retain an explicit
base_std_function_exists or equivalent contract.
The 21 direct production calls fall into these contracts:
| Logical caller and exact call sites | Resolution contract | Error/status/output boundary | Platform fallback and tests | Disposition |
|---|---|---|---|---|
docs.sh:34 (base_docs_platform_opener) |
Resolve the first supported opener from open, xdg-open, or wslview without logging a miss. |
If none is found, retain the local status-1 message directing users to --show-url; do not log a shared fatal error. |
Ordered platform opener selection remains local. cli/bash/commands/basectl/tests/docs.bats covers URL-only and opener behavior. |
Migrated in #2129; preserve selection order and fallback message. |
gh.sh:657 (base_gh_require_command) |
Resolve the requested GitHub/tool executable quietly before use. | Keep the local installation/authentication guidance and caller status; do not replace it with the aggregate assertion message. | No absolute fallback is introduced by this helper. GitHub command behavior is covered by cli/bash/commands/basectl/tests/gh.bats. |
Migrated in #2129; preserve custom prerequisite text. |
gh_branch_worktree.sh:225 |
Resolve gh for branch/worktree readiness. |
Preserve the local readiness/fallback result and status. | GitHub readiness remains separate from Git discovery and is covered by cli/bash/commands/basectl/tests/gh.bats. |
Migrated in #2129; preserve readiness behavior. |
project_command_helpers.sh:190 |
Resolve uv from PATH before invoking a project runner. |
A miss continues through the existing runner-resolution path; it is not a fatal shared-library error. | Preserve the project-local $HOME/.local/bin/uv fallback and manager-specific routing. Runner behavior is covered by build.bats, demo.bats, run.bats, and test.bats. |
Migrated in #2129; preserve local-runner fallback. |
projects.sh:61 |
Resolve python3 for the source-checkout pre-setup path. |
A missing or unusable interpreter falls through to the existing setup guidance; normal list output/status belongs to Python. | Preserve the Base venv first choice and source-checkout PYTHONPATH fallback. Covered by project-list tests. |
Migrated in #2129; preserve pre-setup fallback. |
repo.sh:2171 |
Resolve gh before repository GitHub operations. |
Keep the repository-specific prerequisite error and status. | No new platform fallback; repository GitHub behavior is covered by cli/bash/commands/basectl/tests/repo.bats. |
Migrated in #2129; preserve custom prerequisite text. |
repo_github_settings.sh:21 |
Resolve brew before checking the Homebrew-managed gh package. |
A miss returns the existing quiet “not managed by Homebrew” result to the caller. | Preserve Homebrew package checks and macOS-only behavior. Covered by repository/setup tests. | Migrated in #2129; preserve Homebrew ownership check. |
setup_common.sh:301 |
Resolve optional macOS osascript notification support. |
A miss is non-fatal and emits the existing warning only when notification was requested. | No Linux fallback is implied; notification remains optional. Covered by setup tests. | Migrated in #2129; preserve optional notification behavior. |
setup_common.sh:336,348 (setup_command_path, file prerequisite) |
Resolve generic setup prerequisites and file without logging on ordinary misses. |
The wrapper returns failure so each caller can retain its own error/status text. | No platform fallback is added by the generic wrapper. Covered by setup tests. | Migrated in #2129; preserve caller-owned errors. |
setup_common.sh:490 |
Resolve python3 for setup diagnostics/routing. |
Preserve the existing fallback and diagnostic status when Python is unavailable. | Platform-specific setup layers remain responsible for their own fallback/install paths. Covered by setup tests. | Migrated in #2129; preserve diagnostics behavior. |
setup_linux_debian.sh:69,98 |
Resolve python3 and arbitrary prerequisite names on Debian/Ubuntu. |
Ordinary misses remain quiet until the Debian setup layer emits its command-specific result. | Preserve distro-specific discovery and package-install routing; do not claim support for other Linux flavors. Covered by setup tests. | Migrated in #2129; preserve Debian routing. |
setup_linux_debian.sh:311 |
Resolve dpkg-query before package-state inspection. |
A missing tool returns the existing prerequisite failure. | Keep Debian package semantics and the existing architecture/package probes. Covered by setup tests. | Migrated in #2129; preserve package-state behavior. |
setup_linux_debian.sh:371,372 |
Resolve curl and dpkg before configuring the GitHub CLI apt repository. |
Retain the explicit fatal messages and statuses naming the missing command. | Keep the Ubuntu/Debian apt path and its dry-run/approval boundary. Covered by setup tests. | Migrated in #2129; preserve fatal prerequisite text. |
setup_macos_homebrew.sh:75 |
Resolve brew before Homebrew setup. |
Preserve the existing setup result and diagnostics. | Keep the test-only Homebrew prefix hook and real Homebrew prefix discovery. Covered by setup tests. | Migrated in #2129; preserve Homebrew fallback. |
setup_macos_homebrew.sh:212 |
Resolve curl before downloading a pinned Homebrew installer script. |
Missing curl returns the existing status 127 path; download and verification failures retain their local errors. |
Preserve explicit pinned URL/SHA requirements and macOS-only installer behavior. Covered by setup tests. | Migrated in #2129; preserve installer first-mile behavior. |
setup_macos_homebrew.sh:400 |
Resolve python3 only when the system-Python policy permits it. |
Preserve the existing setup fallback when system Python is unavailable or disallowed. | The policy gate remains separate from command lookup. Covered by setup tests. | Migrated in #2129; preserve policy gate. |
update.sh:227,359 |
Resolve python3 and brew for update routing. |
Keep update-specific diagnostics and delegated statuses. | Preserve source-checkout/Homebrew handoff and Homebrew trust checks. Covered by cli/bash/commands/basectl/tests/update.bats. |
Migrated in #2129; preserve update routing. |
The command-discovery migration is therefore complete for production
subcommands. Future changes must not migrate test-only probes merely to remove
the command -v spelling, and must not remove absolute or project-local
fallbacks that are part of a platform contract.
Before a future implementation PR changes a remaining nested parser or discovery boundary, it must include:
- The caller row updated with exact argv, status, output, logging, and forwarding semantics.
- Runtime
basectl <command> --helpoutput captured before and after the change, where the command is public. - Focused BATS coverage for help, invalid input, duplicates, value forms,
--, option-looking values, and delegated argv/status as applicable. - A check that
PATH, Bash-function support, and platform/project-local fallback behavior have not changed accidentally. - Full Base validation after the focused tests pass.
This issue is an inventory and sequencing gate. It does not authorize a parser API change, a behavior change, a test-fixture rewrite, or an installer fallback change by itself.