devstuff 1.18.0__tar.gz → 2.0.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.
- {devstuff-1.18.0 → devstuff-2.0.0}/.claude/skills/run-stuff/SKILL.md +13 -13
- devstuff-2.0.0/.claude/skills/run-stuff/smoke.sh +55 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/.github/workflows/test-installs.yml +8 -8
- {devstuff-1.18.0 → devstuff-2.0.0}/CHANGELOG.md +19 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/CLAUDE.md +186 -20
- {devstuff-1.18.0 → devstuff-2.0.0}/PKG-INFO +378 -27
- {devstuff-1.18.0 → devstuff-2.0.0}/README.md +377 -26
- {devstuff-1.18.0 → devstuff-2.0.0}/dev/Dockerfile +8 -6
- {devstuff-1.18.0 → devstuff-2.0.0}/dev/Dockerfile.ci +5 -5
- {devstuff-1.18.0 → devstuff-2.0.0}/dev/Makefile +3 -3
- devstuff-2.0.0/dev/docker-compose.yml +14 -0
- devstuff-1.18.0/dev-setup → devstuff-2.0.0/devstuff +4 -4
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/README.md +5 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/agent/development-plan.md +2 -2
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/agent/specifications.md +5 -5
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/agent/stack-decisions.md +3 -3
- devstuff-2.0.0/docs/specs/ansible-config/development-plan.md +84 -0
- devstuff-2.0.0/docs/specs/ansible-config/specifications.md +183 -0
- devstuff-2.0.0/docs/specs/ansible-config/stack-decisions.md +139 -0
- devstuff-2.0.0/docs/specs/bat-config/development-plan.md +86 -0
- devstuff-2.0.0/docs/specs/bat-config/specifications.md +179 -0
- devstuff-2.0.0/docs/specs/bat-config/stack-decisions.md +141 -0
- devstuff-2.0.0/docs/specs/docker-config/development-plan.md +92 -0
- devstuff-2.0.0/docs/specs/docker-config/specifications.md +198 -0
- devstuff-2.0.0/docs/specs/docker-config/stack-decisions.md +146 -0
- devstuff-2.0.0/docs/specs/lazygit-config/development-plan.md +82 -0
- devstuff-2.0.0/docs/specs/lazygit-config/specifications.md +165 -0
- devstuff-2.0.0/docs/specs/lazygit-config/stack-decisions.md +130 -0
- devstuff-2.0.0/docs/specs/precommit-config/development-plan.md +84 -0
- devstuff-2.0.0/docs/specs/precommit-config/specifications.md +198 -0
- devstuff-2.0.0/docs/specs/precommit-config/stack-decisions.md +173 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/install.sh +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/pyproject.toml +1 -2
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/session.py +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/transcript.py +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent_tools.schema.json +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent_tools.yaml +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/catalog.py +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/cli.py +2 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/catalog_cmd.py +1 -1
- devstuff-2.0.0/src/dev_setup/commands/doctor_cmd.py +93 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/help_cmd.py +3 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/run_cmd.py +2 -2
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/__init__.py +31 -0
- devstuff-2.0.0/src/dev_setup/configure/ansible/__init__.py +7 -0
- devstuff-2.0.0/src/dev_setup/configure/ansible/detect.py +292 -0
- devstuff-2.0.0/src/dev_setup/configure/ansible/model.py +646 -0
- devstuff-2.0.0/src/dev_setup/configure/ansible/render.py +201 -0
- devstuff-2.0.0/src/dev_setup/configure/ansible/validate.py +313 -0
- devstuff-2.0.0/src/dev_setup/configure/ansible/wizard.py +467 -0
- devstuff-2.0.0/src/dev_setup/configure/bat/__init__.py +6 -0
- devstuff-2.0.0/src/dev_setup/configure/bat/detect.py +237 -0
- devstuff-2.0.0/src/dev_setup/configure/bat/model.py +406 -0
- devstuff-2.0.0/src/dev_setup/configure/bat/preview.py +229 -0
- devstuff-2.0.0/src/dev_setup/configure/bat/render.py +197 -0
- devstuff-2.0.0/src/dev_setup/configure/bat/wizard.py +436 -0
- devstuff-2.0.0/src/dev_setup/configure/docker/__init__.py +6 -0
- devstuff-2.0.0/src/dev_setup/configure/docker/detect.py +352 -0
- devstuff-2.0.0/src/dev_setup/configure/docker/model.py +627 -0
- devstuff-2.0.0/src/dev_setup/configure/docker/render.py +202 -0
- devstuff-2.0.0/src/dev_setup/configure/docker/validate.py +404 -0
- devstuff-2.0.0/src/dev_setup/configure/docker/wizard.py +535 -0
- devstuff-2.0.0/src/dev_setup/configure/lazygit/__init__.py +7 -0
- devstuff-2.0.0/src/dev_setup/configure/lazygit/detect.py +282 -0
- devstuff-2.0.0/src/dev_setup/configure/lazygit/model.py +546 -0
- devstuff-2.0.0/src/dev_setup/configure/lazygit/render.py +242 -0
- devstuff-2.0.0/src/dev_setup/configure/lazygit/validate.py +230 -0
- devstuff-2.0.0/src/dev_setup/configure/lazygit/wizard.py +422 -0
- devstuff-2.0.0/src/dev_setup/configure/precommit/__init__.py +5 -0
- devstuff-2.0.0/src/dev_setup/configure/precommit/detect.py +292 -0
- devstuff-2.0.0/src/dev_setup/configure/precommit/model.py +845 -0
- devstuff-2.0.0/src/dev_setup/configure/precommit/render.py +296 -0
- devstuff-2.0.0/src/dev_setup/configure/precommit/validate.py +289 -0
- devstuff-2.0.0/src/dev_setup/configure/precommit/wizard.py +506 -0
- devstuff-2.0.0/src/dev_setup/doctor.py +447 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/function_runner.py +5 -5
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/functions.schema.json +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/generic.py +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/integration/test_tools.py +1 -1
- devstuff-2.0.0/tests/test_configure_ansible.py +483 -0
- devstuff-2.0.0/tests/test_configure_bat.py +457 -0
- devstuff-2.0.0/tests/test_configure_docker.py +544 -0
- devstuff-2.0.0/tests/test_configure_lazygit.py +420 -0
- devstuff-2.0.0/tests/test_configure_precommit.py +449 -0
- devstuff-2.0.0/tests/test_doctor.py +285 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_functions.py +1 -1
- {devstuff-1.18.0 → devstuff-2.0.0}/uv.lock +1 -1
- devstuff-1.18.0/.claude/skills/run-stuff/smoke.sh +0 -55
- devstuff-1.18.0/dev/docker-compose.yml +0 -14
- {devstuff-1.18.0 → devstuff-2.0.0}/.github/workflows/bump.yml +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/.github/workflows/ci.yml +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/.github/workflows/publish.yml +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/.gitignore +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/commitizen-config/development-plan.md +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/commitizen-config/specifications.md +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/commitizen-config/stack-decisions.md +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/starship-config/development-plan.md +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/starship-config/specifications.md +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/docs/specs/starship-config/stack-decisions.md +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/__main__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/approval.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/bridges.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/catalog.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/completion.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/config.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/loop.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/ollama.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/preflight.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/primitives.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/registry.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/sandbox.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/agent/wizard.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/base.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/add_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/agent_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/configure_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/delete_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/docs_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/functions_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/install_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/list_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/remove_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/skills_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/commands/update_cmd.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/commitizen/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/commitizen/detect.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/commitizen/model.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/commitizen/render.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/commitizen/validate.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/commitizen/wizard.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/starship/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/starship/fonts.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/starship/model.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/starship/preview.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/starship/render.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/configure/starship/wizard.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/functions.yaml +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/functions_catalog.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/functions_registry.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/registry.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/tools.yaml +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/src/dev_setup/ui.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/integration/__init__.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/integration/conftest.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/integration/test_agent_smoke.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/integration/test_agent_tui.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/integration/test_commitizen_config.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_agent.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_agent_completion.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_agent_loop.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_agent_sandbox.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_agent_tools.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_agent_wizard.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_catalog.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_configure_commitizen.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_configure_starship.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_generic.py +0 -0
- {devstuff-1.18.0 → devstuff-2.0.0}/tests/test_registry.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: run-stuff
|
|
3
|
-
description: Run, build, test, and smoke-test the
|
|
3
|
+
description: Run, build, test, and smoke-test the devstuff CLI; exercise list/catalog/install commands
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`
|
|
6
|
+
`devstuff` is a Python CLI that manages a Linux dev environment. It is driven directly via `uv run devstuff <command>` — no GUI, no server, no browser. The smoke script at `.claude/skills/run-stuff/smoke.sh` is the primary agent harness.
|
|
7
7
|
|
|
8
8
|
## Prerequisites
|
|
9
9
|
|
|
@@ -29,14 +29,14 @@ It exercises `version`, `list` (all variants), `catalog path`, `catalog export`,
|
|
|
29
29
|
To exercise a specific command directly:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
uv run
|
|
33
|
-
uv run
|
|
34
|
-
uv run
|
|
35
|
-
uv run
|
|
36
|
-
uv run
|
|
37
|
-
uv run
|
|
38
|
-
uv run
|
|
39
|
-
uv run
|
|
32
|
+
uv run devstuff version
|
|
33
|
+
uv run devstuff list
|
|
34
|
+
uv run devstuff list core
|
|
35
|
+
uv run devstuff list --installed
|
|
36
|
+
uv run devstuff list --available
|
|
37
|
+
uv run devstuff catalog path
|
|
38
|
+
uv run devstuff catalog export /tmp/export.yaml
|
|
39
|
+
uv run devstuff --help
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Interactive commands (`install`, `remove`, `add`, `delete`) require a TTY and are not smoke-testable without mocking. To test them, run the app directly in a terminal.
|
|
@@ -44,8 +44,8 @@ Interactive commands (`install`, `remove`, `add`, `delete`) require a TTY and ar
|
|
|
44
44
|
## Run (human path)
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
./
|
|
48
|
-
./
|
|
47
|
+
./devstuff list # bash wrapper — bootstraps .venv on first run
|
|
48
|
+
./devstuff install # interactive multi-select picker
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
## Test suite
|
|
@@ -59,7 +59,7 @@ All 6 unit tests pass in ~0.3s.
|
|
|
59
59
|
|
|
60
60
|
## Gotchas
|
|
61
61
|
|
|
62
|
-
- `
|
|
62
|
+
- `devstuff` is not on `$PATH` directly in the dev environment — `uv run devstuff` is the reliable invocation path. The `./devstuff` bash wrapper also works from repo root and bootstraps its own `.venv`.
|
|
63
63
|
- `pip` is not available in this environment; `uv pip install` is the substitute.
|
|
64
64
|
- Interactive commands (`install`, `add`) open questionary/click prompts that hang in non-TTY shells. Pipe input only if you know the exact prompt sequence.
|
|
65
65
|
- `catalog export` writes to a path argument; without an argument it prints to stdout. Check the README for the exact signature.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Smoke test for devstuff CLI. Run from repo root.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
|
|
8
|
+
check() {
|
|
9
|
+
local label="$1"; shift
|
|
10
|
+
if output=$("$@" 2>&1); then
|
|
11
|
+
echo " ✔ $label"
|
|
12
|
+
PASS=$((PASS+1))
|
|
13
|
+
else
|
|
14
|
+
echo " ✘ $label"
|
|
15
|
+
echo " Output: $output"
|
|
16
|
+
FAIL=$((FAIL+1))
|
|
17
|
+
fi
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
check_output() {
|
|
21
|
+
local label="$1"; local expected="$2"; shift 2
|
|
22
|
+
output=$("$@" 2>&1)
|
|
23
|
+
if echo "$output" | grep -q "$expected"; then
|
|
24
|
+
echo " ✔ $label"
|
|
25
|
+
PASS=$((PASS+1))
|
|
26
|
+
else
|
|
27
|
+
echo " ✘ $label (expected '$expected' in output)"
|
|
28
|
+
echo " Got: $output"
|
|
29
|
+
FAIL=$((FAIL+1))
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
echo ""
|
|
34
|
+
echo "=== devstuff smoke tests ==="
|
|
35
|
+
echo ""
|
|
36
|
+
|
|
37
|
+
check "version" uv run devstuff version
|
|
38
|
+
check_output "version number" "1\." uv run devstuff version
|
|
39
|
+
check "list (all)" uv run devstuff list
|
|
40
|
+
check "list core" uv run devstuff list core
|
|
41
|
+
check "list tools" uv run devstuff list tools
|
|
42
|
+
check "list --installed" uv run devstuff list --installed
|
|
43
|
+
check "list --available" uv run devstuff list --available
|
|
44
|
+
check "catalog path" uv run devstuff catalog path
|
|
45
|
+
check_output "catalog path output" ".config/devstuff" uv run devstuff catalog path
|
|
46
|
+
check "catalog export" uv run devstuff catalog export /tmp/devstuff-smoke-export.yaml
|
|
47
|
+
check_output "help flag" "Commands:" uv run devstuff --help
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
51
|
+
echo "All $PASS checks passed."
|
|
52
|
+
else
|
|
53
|
+
echo "$FAIL/$((PASS+FAIL)) checks failed."
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
@@ -24,22 +24,22 @@ jobs:
|
|
|
24
24
|
run: uv build
|
|
25
25
|
|
|
26
26
|
# Two-step docker build matching the local dev workflow:
|
|
27
|
-
# dev/Dockerfile →
|
|
28
|
-
# dev/Dockerfile.ci →
|
|
27
|
+
# dev/Dockerfile → devstuff-sandbox (minimal system + wheel installed)
|
|
28
|
+
# dev/Dockerfile.ci → devstuff-ci (+ pytest + test files baked in)
|
|
29
29
|
- name: Build sandbox image
|
|
30
|
-
run: docker build -f dev/Dockerfile -t
|
|
30
|
+
run: docker build -f dev/Dockerfile -t devstuff-sandbox .
|
|
31
31
|
|
|
32
32
|
- name: Build CI test image
|
|
33
|
-
run: docker build -f dev/Dockerfile.ci -t
|
|
33
|
+
run: docker build -f dev/Dockerfile.ci -t devstuff-ci .
|
|
34
34
|
|
|
35
35
|
- name: Export image archive
|
|
36
|
-
run: docker save
|
|
36
|
+
run: docker save devstuff-ci | gzip > devstuff-ci.tar.gz
|
|
37
37
|
|
|
38
38
|
- name: Upload image artifact
|
|
39
39
|
uses: actions/upload-artifact@v4
|
|
40
40
|
with:
|
|
41
41
|
name: docker-image
|
|
42
|
-
path:
|
|
42
|
+
path: devstuff-ci.tar.gz
|
|
43
43
|
retention-days: 1
|
|
44
44
|
|
|
45
45
|
# ─── Stage 2: one isolated container per tool, all run in parallel ───────────
|
|
@@ -81,12 +81,12 @@ jobs:
|
|
|
81
81
|
name: docker-image
|
|
82
82
|
|
|
83
83
|
- name: Load image
|
|
84
|
-
run: docker load <
|
|
84
|
+
run: docker load < devstuff-ci.tar.gz
|
|
85
85
|
|
|
86
86
|
- name: Test ${{ matrix.tool }} installs
|
|
87
87
|
run: |
|
|
88
88
|
set +e
|
|
89
|
-
docker run --rm
|
|
89
|
+
docker run --rm devstuff-ci \
|
|
90
90
|
pytest "tests/integration/test_tools.py::test_install[${{ matrix.tool }}]" \
|
|
91
91
|
-v --tb=short 2>&1 | tee test-output.txt
|
|
92
92
|
RESULT=${PIPESTATUS[0]}
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## v2.0.0 (2026-08-04)
|
|
2
|
+
|
|
3
|
+
### BREAKING CHANGE
|
|
4
|
+
|
|
5
|
+
- the `dev-setup` command alias is removed. Config/data moved
|
|
6
|
+
from ~/.config/dev-setup and ~/.local/share/dev-setup to the devstuff
|
|
7
|
+
equivalents. bashrc block markers renamed (dev-setup: bat -> devstuff: bat,
|
|
8
|
+
dev-setup-fn:<key> -> devstuff-fn:<key>). Existing users run `devstuff
|
|
9
|
+
doctor --fix` to migrate.
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- drop dev-setup alias, rename paths to devstuff, add doctor command
|
|
14
|
+
- **configure**: add pre-commit, docker, bat, ansible and lazygit wizards
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- **dev**: copy the devstuff wheel, not the stale dev_setup name
|
|
19
|
+
|
|
1
20
|
## v1.18.0 (2026-08-01)
|
|
2
21
|
|
|
3
22
|
### Features
|
|
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## What this is
|
|
6
6
|
|
|
7
|
-
`
|
|
7
|
+
`devstuff` is a Python CLI (Click + Rich + questionary) that installs, removes, and tracks
|
|
8
8
|
developer tools on Linux. There is no per-tool Python code — every tool (built-in or
|
|
9
9
|
user-added) is a data record in a YAML catalog, executed by one generic engine
|
|
10
10
|
(`GenericTool` in `src/dev_setup/generic.py`). Adding a tool is a YAML edit, not a code change;
|
|
@@ -13,8 +13,8 @@ adding a new *install mechanism* (a "type") is a code change touched in ~5 place
|
|
|
13
13
|
## Commands
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
uv run
|
|
17
|
-
./
|
|
16
|
+
uv run devstuff <cmd> # run from source (repo root)
|
|
17
|
+
./devstuff <cmd> # bash wrapper — bootstraps .venv on first run, then execs Python
|
|
18
18
|
uv run pytest # unit tests only (integration tests skipped by default, ~0.3s)
|
|
19
19
|
uv run pytest -m integration # real installs — requires sudo + network, run inside Docker (see below)
|
|
20
20
|
uv run pytest tests/test_catalog.py::test_user_catalog_overrides_bundled_tool_in_place # single test
|
|
@@ -67,7 +67,7 @@ src/dev_setup/
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
**Catalog precedence** (`catalog.load_effective_catalog`): bundled `tools.yaml` loads first →
|
|
70
|
-
user YAML at `~/.config/
|
|
70
|
+
user YAML at `~/.config/devstuff/tools.yaml` overrides matching keys in place and appends
|
|
71
71
|
new ones. `registry.py` turns that merged dict into `GenericTool` instances; a tool is
|
|
72
72
|
`builtin` only if it came from bundled and has no user override.
|
|
73
73
|
|
|
@@ -77,13 +77,13 @@ new ones. `registry.py` turns that merged dict into `GenericTool` instances; a t
|
|
|
77
77
|
`generic.py`) rather than an if/elif chain, and shell out via `subprocess`. `install()` raises
|
|
78
78
|
`RuntimeError`/`CalledProcessError` on failure — there's no result enum, command handlers just
|
|
79
79
|
catch and report. `bash`-type scripts are written to a temp file and run with `bash <file>` for
|
|
80
|
-
full parsing fidelity (not `bash -c "<string>"`). `
|
|
80
|
+
full parsing fidelity (not `bash -c "<string>"`). `devstuff update` reuses the same dispatch
|
|
81
81
|
pattern for upgrading an already-installed tool (latest or a pinned version); for `script`/
|
|
82
82
|
`bash` types "update" is a full reinstall, since there's no narrower mechanism, so the command
|
|
83
83
|
layer confirms before re-running it.
|
|
84
84
|
|
|
85
85
|
**Two ways a tool gets defined**: built-in (an entry added directly to `src/dev_setup/tools.yaml`,
|
|
86
|
-
`builtin=True`) or custom (created via the `
|
|
86
|
+
`builtin=True`) or custom (created via the `devstuff add` wizard, `devstuff catalog import`,
|
|
87
87
|
or hand-edited YAML, landing in the user catalog). Both use the identical schema — the only
|
|
88
88
|
difference is which file the key lives in and `category`.
|
|
89
89
|
|
|
@@ -123,7 +123,7 @@ installing PHP packages via Composer as their own first-class type (analogous to
|
|
|
123
123
|
|
|
124
124
|
`src/dev_setup/functions.yaml` + `functions_catalog.py` + `functions_registry.py` +
|
|
125
125
|
`function_runner.py` are a parallel, independent catalog/registry from tools — functions
|
|
126
|
-
aren't installed/removed, they're invoked (`
|
|
126
|
+
aren't installed/removed, they're invoked (`devstuff run <key>`), so they get their own
|
|
127
127
|
schema instead of overloading `GenericTool`. Some duplication with `catalog.py`/`registry.py`
|
|
128
128
|
is deliberate (see "Key design decisions" below).
|
|
129
129
|
|
|
@@ -133,18 +133,18 @@ loaded or enforced at runtime (no `jsonschema` dependency), so if you add/change
|
|
|
133
133
|
a constraint in `functions_catalog.py`'s `validate_catalog()`, update the schema file too or
|
|
134
134
|
they'll silently drift apart.
|
|
135
135
|
|
|
136
|
-
**Why two function `type`s exist**: a `
|
|
136
|
+
**Why two function `type`s exist**: a `devstuff` command is its own child process, so
|
|
137
137
|
anything it does with `subprocess` (env vars, `cd`, aliases) is invisible to the shell that
|
|
138
138
|
invoked it the moment the process exits. `type: script` is for functions that don't need to
|
|
139
139
|
mutate the calling shell (runs as a subprocess, like a tool's `install_script`). `type:
|
|
140
140
|
shell-eval` is for functions that must (`ssh-agent`, `nvm use`-style tools) — it has two
|
|
141
141
|
`register` modes:
|
|
142
|
-
- `register: bashrc` (default) — `
|
|
142
|
+
- `register: bashrc` (default) — `devstuff functions enable <key>` patches a real shell
|
|
143
143
|
function into `~/.bashrc` via `base.patch_bashrc`; the user calls it directly by name in a
|
|
144
|
-
new shell afterward. `
|
|
144
|
+
new shell afterward. `devstuff run` refuses to run these directly (there's nothing it
|
|
145
145
|
*can* do) and points at `functions enable` instead.
|
|
146
|
-
- `register: eval` — `
|
|
147
|
-
`eval "$(
|
|
146
|
+
- `register: eval` — `devstuff run <key>` prints resolved shell code to stdout for
|
|
147
|
+
`eval "$(devstuff run key args)"`. This path must never print anything else to stdout
|
|
148
148
|
(no `ui.*` calls, no prompts) since it would corrupt what gets `eval`'d — missing required
|
|
149
149
|
params are reported on stderr and exit non-zero instead of being prompted for.
|
|
150
150
|
|
|
@@ -161,7 +161,7 @@ make `functions disable` orphan everything after it — closing brace included.
|
|
|
161
161
|
|
|
162
162
|
Functions have a `category` field (defaults to `custom`, freeform — not an enum) that
|
|
163
163
|
`functions list` groups/sorts by, mirroring tools. A `script`-type function that shells out to
|
|
164
|
-
another CLI should guard on `command -v <tool>` and point at `
|
|
164
|
+
another CLI should guard on `command -v <tool>` and point at `devstuff install <tool>` in the
|
|
165
165
|
error rather than let a raw "command not found" surface — see `validate-yaml`/`aws-saml-reauth`
|
|
166
166
|
in `functions.yaml`. If that CLI is only reachable via nvm (like `pi`), source
|
|
167
167
|
`"$HOME/.nvm/nvm.sh"` first (see `acc-check`) — `script`-type functions run via a non-login,
|
|
@@ -173,9 +173,27 @@ ones tools already have.
|
|
|
173
173
|
## Configurators (`configure/`) — tool-specific wizards, deliberately *not* catalog-driven
|
|
174
174
|
|
|
175
175
|
`src/dev_setup/configure/` holds per-tool setup wizards (`devstuff configure <tool>`), registered
|
|
176
|
-
in a `CONFIGURATORS` dict keyed by catalog tool key. There are
|
|
177
|
-
`configure/starship/{model,render,preview,wizard}.py
|
|
178
|
-
`configure/commitizen/{model,render,detect,validate,wizard}.py
|
|
176
|
+
in a `CONFIGURATORS` dict keyed by catalog tool key. There are seven:
|
|
177
|
+
`configure/starship/{model,render,preview,fonts,wizard}.py`,
|
|
178
|
+
`configure/commitizen/{model,render,detect,validate,wizard}.py`,
|
|
179
|
+
`configure/precommit/{model,render,detect,validate,wizard}.py` (the package can't be named
|
|
180
|
+
`pre-commit`, so the hyphenated catalog key maps to the `precommit` package in `CONFIGURATORS`),
|
|
181
|
+
`configure/docker/{model,render,detect,validate,wizard}.py`,
|
|
182
|
+
`configure/bat/{model,render,detect,preview,wizard}.py`,
|
|
183
|
+
`configure/ansible/{model,render,detect,validate,wizard}.py` and
|
|
184
|
+
`configure/lazygit/{model,render,detect,validate,wizard}.py`.
|
|
185
|
+
|
|
186
|
+
**The pattern they all share, and the one thing that matters most when adding another:**
|
|
187
|
+
*measure the tool, don't recall it.* Every configurator's `model.py` was built by interrogating
|
|
188
|
+
the installed binary, and in every single case that turned up something a plausible-sounding
|
|
189
|
+
memory would have got wrong — see each one's "things learned" block below. If you are adding a
|
|
190
|
+
configurator and you have not run the tool, you are guessing.
|
|
191
|
+
|
|
192
|
+
**The second shared pattern: work out what the tool's own validator does *not* check.** All four
|
|
193
|
+
of the newer wizards found their reason to exist there. `dockerd --validate`, `pre-commit
|
|
194
|
+
validate-config`, `ansible-config validate` and lazygit's startup check each accept configs that
|
|
195
|
+
are silently broken, and each in a different way. Establishing that boundary — by writing a
|
|
196
|
+
deliberately broken config and seeing what happens — is the first thing to do, not the last.
|
|
179
197
|
|
|
180
198
|
**Why this one breaks the YAML-catalog rule.** Installation generalises into ~7 mechanisms, which
|
|
181
199
|
is what makes `GenericTool` possible. Configuration does not: starship's config is a TOML file of
|
|
@@ -282,9 +300,157 @@ other edit. Full reasoning in `docs/specs/commitizen-config/`.
|
|
|
282
300
|
time — not on every redraw (~3s, unlike starship's millisecond preview) — and a disagreement
|
|
283
301
|
*warns*, it never vetoes a save. Keep that distinction in the comments.
|
|
284
302
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
303
|
+
**Within the pre-commit configurator, the object being configured is a curated catalog of hooks,
|
|
304
|
+
not arbitrary YAML.** `REPOS`/`HOOKS`/`PRESETS` in `model.py` are ordered and everything else reads
|
|
305
|
+
them — adding a hook is one `Hook` record and it reaches the picker, any preset naming it, the
|
|
306
|
+
language detector, the emitter and the preview with no other edit. Full reasoning in
|
|
307
|
+
`docs/specs/precommit-config/`.
|
|
308
|
+
|
|
309
|
+
**Things learned from the real binary — don't "simplify" these away:**
|
|
310
|
+
- **`default_install_hook_types` is derived from the selected hooks' stages, never asked.**
|
|
311
|
+
`pre-commit install` installs *only* the `pre-commit` git hook unless given `--install-hook-types`,
|
|
312
|
+
so a `commit-msg` hook (commitizen's) without this yields a config that validates, an install
|
|
313
|
+
that reports success, and a hook that never fires. `install_hook_types()` feeds three places: the
|
|
314
|
+
emitted key, the `pre-commit install --hook-type` flags, and a check in `verify()`. Verified end
|
|
315
|
+
to end — with it, `git commit -m "oops no convention"` is rejected.
|
|
316
|
+
- **`pre-commit validate-config` does not check hook ids.** It reads the file's shape and never
|
|
317
|
+
opens a repository, so a mistyped id passes and fails on the user's next commit. That is why
|
|
318
|
+
`validate.resolve()` (`pre-commit install-hooks`, network, minutes) exists as a separate level
|
|
319
|
+
from `verify()` (offline, milliseconds, runs before every save). Don't collapse them.
|
|
320
|
+
- **Quoting asks the YAML parser rather than a rules table** (`render._str`): a scalar is written
|
|
321
|
+
bare only if `yaml.safe_load(value) == value`. This is what keeps shfmt's indent width `'2'` a
|
|
322
|
+
string, which pre-commit's schema requires, and a rev like `1.0` from becoming a float.
|
|
323
|
+
- **`repos:` with nothing under it parses as null, not `[]`,** and pre-commit rejects it — the
|
|
324
|
+
emitter special-cases `repos: []`. Found by the round-trip assertion, not by reasoning.
|
|
325
|
+
- **The emitter writes YAML by hand for the comments, and `render.matches()` parses it back and
|
|
326
|
+
compares against `render.data()`.** Those two representations must stay in sync; the round-trip
|
|
327
|
+
test over every preset is what enforces it. Don't swap in `yaml.safe_dump` (loses the comments)
|
|
328
|
+
or ruamel (a new runtime dependency for one wizard).
|
|
329
|
+
- **`pre-commit autoupdate` can move a pin to a prerelease** (observed: isort `8.0.1` → `9.0.0b1`),
|
|
330
|
+
so "refresh the revs" shows a diff and confirms rather than applying silently. The GitHub tags
|
|
331
|
+
API is *not* version-ordered and disagreed with autoupdate in both directions — every rev in
|
|
332
|
+
`REPOS` is what autoupdate itself resolved.
|
|
333
|
+
- **`ruff-check` carries `--exit-non-zero-on-fix` deliberately.** Without it the hook exits 0
|
|
334
|
+
having rewritten files, and the fixes silently miss the commit that triggered them.
|
|
335
|
+
- **`Hook.needs` is load-bearing, not documentation.** It surfaces prerequisites on the review
|
|
336
|
+
screen *and* derives the pre-commit.ci `skip` list — pre-commit.ci has no Docker daemon, so
|
|
337
|
+
every `language: docker` hook must be skipped there or the whole CI run fails.
|
|
338
|
+
- **Language detection goes through `git ls-files`** so gitignored content can't decide the
|
|
339
|
+
answer, and needs two files before a language counts — one stray `.sh` in a Python repo must not
|
|
340
|
+
pull in two Docker-backed hooks.
|
|
341
|
+
- **The commitizen hook is only suggested when a commitizen config exists.** It runs `cz check`,
|
|
342
|
+
which fails *every* commit without one.
|
|
343
|
+
- Conflicting hook pairs (`ruff-format` + `black`) are **warned about, not refused** — but no
|
|
344
|
+
shipped preset may contain one, which is a test.
|
|
345
|
+
|
|
346
|
+
**Within the docker configurator, the object being configured is a daemon that has to be
|
|
347
|
+
restarted before anything takes effect.** Full reasoning in `docs/specs/docker-config/`.
|
|
348
|
+
|
|
349
|
+
**Things learned from the real binary — don't "simplify" these away:**
|
|
350
|
+
- **`dockerd --validate` accepts five configs that break every container.** Measured: an
|
|
351
|
+
unloadable log driver, an unknown log option for the chosen driver, `compress: "true"` with
|
|
352
|
+
`max-file: "1"`, an address pool `size` below its base prefix, and `hosts` alongside a systemd
|
|
353
|
+
unit passing `-H`. The daemon starts healthy and every `docker run` then fails with an error
|
|
354
|
+
that never mentions daemon.json. `validate.py` runs `--validate` *and* those checks; do not
|
|
355
|
+
collapse it into "the tool already validates this".
|
|
356
|
+
- **Per-driver `log-opts` are measured, and two contradict the obvious guess.** `local` *does*
|
|
357
|
+
take `tag`/`labels`/`env`; `journald` does *not* take `max-size`/`max-file`/`compress`. So
|
|
358
|
+
`log_opts()` filters by driver rather than emitting whatever was set.
|
|
359
|
+
- **`none` is not in `docker info`'s `Plugins.Log`** — it is built into the daemon. The plugin
|
|
360
|
+
list is "drivers that certainly exist", not "all valid drivers", hence `BUILTIN_DRIVERS`.
|
|
361
|
+
- **Only non-default values are written.** A config restating a default freezes it across
|
|
362
|
+
daemon upgrades. This makes `Setting.default` and the `DockerConfig` field default a pair that
|
|
363
|
+
must agree — which is a test, not a convention.
|
|
364
|
+
- **daemon.json has no comment syntax**, so it cannot carry a "generated by" marker (an unknown
|
|
365
|
+
key is rejected — measured). The overwrite path shows a unified diff instead, which answers a
|
|
366
|
+
better question anyway.
|
|
367
|
+
- **The restart is a separate confirmation and reads the daemon's *current* live-restore state**,
|
|
368
|
+
not the new config's: turning live-restore on protects the *next* restart, not the one applying
|
|
369
|
+
it.
|
|
370
|
+
- Writes go through `sudo install` staged via a temp file, never a redirect — a truncating
|
|
371
|
+
redirect that fails leaves the daemon with half a config.
|
|
372
|
+
|
|
373
|
+
**Within the bat configurator, the preview is the deliverable** — it is the closest sibling to
|
|
374
|
+
starship's. Full reasoning in `docs/specs/bat-config/`.
|
|
375
|
+
|
|
376
|
+
**Things learned from the real binary — don't "simplify" these away:**
|
|
377
|
+
- **A bad theme is a warning and exit 0.** bat prints `[bat warning]: Unknown theme` to stderr —
|
|
378
|
+
which a pager swallows — and carries on with the default forever. Every theme named is checked
|
|
379
|
+
against `bat --list-themes`. A bad `--style` component, by contrast, is a hard error and needs
|
|
380
|
+
no help; the checks are asymmetric on purpose.
|
|
381
|
+
- **The theme list is read at run time, not tabled.** `bat cache --build` lets users add their
|
|
382
|
+
own. The shipped table is the offline fallback plus the light/dark metadata bat does not
|
|
383
|
+
expose — and a test asserts every shipped name still exists, so the fallback can't rot.
|
|
384
|
+
- **Classifying themes by foreground luminance is wrong for Solarized.** Both variants share one
|
|
385
|
+
palette by design and measure identically; three more (`ansi`, `base16`, `base16-256`) emit no
|
|
386
|
+
true colour at all. Hence three modes, hand-corrected against the measurement.
|
|
387
|
+
- **Unquoted values containing spaces are split into arguments** — `--theme=Solarized (dark)`
|
|
388
|
+
makes bat try to open a file called `(dark)`. Every value is quoted.
|
|
389
|
+
- **`rule` is a subset of `grid`** and bat says so. Found when the live check rejected a
|
|
390
|
+
"turn everything on" preset; all 21 component pairs were then swept and this is the only one.
|
|
391
|
+
- The preview clears every `BAT_*` variable, because they beat the config file — otherwise it
|
|
392
|
+
would render the environment's theme while claiming to show the candidate's.
|
|
393
|
+
|
|
394
|
+
**Within the ansible configurator, the check that matters is "did ansible *read* this",
|
|
395
|
+
not "is this valid".** Full reasoning in `docs/specs/ansible-config/`.
|
|
396
|
+
|
|
397
|
+
**Things learned from the real binary — don't "simplify" these away:**
|
|
398
|
+
- **`[ssh_connection]` is not a section ansible-core reads.** `pipelining` moved to `[defaults]`.
|
|
399
|
+
Verified both ways: under `[ssh_connection]` nothing appears in `ansible-config dump
|
|
400
|
+
--only-changed`; under `[defaults]` it appears as `ANSIBLE_PIPELINING`. That stanza is the most
|
|
401
|
+
copy-pasted three lines in Ansible and it is inert.
|
|
402
|
+
- **So the load-bearing check is the dump comparison**, not `ansible-config validate`. Every
|
|
403
|
+
setting written must appear in `ansible-config dump --only-changed` sourced from our file. A
|
|
404
|
+
test proves the check can *fail* by moving `pipelining` back into `[ssh_connection]`.
|
|
405
|
+
- **The `yaml` stdout callback was removed** — superseded by `callback_result_format`. Setting
|
|
406
|
+
`stdout_callback = yaml` passes every check and produces JSON. The first draft of the `project`
|
|
407
|
+
preset did exactly this.
|
|
408
|
+
- **`ansible-config validate` rejects a working setting.** `callback_result_format` is a
|
|
409
|
+
callback-*plugin* option and the validator only knows core settings. So it is unreliable in
|
|
410
|
+
both directions. Hence `Setting.dump_type`, the discounted complaint (stated in the check's own
|
|
411
|
+
detail, never silent), and the warning telling the user what they will see.
|
|
412
|
+
- **Plugin options dump under `-t <type>` and in lowercase**, unlike core settings' SHOUTING_CASE.
|
|
413
|
+
- **Values are never quoted.** `inventory = "./inv"` becomes a path containing quotes, and
|
|
414
|
+
`pipelining = "True"` is read as **False** — quoting a boolean inverts it. The reader coerces
|
|
415
|
+
the way ansible does, not the way `configparser` would.
|
|
416
|
+
- **A retired section is preserved and reported, never migrated.** The wizard cannot know whether
|
|
417
|
+
an older ansible elsewhere still reads it.
|
|
418
|
+
- **ansible ignores `./ansible.cfg` in a world-writable directory** — warned about before the
|
|
419
|
+
questions and again after the save.
|
|
420
|
+
|
|
421
|
+
**Within the lazygit configurator, the tool provides no usable way to check a config's keys.**
|
|
422
|
+
Full reasoning in `docs/specs/lazygit-config/`.
|
|
423
|
+
|
|
424
|
+
**Things learned from the real binary — don't "simplify" these away:**
|
|
425
|
+
- **lazygit rejects wrong *types* and silently ignores unknown *keys* and invalid enum values.**
|
|
426
|
+
Everything in the package follows from that split. Two live tests assert both halves, so a
|
|
427
|
+
future lazygit changing either is caught.
|
|
428
|
+
- **`lazygit --config` is not a key list.** It omits every setting with no default, including
|
|
429
|
+
`git.paging.pager` — the delta integration everyone wants. Treating it as authoritative would
|
|
430
|
+
have made the wizard refuse to write the most-wanted setting there is.
|
|
431
|
+
- **Keys were verified with a type probe**: set the key to a value of obviously the wrong type
|
|
432
|
+
and start lazygit; a real key errors, an unknown one is ignored. The method is recorded in
|
|
433
|
+
`model.py` so the table can be re-derived. It is what found `git.paging.useConfig` dead.
|
|
434
|
+
- **The defaults dump *is* used for values**, and `default_drift()` compares it against the
|
|
435
|
+
model — which caught `gui.sidePanelWidth` being modelled as a string when it is a float. A
|
|
436
|
+
setting absent from the dump is explicitly *not* drift.
|
|
437
|
+
- **PyYAML cannot load lazygit's own default config.** `expandAll: =` hits YAML's
|
|
438
|
+
`tag:yaml.org,2002:value` and `safe_load` raises. All parsing goes through `render.load()`, and
|
|
439
|
+
`=` is quoted on the way out.
|
|
440
|
+
- **A list of mappings must not be hand-emitted.** The first emitter stringified
|
|
441
|
+
`customCommands` — the user data this wizard most promises not to damage. Carried-over
|
|
442
|
+
structures go through `yaml.safe_dump` and are re-indented; only settings the wizard authors
|
|
443
|
+
are hand-emitted, because only those need comments.
|
|
444
|
+
- **Icons reuse `configure/starship/fonts.detect()`**, including its `None` — "cannot tell" is
|
|
445
|
+
not "no", and inverting that would nag every user without fontconfig.
|
|
446
|
+
|
|
447
|
+
Not yet built: configurators for anything other than starship, commitizen, pre-commit, docker,
|
|
448
|
+
bat, ansible and lazygit; `repo: local` pre-commit hook authoring and hooks outside the curated
|
|
449
|
+
catalog; lazygit keybindings and `customCommands` authoring (both preserved, never edited);
|
|
450
|
+
ansible fact-caching backends; and — for pre-commit and commitizen only — round-tripping an
|
|
451
|
+
existing hand-edited config back into wizard state. The four newer configurators *do* round-trip,
|
|
452
|
+
because JSON, INI, bat's flag list and lazygit's YAML are all flat enough to preserve faithfully;
|
|
453
|
+
pre-commit's is not, and that asymmetry is deliberate (see each spec's stack-decisions).
|
|
288
454
|
|
|
289
455
|
## Specs (`docs/specs/`)
|
|
290
456
|
|
|
@@ -321,7 +487,7 @@ strategy-dispatch pattern as `_INSTALLERS` in `generic.py`.
|
|
|
321
487
|
deliberately not disabled by `--yolo`. The prompt is a human attention filter; attention
|
|
322
488
|
degrades over a session, the denylist does not.
|
|
323
489
|
- Credential dirs are blocked for **read** as well as write — exfiltrating an SSH key into a model
|
|
324
|
-
context is as bad as overwriting one. `~/.config/
|
|
490
|
+
context is as bad as overwriting one. `~/.config/devstuff` is readable but not writable, so
|
|
325
491
|
the agent cannot author catalogs (FR-14a).
|
|
326
492
|
- `assess()` (the launch guard) is advisory UX, not a control. Keep that distinction in comments;
|
|
327
493
|
the risk is a future reader mistaking a warning for enforcement.
|