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
9 changes: 7 additions & 2 deletions .ai-context/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ the v1.x compatibility window; new automation should use `--debug-wrapper`.
- `basectl docs` - open the Base documentation home page on GitHub.
- `basectl projects list` - list Base-managed projects discovered in the
workspace.
- `basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup>` -
- `basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test>` -
inspect workspace status, checks, diagnostics, read-only first-day
onboarding, and local agent-handoff readiness; explicitly clone expected
repositories from a manifest; initialize a workspace from a workspace
configuration repo; explicitly sync a local manifest from a configured
canonical source; apply repo configuration across a workspace; or run local
project setup across eligible repositories.
project setup and declared tests across eligible repositories.
- `workspace status`, `workspace check`, `workspace doctor`,
`workspace onboarding`, and `workspace agent-brief` support `--format json`;
`workspace update` also supports stable `--format json` output documented in
Expand Down Expand Up @@ -101,6 +101,11 @@ the v1.x compatibility window; new automation should use `--debug-wrapper`.
without `--dry-run`, it skips ineligible repositories, continues after
per-repo failures, and reports setup/skipped/failed counts. `--yes` forwards
confirmation to each delegated setup command.
- `workspace test` runs declared project test commands serially in manifest
order. `--projects name[,name...]` selects a subset, `--fail-fast` stops
after the first failure, and `--format json` emits per-project results and
passed/failed/skipped counts. Projects without a declared test command are
skipped.
- `workspace update --dry-run` previews the ordered Git pull plan; without
`--dry-run`, it continues after per-repo failures and reports
updated/unchanged/skipped/failed counts. JSON dry runs report `planned`
Expand Down
4 changes: 2 additions & 2 deletions cli/bash/commands/basectl/basectl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ Compatibility:
Deprecated alias for the corresponding lifecycle command with --ci.

Workspace and repositories:
workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup> [options]
Show workspace status, onboarding, agent readiness, checks, or explicit workspace mutations.
workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test> [options]
Show workspace status, onboarding, agent readiness, checks, declared project tests, or explicit workspace mutations.
repo <init|clone|check|configure|agent-guidance|installer-template> [options]
Create, clone, check, and configure repository baselines and guidance.
gh <area> <command> [options]
Expand Down
30 changes: 28 additions & 2 deletions cli/bash/commands/basectl/subcommands/workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,28 @@ Set up eligible repositories in a workspace manifest in manifest order.
EOF
}

base_workspace_test_usage() {
cat <<'EOF'
Usage:
basectl workspace test [options]

Options:
--workspace <path> Workspace directory to test. Defaults to workspace.root, then BASE_HOME's parent.
--manifest <path> Local workspace manifest describing expected repositories.
Overrides workspace.manifest from ~/.base.d/config.yaml.
--projects <name[,name...]>
Test only the selected workspace projects, in manifest order.
--fail-fast Stop after the first project failure.
--format <text|json>
Output text or structured JSON. Defaults to text.
-v Enable DEBUG logging for this subcommand.
-h, --help Show this help text.

Run each selected project's declared test command serially.
Projects without a declared test command are skipped.
EOF
}

base_workspace_subcommand_usage() {
case "${1:-}" in
status|check|doctor)
Expand Down Expand Up @@ -204,10 +226,13 @@ base_workspace_subcommand_usage() {
setup)
base_workspace_setup_usage
;;
test)
base_workspace_test_usage
;;
*)
cat <<'EOF'
Usage:
basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup> [options]
basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test> [options]

Commands:
status Show workspace status. Supports --format text|csv|tsv|yaml|json.
Expand All @@ -221,6 +246,7 @@ Commands:
init Initialize a workspace from a workspace configuration repository.
configure Apply repo configure across workspace repositories.
setup Set up eligible workspace repositories in manifest order.
test Run declared project tests serially across selected repositories.

Run `basectl workspace <command> --help` for command-specific options.
EOF
Expand All @@ -244,7 +270,7 @@ base_workspace_subcommand_main() {
base_workspace_subcommand_usage
return 0
;;
status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup)
status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test)
shift
;;
*)
Expand Down
7 changes: 6 additions & 1 deletion cli/bash/commands/basectl/tests/completions.bats
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ EOF
COMP_CWORD=3; \
_base_basectl_completion; \
printf "workspace_setup_options=%s\n" "${COMPREPLY[*]}"; \
COMP_WORDS=(basectl workspace test --); \
COMP_CWORD=3; \
_base_basectl_completion; \
printf "workspace_test_options=%s\n" "${COMPREPLY[*]}"; \
COMP_WORDS=(basectl onboard --); \
COMP_CWORD=2; \
_base_basectl_completion; \
Expand Down Expand Up @@ -311,6 +315,7 @@ EOF
[[ "$output" == *"workspace_init_options=--owner --path --workspace --manifest --include-optional --dry-run"* ]]
[[ "$output" == *"workspace_configure_options=--workspace --manifest --dry-run"* ]]
[[ "$output" == *"workspace_setup_options=--workspace --manifest --dry-run --yes"* ]]
[[ "$output" == *"workspace_test_options=--workspace --manifest --projects --fail-fast --format"* ]]
[[ "$output" == *"onboard_options=--profile --dry-run --yes --allow-project-ide-mutations --no-profile"* ]]
[[ "$output" == *"onboard_projects=base demo"* ]]
[[ "$output" == *"onboard_profiles=dev sre ai linux-lab dev,sre dev,ai dev,linux-lab sre,ai sre,linux-lab ai,linux-lab dev,sre,ai dev,sre,linux-lab dev,ai,linux-lab sre,ai,linux-lab dev,sre,ai,linux-lab"* ]]
Expand Down Expand Up @@ -451,6 +456,6 @@ EOF
printf "options=%s\n" "${COMPREPLY[*]}"'

[ "$status" -eq 0 ]
[[ "$output" == *"commands=status check doctor onboarding agent-brief clone pull update init configure setup"* ]]
[[ "$output" == *"commands=status check doctor onboarding agent-brief clone pull update init configure setup test"* ]]
[[ "$output" == *"options=--workspace --manifest --dry-run --apply --yes"* ]]
}
8 changes: 4 additions & 4 deletions cli/bash/commands/basectl/tests/help.bats
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ load ./basectl_helpers.bash
[[ "$output" == *"demo [project] [options]"* ]]
[[ "$output" == *"update [project] [options]"* ]]
[[ "$output" == *"projects list [options]"* ]]
[[ "$output" == *"workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup> [options]"* ]]
[[ "$output" == *"workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test> [options]"* ]]
[[ "$output" == *"Invoking \`basectl\` with no command starts a Base runtime shell"* ]]
[[ "$output" == *"--version"* ]]
[[ "$output" == *"Wrapper options:"* ]]
Expand Down Expand Up @@ -98,7 +98,7 @@ load ./basectl_helpers.bash
grep -Fqx ' docs [options]' <<<"$output"
grep -Fqx ' logs [options]' <<<"$output"
grep -Fqx ' history [options]' <<<"$output"
grep -Fqx ' workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup> [options]' <<<"$output"
grep -Fqx ' workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test> [options]' <<<"$output"
grep -Fqx ' trust <status|allow|revoke> [project] [options]' <<<"$output"
[[ "$output" != *"-b DIR"* ]]
[[ "$output" != *"Force install"* ]]
Expand All @@ -117,7 +117,7 @@ load ./basectl_helpers.bash

[ "$status" -eq 0 ]
[[ "$output" == *"Usage:"* ]]
[[ "$output" == *"basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup> [options]"* ]]
[[ "$output" == *"basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test> [options]"* ]]
[[ "$output" != *"Usage: basectl [options] <command> [args...]"* ]]

run_basectl help release
Expand Down Expand Up @@ -250,7 +250,7 @@ load ./basectl_helpers.bash
@test "AI command context includes current clone and update surfaces" {
local commands_file="$BASE_REPO_ROOT/.ai-context/COMMANDS.md"

grep -Fqx -- "- \`basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup>\` -" "$commands_file"
grep -Fqx -- "- \`basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test>\` -" "$commands_file"
grep -Fqx -- " - \`workspace clone\` mutates repository checkouts only when invoked directly;" "$commands_file"
grep -Fqx -- "- \`basectl repo <init|clone|check|configure|agent-guidance|installer-template>\` -" "$commands_file"
grep -Fqx -- "- \`basectl update [project]\` - update Base or a named project using the" "$commands_file"
Expand Down
13 changes: 12 additions & 1 deletion cli/bash/commands/basectl/tests/workspace.bats
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,17 @@ EOF
[[ "$output" == *"ordered workspace setup plan"* ]]
[[ "$output" != *"--format"* ]]

run_basectl workspace test --help

[ "$status" -eq 0 ]
[[ "$output" == *"basectl workspace test [options]"* ]]
[[ "$output" == *"--workspace <path>"* ]]
[[ "$output" == *"--manifest <path>"* ]]
[[ "$output" == *"--projects <name[,name...]>"* ]]
[[ "$output" == *"--fail-fast"* ]]
[[ "$output" == *"--format <text|json>"* ]]
[[ "$output" == *"declared test command"* ]]

run_basectl workspace init --help

[ "$status" -eq 0 ]
Expand All @@ -490,7 +501,7 @@ EOF
run_basectl workspace help

[ "$status" -eq 0 ]
[[ "$output" == *"basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup> [options]"* ]]
[[ "$output" == *"basectl workspace <status|check|doctor|onboarding|agent-brief|clone|pull|update|init|configure|setup|test> [options]"* ]]
[[ "$output" != *"Project virtual environment Python was not found"* ]]
}

Expand Down
16 changes: 16 additions & 0 deletions cli/python/base_projects/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from base_projects.workspace_init import workspace_init_command
from base_projects.workspace_pull_command import workspace_pull_command
from base_projects.workspace_setup import workspace_setup_from_options
from base_projects.workspace_test import workspace_test_from_options
from base_projects.workspace_update import workspace_update_from_options
from base_projects.workspace_onboarding import workspace_onboarding_summary
from base_projects.workspace_report_json import workspace_check_to_json
Expand Down Expand Up @@ -122,6 +123,16 @@ def main(argv: list[str] | None = None) -> int:
)
@base_cli.option("--apply", is_flag=True, help="Apply workspace configure changes after the plan is shown.")
@base_cli.option("--yes", is_flag=True, help="Approve workspace setup or configure changes that require confirmation.")
@base_cli.option(
"--projects",
"workspace_projects",
help="Comma-separated workspace project names to test.",
)
@base_cli.option(
"--fail-fast",
is_flag=True,
help="Stop workspace testing after the first project failure.",
)
@base_cli.option(
"--test-preflight",
is_flag=True,
Expand All @@ -143,6 +154,8 @@ def run(
dry_run: bool,
apply: bool,
yes: bool,
workspace_projects: str | None,
fail_fast: bool,
test_preflight: bool,
) -> int:
try:
Expand All @@ -162,6 +175,8 @@ def run(
dry_run=dry_run,
apply=apply,
yes=yes,
workspace_projects=workspace_projects,
fail_fast=fail_fast,
test_preflight=test_preflight,
),
project_command_actions(),
Expand All @@ -184,6 +199,7 @@ def project_command_actions() -> ProjectCommandActions:
workspace_init=workspace_init_project_command,
workspace_configure=workspace_configure_from_options,
workspace_setup=workspace_setup_from_options,
workspace_test=workspace_test_from_options,
workspace_update=workspace_update_from_options,
current_project=current_project_command,
manifest_project=manifest_project_command,
Expand Down
15 changes: 15 additions & 0 deletions cli/python/base_projects/project_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ class WorkspaceCommandOptions:
project_name: str | None = None
workspace_manifest: str | None = None
workspace_repos: str | None = None
workspace_projects: str | None = None
workspace_manifest_source: str | None = None
workspace_config_path: str | None = None
workspace_owner: str | None = None
include_optional: bool = False
dry_run: bool = False
apply: bool = False
yes: bool = False
fail_fast: bool = False
test_preflight: bool = False


Expand All @@ -38,6 +40,7 @@ class ProjectCommandActions:
workspace_init: Callable[[base_cli.Context, str, WorkspaceCommandOptions], int]
workspace_configure: Callable[[base_cli.Context, WorkspaceCommandOptions], int]
workspace_setup: Callable[[base_cli.Context, WorkspaceCommandOptions], int]
workspace_test: Callable[[base_cli.Context, WorkspaceCommandOptions], int]
current_project: Callable[[base_cli.Context, str], int]
manifest_project: Callable[[base_cli.Context, str | None, str], int]
resolve_project: Callable[[base_cli.Context, str | None, str | None, str], int]
Expand Down Expand Up @@ -217,6 +220,16 @@ def _handle_setup(
return actions.workspace_setup(ctx, options)


def _handle_test(
ctx: base_cli.Context,
arguments: tuple[str, ...],
options: WorkspaceCommandOptions,
actions: ProjectCommandActions,
) -> int:
require_argument_count("test", arguments, 0, 0)
return actions.workspace_test(ctx, options)


def _handle_current(
ctx: base_cli.Context,
arguments: tuple[str, ...],
Expand Down Expand Up @@ -339,6 +352,7 @@ def _handle_build_target_list(
"clone",
"configure",
"setup",
"test",
"init",
"test-command",
"demo-script",
Expand All @@ -364,6 +378,7 @@ def _handle_build_target_list(
"init": _handle_init,
"configure": _handle_configure,
"setup": _handle_setup,
"test": _handle_test,
"current": _handle_current,
"manifest": _handle_manifest,
"resolve": _handle_resolve,
Expand Down
2 changes: 1 addition & 1 deletion cli/python/base_projects/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def invoke_engine(
user_config: str | None = None,
extra_env: dict[str, str] | None = None,
) -> tuple[int, str, str]:
workspace_commands = {"status", "check", "doctor", "onboarding", "agent-brief", "run-commands"}
workspace_commands = {"status", "check", "doctor", "onboarding", "agent-brief", "run-commands", "test"}
stdout = TerminalStringIO() if any(argument in workspace_commands for argument in args) else io.StringIO()
stderr = io.StringIO()
if user_config is not None:
Expand Down
Loading
Loading