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
5 changes: 4 additions & 1 deletion .ai-context/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,10 @@ are documented in `docs/inspection-json.md`.
- `basectl update [project]` - update Base or a named project using the
configured Git checkout or Homebrew-managed Base handoff, then run setup for
the selected project.
- `basectl version` - show the installed Base version.
- `basectl version` - show the installed Base version without provider bootstrap.
`--all` reports selected Base/base-cli/base-bash-libs identity, paths, Git revision
and dirty state; `--all --json` emits a v1 inspection envelope. Detailed
inspection needs Python 3 but tolerates missing providers and the Base venv.
- `basectl help` - show command help.

## Command Implementation Pattern
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ numeric next development line is tracked in `DEVELOPMENT_VERSION`.

## [Unreleased]

- Added `basectl version --all` and `--all --json` to inspect active component
versions, provider paths, Git revisions, and dirty state even when providers
or the Base Python environment are unavailable.

- Added an optional repository-owned review policy for `basectl repo configure`.
Teams can request approving reviews and code-owner review without changing
the no-configuration behavior of existing solo repositories; stronger
Expand Down
78 changes: 6 additions & 72 deletions base_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,78 +146,6 @@ base_init_resolve_home() {
printf '%s\n' "$source_dir"
}

base_init_homebrew_prefix() {
case "$BASE_HOME" in
*/opt/base/libexec)
printf '%s\n' "${BASE_HOME%/opt/base/libexec}"
;;
*/Cellar/base/*/libexec)
printf '%s\n' "${BASE_HOME%%/Cellar/base/*}"
;;
esac
}

base_init_bash_libs_dir_is_usable() {
local candidate="${1:-}"

[[ -n "$candidate" ]] || return 1
[[ -f "$candidate/std/lib_std.sh" ]]
}

base_init_report_missing_bash_libs() {
local candidate
local homebrew_prefix

base_init_error "Base reusable Bash libraries were not found."

candidate="$BASE_HOME/../base-bash-libs/lib/bash"
base_init_error "Tried sibling base-bash-libs checkout at '$candidate'."

homebrew_prefix="$(base_init_homebrew_prefix || true)"
if [[ -n "$homebrew_prefix" ]]; then
candidate="$homebrew_prefix/opt/base-bash-libs/libexec/lib/bash"
base_init_error "Tried Homebrew base-bash-libs package at '$candidate'."
fi

base_init_error "Clone basefoundry/base-bash-libs next to Base, install it with 'brew install basefoundry/base/base-bash-libs', or set BASE_BASH_LIBS_DIR to a compatible lib/bash directory."
}

base_init_set_bash_libs_contract() {
local candidate
local homebrew_prefix
local explicit_dir="${BASE_BASH_LIBS_DIR:-}"

if [[ -n "$explicit_dir" ]]; then
base_init_bash_libs_dir_is_usable "$explicit_dir" || {
base_init_error "BASE_BASH_LIBS_DIR '$explicit_dir' does not contain std/lib_std.sh."
return 1
}
BASE_BASH_LIBS_DIR="$(cd -L -- "$explicit_dir" && pwd -L)" || return 1
BASE_BASH_LIBS_SOURCE=explicit
return $?
fi

candidate="$BASE_HOME/../base-bash-libs/lib/bash"
if base_init_bash_libs_dir_is_usable "$candidate"; then
BASE_BASH_LIBS_DIR="$(cd -L -- "$candidate" && pwd -L)" || return 1
BASE_BASH_LIBS_SOURCE=sibling
return $?
fi

homebrew_prefix="$(base_init_homebrew_prefix || true)"
if [[ -n "$homebrew_prefix" ]]; then
candidate="$homebrew_prefix/opt/base-bash-libs/libexec/lib/bash"
if base_init_bash_libs_dir_is_usable "$candidate"; then
BASE_BASH_LIBS_DIR="$(cd -L -- "$candidate" && pwd -L)" || return 1
BASE_BASH_LIBS_SOURCE=homebrew
return $?
fi
fi

base_init_report_missing_bash_libs
return 1
}

base_init_export_contract() {
local base_home base_os base_platform base_host base_host_env uname_os

Expand Down Expand Up @@ -360,7 +288,13 @@ import_base_lib() {
}

base_init_main() {
local init_path

base_init_require_bash || return 1
init_path="$(base_init_resolve_path "${BASH_SOURCE[0]}")" || return 1
# Load definitions from this bootstrap, even when diagnosing a stale BASE_HOME.
# shellcheck source=lib/base/base_bash_libs_runtime.sh
source "${init_path%/*}/lib/base/base_bash_libs_runtime.sh" || return 1
base_init_export_contract || return 1
base_init_source_stdlib "$@" || return 1
base_init_require_bash_libs_version || return 1
Expand Down
41 changes: 4 additions & 37 deletions bin/basectl
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,6 @@ basectl_print_version() {
printf 'basectl %s\n' "$(base_read_version "$base_home")"
}

basectl_version_usage() {
cat <<'EOF'
Usage:
basectl version

Purpose:
Show the installed Base version.

Options:
-h, --help Show this help text.
EOF
}

basectl_version_usage_error() {
printf 'ERROR: %s\n' "$*" >&2
printf "Run 'basectl version --help' for usage.\n" >&2
return 2
}

basectl_proc_translated() {
local translated

Expand Down Expand Up @@ -355,24 +336,10 @@ main() {

if [[ "${1:-}" == "version" ]]; then
shift
case "${1:-}" in
-h|--help|help)
(($# == 1)) || {
basectl_version_usage_error "version does not accept arguments."
return $?
}
basectl_version_usage
return 0
;;
"")
basectl_print_version "$base_home"
return 0
;;
*)
basectl_version_usage_error "version does not accept arguments."
return $?
;;
esac
# shellcheck source=cli/bash/commands/basectl/subcommands/version.sh
source "$base_home/cli/bash/commands/basectl/subcommands/version.sh"
basectl_version_main "$base_home" "$@"
return $?
fi

if [[ $# -eq 0 && -t 0 && -t 1 ]]; then
Expand Down
35 changes: 35 additions & 0 deletions cli/bash/commands/basectl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,38 @@ such command directories exist. Optional utility CLIs such as `caff` and
- `basectl version` prints the installed Base version from the repo-root `VERSION` file.
- basectl-specific bootstrap subcommands live under `cli/bash/commands/basectl/subcommands/`.
- basectl tests live under `cli/bash/commands/basectl/tests/`.

### Component versions

`basectl version` and `basectl --version` keep the concise, dependency-independent
Base identity. Use `basectl version --all` to inspect Base, base-cli and
base-bash-libs, or `basectl version --all --json` for automation.

The detailed report includes selected provider source, resolved location, version,
Git commit and dirty state (including untracked files), plus the Base Python path
selected by `base-wrapper`, respecting `BASE_SETUP_VENV_DIR`. It uses the same
explicit/sibling/installed provider precedence as runtime startup. Source versions
come from the selected checkout, never a shadowed wheel's metadata. Git identity
is only read at the component root, so an enclosing workspace repository cannot
be mistaken for the component. Packaged Bash releases can use embedded metadata.

These are observed component identities, not release-pinned expectations or a
compatibility verdict. The [release BOM](../../../../docs/release-bom.md) records
the tested release combination; a source override may intentionally differ.
Inspection locates provider files without importing or executing provider code.
Use `basectl check base` for readiness and compatibility checks.

Detailed inspection uses a stdlib-only Python 3 bootstrap diagnostic and remains
available when the Base venv or providers are missing or incompatible. Missing
providers appear as `unavailable`, unreadable identity as `unknown`, and unknown
JSON values as `null`. Partial reports exit 0 with envelope status `warn`; a report
is not a health gate. If no Python 3 interpreter is available, detailed inspection
fails with an actionable error; simple version output still works. No installation
or network access is performed. Paths in the report may contain local usernames.

JSON uses the inspection v1 envelope (`schema_version`, `command: "version"`,
`status`, `data`, `error`). `data.components` contains records with `name`, `source`,
`path`, `version`, `revision`, `dirty`, `status`, and `detail`. `data.python` contains
`path` and `exists` (executable availability, not a health verdict). Consumers must
allow additive fields. Invalid CLI arguments exit 2 and write usage errors to stderr.
The Bash identity reader is also used in the existing check/doctor provider message.
47 changes: 4 additions & 43 deletions cli/bash/commands/basectl/basectl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ BASECTL_REQUIRED_HOME_FILES=(
lib/bash/runtime/bashrc
lib/bash/runtime/command_protocol.sh
lib/bash/version/lib_version.sh
lib/base/base_bash_libs_runtime.sh
bin/basectl
bin/base-wrapper
cli/bash/commands/basectl/basectl.sh
cli/bash/commands/basectl/subcommands/version.sh
)
readonly BASECTL_REQUIRED_HOME_FILES

Expand Down Expand Up @@ -107,7 +109,7 @@ Diagnostics and maintenance:

Other:
version
Show the installed Base version.
Show the installed Base version; --all includes component providers.
help
Show this help text.

Expand Down Expand Up @@ -473,49 +475,8 @@ basectl_do_workspace() {
base_workspace_subcommand_main "$@"
}

basectl_source_version_library() {
local version_lib="$BASE_HOME/lib/bash/version/lib_version.sh"

[[ -f "$version_lib" ]] || {
basectl_error "Base version library '$version_lib' was not found."
return 1
}

# shellcheck source=/dev/null
source "$version_lib"
}

basectl_do_version() {
case "${1:-}" in
"")
;;
-h|--help|help)
(($# == 1)) || {
basectl_error "version does not accept arguments."
printf "Run 'basectl version --help' for usage.\n" >&2
return 2
}
cat <<'EOF'
Usage:
basectl version

Purpose:
Show the installed Base version.

Options:
-h, --help Show this help text.
EOF
return 0
;;
*)
basectl_error "version does not accept arguments."
printf "Run 'basectl version --help' for usage.\n" >&2
return 2
;;
esac

basectl_source_version_library || return 1
printf 'basectl %s\n' "$(base_read_version "$BASE_HOME")"
"$BASE_HOME/bin/basectl" version "$@"
}

basectl_should_start_shell() {
Expand Down
9 changes: 9 additions & 0 deletions cli/bash/commands/basectl/subcommands/setup_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,15 @@ setup_base_bash_libraries_status() {
}

setup_base_bash_libraries_check_message() {
local python_bin summary

python_bin="$(command -v python3 || true)"
# Same stdlib-only identity reader as version --all; retain source guidance
# below when Python is unavailable during early setup diagnostics.
if [[ -n "$python_bin" ]] && summary="$("$python_bin" -I -S "$BASE_HOME/cli/python/base_version/report.py" \
--bash-summary "${BASE_BASH_LIBS_SOURCE:-unknown}" "${BASE_BASH_LIBS_DIR:-}" 2>/dev/null)"; then
printf '%s; ' "$summary"
fi
case "${BASE_BASH_LIBS_SOURCE:-unknown}" in
explicit)
printf "Base is using reusable Bash libraries from explicit BASE_BASH_LIBS_DIR '%s'.\n" "${BASE_BASH_LIBS_DIR:-unknown}"
Expand Down
92 changes: 92 additions & 0 deletions cli/bash/commands/basectl/subcommands/version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# shellcheck shell=bash
# Version inspection deliberately runs before provider-dependent bootstrap.

basectl_version_usage() {
cat <<'USAGE'
Usage:
basectl version [--all [--json]]

Purpose:
Show the installed Base version. Use --all to inspect selected component providers.

Options:
--all Show Base, base-cli, and base-bash-libs identity and source paths.
--json Emit a versioned JSON report (requires --all).
-h, --help Show this help text.

Detailed inspection requires Python 3, but does not require working providers.
Unavailable providers are reported without suppressing other components.
USAGE
}

basectl_version_usage_error() {
printf 'ERROR: %s\n' "$*" >&2
printf "Run 'basectl version --help' for usage.\n" >&2
return 2
}

basectl_version_all() (
# Subshell: runtime selection must not mutate a caller's loaded contract.
local base_home="$1" output_format="$2"
local cli_root cli_kind cli_error="" bash_error="" python_bin selected_python
local base_version candidate
BASE_HOME="$base_home"
# shellcheck source=lib/base/base_cli_runtime.sh
source "$base_home/lib/base/base_cli_runtime.sh" || return 1
# shellcheck source=lib/base/base_bash_libs_runtime.sh
source "$base_home/lib/base/base_bash_libs_runtime.sh" || return 1
base_init_error() { printf 'ERROR: %s\n' "$*" >&2; }

cli_root="$(base_cli_runtime_source_root 2>&1)" || { cli_error="$cli_root"; cli_root=""; }
cli_kind="$(base_cli_runtime_source_kind)" || cli_kind=unavailable
# Capture errors without a temporary file; resolve in this subshell after success.
bash_error="$(base_init_set_bash_libs_contract 2>&1)"
if [[ -z "$bash_error" ]]; then
base_init_set_bash_libs_contract || return 1
fi
selected_python="${BASE_SETUP_VENV_DIR:-$HOME/.base.d/base/.venv}/bin/python"
python_bin=""
for candidate in "$selected_python" "$(command -v python3 || true)" /usr/bin/python3; do
if [[ -x "$candidate" ]] && "$candidate" -I -S -c 'import importlib.metadata' >/dev/null 2>&1; then
python_bin="$candidate"
break
fi
done
[[ -n "$python_bin" ]] || {
printf 'ERROR: Detailed version inspection requires Python 3. Use basectl version for Base alone.\n' >&2
return 1
}
base_version="$(base_read_version "$base_home")"
# Bootstrap diagnostic exception: stdlib-only reporter must survive missing
# venvs and incompatible providers, so it cannot use base-wrapper.
"$python_bin" -I -S "$base_home/cli/python/base_version/report.py" \
"$base_home" "$base_version" "$output_format" "$selected_python" \
"$cli_kind" "$cli_root" "$cli_error" \
"${BASE_BASH_LIBS_SOURCE:-unavailable}" "${BASE_BASH_LIBS_DIR:-}" "$bash_error"
)

basectl_version_main() {
local base_home="$1" all=0 output_format=text arg
shift
if [[ "$#" -eq 1 && ( "$1" == -h || "$1" == --help || "$1" == help ) ]]; then
basectl_version_usage
return 0
fi
for arg in "$@"; do
case "$arg" in
--all) all=1 ;;
--json) output_format=json ;;
*) basectl_version_usage_error "Unknown version argument '$arg'."; return $? ;;
esac
done
if [[ "$all" -eq 0 ]]; then
[[ "$output_format" == text ]] || {
basectl_version_usage_error '--json requires --all.'
return $?
}
basectl_print_version "$base_home"
return $?
fi
basectl_source_version_library "$base_home"
basectl_version_all "$base_home" "$output_format"
}
2 changes: 1 addition & 1 deletion cli/bash/commands/basectl/tests/runtime-dispatch.bats
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ EOF
run_basectl version nonsense

[ "$status" -eq 2 ]
[ "${lines[0]}" = "ERROR: version does not accept arguments." ]
[ "${lines[0]}" = "ERROR: Unknown version argument 'nonsense'." ]
[ "${lines[1]}" = "Run 'basectl version --help' for usage." ]
[[ "$output" != *"basectl $(head -n 1 "$BASE_REPO_ROOT/VERSION")"* ]]
}
Expand Down
Loading
Loading