shell-next 0.1.0__tar.gz → 0.1.2__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.
Files changed (143) hide show
  1. shell_next-0.1.2/.github/workflows/docs.yml +54 -0
  2. {shell_next-0.1.0 → shell_next-0.1.2}/.gitignore +2 -1
  3. shell_next-0.1.2/AGENTS.md +321 -0
  4. shell_next-0.1.2/CHANGELOG.md +41 -0
  5. {shell_next-0.1.0 → shell_next-0.1.2}/PKG-INFO +30 -3
  6. {shell_next-0.1.0 → shell_next-0.1.2}/README.md +26 -2
  7. shell_next-0.1.2/docs/assets/stylesheets/brand.css +105 -0
  8. shell_next-0.1.2/docs/backends.md +59 -0
  9. shell_next-0.1.2/docs/development/documentation.md +42 -0
  10. {shell_next-0.1.0 → shell_next-0.1.2}/docs/development/release.md +5 -0
  11. {shell_next-0.1.0 → shell_next-0.1.2}/docs/development/specification.md +39 -4
  12. shell_next-0.1.2/docs/getting-started.md +66 -0
  13. shell_next-0.1.2/docs/index.md +55 -0
  14. shell_next-0.1.2/docs/testing.md +67 -0
  15. shell_next-0.1.2/docs/troubleshooting.md +55 -0
  16. {shell_next-0.1.0 → shell_next-0.1.2}/docs/usage.md +89 -1
  17. shell_next-0.1.2/mkdocs.yml +42 -0
  18. {shell_next-0.1.0 → shell_next-0.1.2}/pyproject.toml +63 -61
  19. shell_next-0.1.2/scripts/docs/validate.py +77 -0
  20. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/client.py +1 -1
  21. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/github.py +1 -1
  22. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/publish.py +7 -1
  23. shell_next-0.1.2/shell-next-logo.png +0 -0
  24. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/mock/scenario.py +132 -131
  25. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/execution.py +200 -199
  26. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/handle.py +243 -228
  27. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/session.py +327 -313
  28. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/capabilities.py +75 -74
  29. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/commands.py +49 -48
  30. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/config.py +175 -174
  31. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/input.py +92 -91
  32. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/privilege.py +81 -80
  33. shell_next-0.1.2/src/shell_next/models/representation.py +71 -0
  34. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/results.py +194 -167
  35. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_state_integration.py +4 -1
  36. {shell_next-0.1.0 → shell_next-0.1.2}/tests/contracts/test_execution.py +14 -6
  37. {shell_next-0.1.0 → shell_next-0.1.2}/tests/contracts/test_queue_cancellation.py +2 -0
  38. shell_next-0.1.2/tests/docs/test_validation.py +50 -0
  39. shell_next-0.1.2/tests/frontend/test_representation.py +32 -0
  40. shell_next-0.1.2/tests/frontend/test_result_content.py +39 -0
  41. shell_next-0.1.2/tests/models/test_representation.py +134 -0
  42. shell_next-0.1.2/tests/models/test_results.py +37 -0
  43. shell_next-0.1.2/wiki/Backend-Support.md +22 -0
  44. shell_next-0.1.2/wiki/Command-Lifecycle.md +23 -0
  45. shell_next-0.1.2/wiki/Getting-Started.md +37 -0
  46. shell_next-0.1.2/wiki/Home.md +24 -0
  47. shell_next-0.1.2/wiki/README.md +20 -0
  48. shell_next-0.1.2/wiki/Testing-with-Mocks.md +34 -0
  49. shell_next-0.1.2/wiki/Troubleshooting.md +16 -0
  50. shell_next-0.1.2/wiki/_Footer.md +4 -0
  51. shell_next-0.1.2/wiki/_Sidebar.md +10 -0
  52. shell_next-0.1.0/CHANGELOG.md +0 -18
  53. {shell_next-0.1.0 → shell_next-0.1.2}/.gitattributes +0 -0
  54. {shell_next-0.1.0 → shell_next-0.1.2}/.github/workflows/ci.yml +0 -0
  55. {shell_next-0.1.0 → shell_next-0.1.2}/.github/workflows/publish.yml +0 -0
  56. {shell_next-0.1.0 → shell_next-0.1.2}/LICENSE +0 -0
  57. {shell_next-0.1.0 → shell_next-0.1.2/docs/assets}/shell-next-logo.png +0 -0
  58. {shell_next-0.1.0 → shell_next-0.1.2}/docs/development/architecture.md +0 -0
  59. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/__init__.py +0 -0
  60. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/quality/__init__.py +0 -0
  61. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/quality/check_source.py +0 -0
  62. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/quality/rhel8.sh +0 -0
  63. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/__init__.py +0 -0
  64. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/check_wheel.py +0 -0
  65. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/verify.py +0 -0
  66. {shell_next-0.1.0 → shell_next-0.1.2}/scripts/release/wheel_probe.py +0 -0
  67. {shell_next-0.1.0 → shell_next-0.1.2}/shell-next Development Specification.md +0 -0
  68. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/__init__.py +0 -0
  69. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/__init__.py +0 -0
  70. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/bash/__init__.py +0 -0
  71. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/bash/authentication.py +0 -0
  72. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/bash/containment.py +0 -0
  73. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/bash/password_channel.py +0 -0
  74. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/bash/syntax.py +0 -0
  75. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/cmd/__init__.py +0 -0
  76. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/cmd/syntax.py +0 -0
  77. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/mock/__init__.py +0 -0
  78. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/mock/driver.py +0 -0
  79. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/mock/session.py +0 -0
  80. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/mock/state.py +0 -0
  81. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/__init__.py +0 -0
  82. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/bridge.py +0 -0
  83. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/channels.py +0 -0
  84. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/containment.py +0 -0
  85. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/driver.py +0 -0
  86. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/preparation.py +0 -0
  87. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/process.py +0 -0
  88. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/syntax.py +0 -0
  89. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/native/termination.py +0 -0
  90. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/powershell/__init__.py +0 -0
  91. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/powershell/driver.ps1 +0 -0
  92. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/powershell/syntax.py +0 -0
  93. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/protocol.py +0 -0
  94. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/windows/__init__.py +0 -0
  95. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/windows/containment.py +0 -0
  96. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/backends/windows/limits.py +0 -0
  97. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/errors.py +0 -0
  98. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/__init__.py +0 -0
  99. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/capture.py +0 -0
  100. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/finalization.py +0 -0
  101. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/lease.py +0 -0
  102. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/observation.py +0 -0
  103. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/operations.py +0 -0
  104. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/frontend/output.py +0 -0
  105. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/__init__.py +0 -0
  106. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/models/state.py +0 -0
  107. {shell_next-0.1.0 → shell_next-0.1.2}/src/shell_next/py.typed +0 -0
  108. {shell_next-0.1.0 → shell_next-0.1.2}/tests/__init__.py +0 -0
  109. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/__init__.py +0 -0
  110. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/bash/__init__.py +0 -0
  111. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/bash/test_authentication.py +0 -0
  112. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/bash/test_password_channel.py +0 -0
  113. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/bash/test_sudo_integration.py +0 -0
  114. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/mock/__init__.py +0 -0
  115. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/mock/test_input_edges.py +0 -0
  116. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/mock/test_privilege.py +0 -0
  117. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/mock/test_session.py +0 -0
  118. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/mock/test_state.py +0 -0
  119. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/mock/test_virtual_observation.py +0 -0
  120. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/__init__.py +0 -0
  121. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_bridge.py +0 -0
  122. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_channels.py +0 -0
  123. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_interrupt_integration.py +0 -0
  124. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_platform_containment.py +0 -0
  125. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_process_failures.py +0 -0
  126. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_resources_integration.py +0 -0
  127. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_stdin_integration.py +0 -0
  128. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_syntax.py +0 -0
  129. {shell_next-0.1.0 → shell_next-0.1.2}/tests/backends/native/test_termination.py +0 -0
  130. {shell_next-0.1.0 → shell_next-0.1.2}/tests/conftest.py +0 -0
  131. {shell_next-0.1.0 → shell_next-0.1.2}/tests/contracts/test_lifecycle.py +0 -0
  132. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/__init__.py +0 -0
  133. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/test_capture.py +0 -0
  134. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/test_capture_failures.py +0 -0
  135. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/test_failures.py +0 -0
  136. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/test_lease_races.py +0 -0
  137. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/test_observation_edges.py +0 -0
  138. {shell_next-0.1.0 → shell_next-0.1.2}/tests/frontend/test_operations.py +0 -0
  139. {shell_next-0.1.0 → shell_next-0.1.2}/tests/models/__init__.py +0 -0
  140. {shell_next-0.1.0 → shell_next-0.1.2}/tests/models/test_values.py +0 -0
  141. {shell_next-0.1.0 → shell_next-0.1.2}/tests/support/__init__.py +0 -0
  142. {shell_next-0.1.0 → shell_next-0.1.2}/tests/support/native.py +0 -0
  143. {shell_next-0.1.0 → shell_next-0.1.2}/tests/support/sessions.py +0 -0
@@ -0,0 +1,54 @@
1
+ name: documentation
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ paths:
6
+ - 'docs/**'
7
+ - 'wiki/**'
8
+ - 'scripts/docs/**'
9
+ - 'shell-next-logo.png'
10
+ - 'mkdocs.yml'
11
+ - 'pyproject.toml'
12
+ - '.github/workflows/docs.yml'
13
+ pull_request:
14
+ paths:
15
+ - 'docs/**'
16
+ - 'wiki/**'
17
+ - 'scripts/docs/**'
18
+ - 'shell-next-logo.png'
19
+ - 'mkdocs.yml'
20
+ - 'pyproject.toml'
21
+ - '.github/workflows/docs.yml'
22
+ workflow_dispatch:
23
+ permissions:
24
+ contents: read
25
+ concurrency:
26
+ group: documentation-${{ github.ref }}
27
+ cancel-in-progress: true
28
+ jobs:
29
+ build:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: '3.14'
36
+ - run: python -m pip install ".[docs]"
37
+ - run: python -m mkdocs build --strict
38
+ - run: python -m scripts.docs.validate
39
+ - uses: actions/upload-pages-artifact@v3
40
+ with:
41
+ path: site
42
+ deploy:
43
+ if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
44
+ needs: build
45
+ runs-on: ubuntu-latest
46
+ permissions:
47
+ pages: write
48
+ id-token: write
49
+ environment:
50
+ name: github-pages
51
+ url: ${{ steps.deployment.outputs.page_url }}
52
+ steps:
53
+ - uses: actions/deploy-pages@v4
54
+ id: deployment
@@ -5,7 +5,8 @@
5
5
  /.ruff_cache/
6
6
  /build/
7
7
  /dist/
8
- /reports/
8
+ /reports/
9
+ /site/
9
10
  /*.egg-info/
10
11
  **/__pycache__/
11
12
  *.py[cod]
@@ -0,0 +1,321 @@
1
+ # shell-next engineering requirements
2
+
3
+ ## Verified project mappings and adoption status
4
+
5
+ - Distribution: `shell-next`; import: `shell_next`; source: `src/shell_next`.
6
+ - Python: 3.14+; Windows (PowerShell 7 and cmd) and Linux Bash, with RHEL 8
7
+ as the reference Linux platform. License: MIT, recorded in `LICENSE`.
8
+ - Runtime dependencies: Python standard library only. Development/build tools
9
+ are in the `dev` extra and MkDocs is in the `docs` extra in `pyproject.toml`.
10
+ - Version source: `pyproject.toml`; there is no runtime `__version__` copy.
11
+ Published tags use `v<version>`. Default branch: `main`; feature branches:
12
+ `codex/<description>` or `codex/<issue-number>-<description>`.
13
+ - GitHub repository: https://github.com/gokurakujoudo/shell-next, remote `origin`.
14
+ Preserve the established `release/<version>` branches (currently
15
+ `release/0.1.1`), rather than introducing a branch named `release`.
16
+ - Contracts: `docs/development/specification.md`; architecture:
17
+ `docs/development/architecture.md`; guides: `docs/usage.md` and
18
+ `docs/getting-started.md`; implemented inventory: `docs/index.md` and
19
+ `README.md`; change record: `CHANGELOG.md`. The root development
20
+ specification and `wiki/` are earlier reference/draft material; canonical
21
+ published documentation is in `docs/`.
22
+ - Production dependencies flow from backends and frontend to models. Preserve
23
+ the shared lifecycle used by native and mock sessions. Original command
24
+ objects are trusted input, not a hostile-code sandbox. Authentication secrets
25
+ and secret stdin payloads must stay out of results and call history.
26
+ - Tests mirror responsibilities under `tests/`; shared fixtures are in
27
+ `tests/support/`. Mock scenarios remain deterministic and free of real
28
+ subprocesses, filesystem writes, network calls, and sleeps. Keep native
29
+ Bash/sudo/Windows differences explicit.
30
+
31
+ The authoritative complete quality gate is `.github/workflows/ci.yml`:
32
+ Ruff check/format, strict mypy, `python scripts/quality/check_source.py`,
33
+ platform behavioral contracts, installed-wheel probes, combined 100% branch
34
+ coverage across Windows/Linux/RHEL 8, builds, and Twine checks. Per-platform
35
+ coverage collection uses a zero threshold only for combination; acceptance
36
+ still requires the combined 100% gate. Genuine platform skips are reported.
37
+
38
+ Use `venv/Scripts/python.exe` in this Windows checkout, or the selected Python
39
+ 3.14+ environment elsewhere. Local handoff runs `git diff --check`,
40
+ `python -m ruff check .`, `python -m ruff format --check .`,
41
+ `python scripts/quality/check_source.py`, `python -m mypy`, documentation
42
+ checks, then `python -m pytest --cov --cov-branch --cov-report=xml`.
43
+ Report any platform-only coverage shortfall; local Windows evidence cannot
44
+ substitute for the combined hosted gate. Installed-wheel/build checks use
45
+ `python -m scripts.release.check_wheel`, `python -m build`, and
46
+ `python -m twine check dist/*`.
47
+
48
+ Documentation checks are `python -m pytest tests/docs`,
49
+ `python -m mkdocs build --strict`, and `python -m scripts.docs.validate`.
50
+ `.github/workflows/docs.yml` builds PR documentation and deploys Pages from
51
+ `main`. It generates `site/` from canonical Markdown; generated files stay ignored.
52
+
53
+ Adoption gaps: `scripts/quality/__init__.py` is empty and no local
54
+ `python -m scripts.quality` entry point exists. A consolidated local entry point,
55
+ automated architecture/capability inventory checks, complete constant/exception
56
+ policy enforcement, and executable marking of legacy examples remain required
57
+ but unimplemented. Existing source-policy checks enforce line counts, names,
58
+ and parameter/return docstrings. Newly marked `python-doc-exec` examples are
59
+ executed by the documentation tests; do not claim legacy examples are covered.
60
+ This policy adoption does not authorize an unrelated mass refactor or release.
61
+
62
+ ## Established publication profile and explicit exceptions
63
+
64
+ Preserve `docs/development/release.md`: prepare versions through a PR to `main`,
65
+ squash merge after verification, then create `release/<version>` at that exact
66
+ merged commit. Do not introduce release-only commits or overwrite published tags.
67
+ Use compatibility-aware PEP 440 versions and `v<version>` tags. The dated,
68
+ nonempty release-note requirement below applies to future releases; existing
69
+ undated changelog sections are historical adoption gaps.
70
+
71
+ The requested default publication target is PyPI and matching GitHub Releases.
72
+ `.github/workflows/publish.yml` is manually dispatched and verifies exact-commit
73
+ quality evidence before building and using Trusted Publishing in environment
74
+ `pypi`. It does not currently create the matching GitHub Release. The existing
75
+ explicit alternative, `python -m scripts.release.publish`, uses configured
76
+ local Twine credentials and stages matching GitHub artifacts after the same
77
+ quality evidence. Preserve this authorized local-release option. Automatic
78
+ release-branch triggering and CI creation of matching GitHub Releases remain
79
+ adoption gaps, not completed capabilities. No publication is part of a feature
80
+ request unless the user requests it.
81
+
82
+ ## Scope, project facts, and sources of truth
83
+
84
+ These requirements govern engineering quality and delivery. Keep the project's
85
+ business purpose, domain model, public behavior, and specialized architecture
86
+ in their existing sections. Read AGENTS.md, README.md, pyproject.toml, the
87
+ feature/status inventory if present, and relevant reference/development
88
+ documentation before changing code.
89
+
90
+ Record the actual distribution/import names, production source root, supported
91
+ Python versions and platforms, license, dependency policy, version source,
92
+ default branch, release branch, quality command, and documentation entry points.
93
+ Use pyproject.toml for packaging/tool metadata. Resolve license and compatibility
94
+ decisions from the project's choices; do not assume a license, Python minimum,
95
+ fixed version series, or release destination.
96
+
97
+ Reference documentation owns public contracts; executable guides own workflows;
98
+ tests own behavioral evidence; the feature inventory and README describe
99
+ implemented capability. The changelog records user-visible changes. Requirements
100
+ are not evidence of compliance: identify missing enforcement or documentation
101
+ without claiming that it already exists.
102
+
103
+ ## Design and public API
104
+
105
+ - Organize production code into small packages by responsibility. Dependencies
106
+ flow toward core types and domain logic; CLI, filesystem, network, and other
107
+ adapters depend on the core. Avoid cyclic imports and cross-layer shortcuts.
108
+ Package __init__.py files curate re-exports without behavioral initialization.
109
+ - Prefer the standard library and existing project mechanisms. Keep runtime
110
+ dependencies minimal and separate development, documentation, build, and
111
+ publishing dependencies. Do not introduce speculative abstractions or new
112
+ backends without a concrete requirement; preserve explicitly chosen native
113
+ integrations or dependencies.
114
+ - Design typed public APIs with explicit input validation, stable result/error
115
+ contracts, and documented compatibility. Use __all__ to curate exports and
116
+ ship py.typed for typed distributions. Distinguish intentional exceptions
117
+ from unexpected failures; preserve causes and useful context.
118
+ - For asynchronous I/O and resource workflows, prefer async public entry points.
119
+ Keep synchronous convenience boundaries explicit and prevent nested event
120
+ loops. Pure calculations need not become async merely for uniformity.
121
+ - Define resource ownership, cleanup order, cancellation behavior, and allowed
122
+ concurrency scope. Close caller-owned resources explicitly or through context
123
+ managers. Document whether objects are task-, thread-, or process-safe.
124
+ Specify cache consistency and recalculation/invalidation rules where caching
125
+ exists; do not import another project's cache semantics by default.
126
+ - State the actual trust boundary. Do not describe trusted configuration or
127
+ static capability checks as a hostile-code sandbox. Dynamic execution,
128
+ reflection, imports, ambient I/O, and connectivity need an explicit role in
129
+ the design rather than appearing accidentally through convenience helpers.
130
+
131
+ ## Production source requirements
132
+
133
+ - Each production Python file has at most 200 code-bearing physical lines,
134
+ excluding imports, declaration/attribute docstrings, pure comments, and blank
135
+ lines. Multiline signatures, expressions, and runtime strings count by their
136
+ physical lines. Do not compress statements or embed code in strings to evade
137
+ the limit. Split by responsibility, not arbitrary line count.
138
+ - Production functions and classes use descriptive names without an underscore
139
+ prefix. Only required Python protocol methods use dunder spellings. Do not
140
+ replace meaningful names with generic internal prefixes; curate exports with
141
+ __all__. These declaration rules do not forbid ordinary private state fields.
142
+ - Every production docstring is English rST. Functions and methods, including
143
+ nested helpers, explain how they work, every parameter with :param name:,
144
+ returned values with :returns: unless returning no value, and intentional
145
+ escaping exceptions with :raises Type:. Value classes document constructor
146
+ fields and edge cases. Use notes for useful special behavior, not repetition.
147
+ - Document every named constant and coherent constant group, including enums:
148
+ units or absence of units, actual source, purpose, and choice rationale.
149
+ Never invent external sources or annotate every ordinary control-flow literal.
150
+ - These size, naming, docstring, and constant requirements apply to production
151
+ source. Tests and scripts still pass Ruff and strict mypy. Existing explicit
152
+ project exceptions must be recorded rather than silently broadened.
153
+
154
+ ## Behavioral tests and isolation
155
+
156
+ - Mirror production subsystem responsibilities in tests. A test file may cover
157
+ several related implementation modules; each behavior has one obvious owner.
158
+ Keep integration contracts explicit and reusable fixtures in support modules
159
+ when actually shared. Do not require one test file per source file.
160
+ - Assert public behavior, observable state, and meaningful diagnostics rather
161
+ than private method calls or a second implementation of the same algorithm.
162
+ Cover sunny, rainy, boundary, and composite cases where applicable.
163
+ - Unit tests mock external connectivity, clocks, randomness, and other
164
+ nondeterminism as needed. Isolate file I/O and enabled logs in a separate
165
+ TemporaryDirectory per test. CLI test configuration sources are static
166
+ fixtures declared with their cases. No test should depend on a live account.
167
+ - Include deterministic stress, concurrency, cancellation, and lifecycle/leak
168
+ tests for relevant behavior in the ordinary full gate. Use property or
169
+ differential tests where there is a useful invariant or independent oracle.
170
+ Keep performance benchmarks separate from correctness acceptance.
171
+ - Require 100% production branch coverage, with no threshold reductions,
172
+ exclusions, ignores, weakened assertions, or invented skips merely to pass.
173
+ Report genuine platform/dependency skips explicitly and cover supported
174
+ platforms in the appropriate environment. Use strict test markers/config.
175
+ - If filesystem or temporary-directory permissions block a tool or test, stop
176
+ that operation and obtain the required permission. Do not relocate temporary
177
+ files, change temporary environment variables, weaken isolation, skip checks,
178
+ or change paths solely to bypass the restriction. Continue independent work.
179
+
180
+ ## Change workflow and quality gate
181
+
182
+ - For a bug: update the relevant English contract; add a behavioral regression
183
+ test and prove the intended failure; implement the smallest correct fix;
184
+ prove the test passes; refactor if needed and run focused/full checks.
185
+ - For a feature: prototypes may precede the settled contract, but acceptance
186
+ requires reference documentation and behavioral tests. Update README,
187
+ changelog, and the feature inventory when public claims change.
188
+ - For a refactor: pass existing tests first, change production code, pass the
189
+ same tests, then reorganize test ownership and verify again.
190
+ - Use one authoritative, reproducible quality entry point, preferably
191
+ python -m scripts.quality if no project equivalent exists. Define its concrete
192
+ command and environment in this file; identify it as required but unimplemented
193
+ if it does not exist yet.
194
+ - The gate fails fast through whitespace validation, Ruff, production-policy
195
+ checks, architecture/capability checks, strict mypy over source/tests/scripts,
196
+ documentation checks without coverage, and full behavioral tests with branch
197
+ coverage, including stress tests. Keep all checks in the same environment.
198
+ - Run affected checks during editing. Each completed code-refactor stage and
199
+ final code handoff runs the full gate. Re-run after new changes, failures, or
200
+ unresolved concerns; do not repeat unchanged successful suites without cause.
201
+ - Pure prose needs relevant structure and link checks. Execute the exact source
202
+ of changed examples. Structural documentation changes run the documentation
203
+ suite once without coverage; avoid chapter/series/full-gate duplication.
204
+ - Produce useful JUnit and machine-readable/browsable coverage reports when
205
+ supported. Keep generated reports ignored. Record actual commands, results,
206
+ skips, and tested revisions; never equate configured checks with passing ones.
207
+
208
+ ## Documentation and tutorials
209
+
210
+ - Keep public documentation in English with reference contracts, practical
211
+ guides, an accessible README, and a concise implemented-feature inventory.
212
+ Explain behavior and limitations; never present plans as implemented features.
213
+ - Each tutorial series has a useful standalone introduction and one ordered
214
+ table of contents. Link every published chapter; do not list planned chapters
215
+ as available or duplicate the inventory in other documentation entry points.
216
+ Use stable ordered topic filenames, such as NN-topic-name.md.
217
+ - Mark every complete copyable Python example with a project-specific execution
218
+ marker, using `<!-- python-doc-exec -->` when no marker exists. Documentation
219
+ tests extract and execute that exact Markdown source, not a copied equivalent.
220
+ - Teach the smallest useful operation first, add one concept at a time, and end
221
+ with a realistic composition. Put observable results in inline assertions
222
+ where practical. Explain why each result follows, what resolves or executes,
223
+ and who owns state; do not duplicate assertions in expected-result sections.
224
+ - Examples are deterministic and independent. Mock connectivity, isolate file
225
+ examples in TemporaryDirectory, and close async/resource scopes explicitly.
226
+ Examples must not use production credentials or persistent external effects.
227
+ - Test chapter discovery against the single table of contents and published
228
+ files. Update navigation and changelog when adding, renaming, reordering, or
229
+ retiring chapters. Do not impose word counts, slogans, one test per chapter,
230
+ or arbitrary exact example counts.
231
+ - Check relative links and anchors, CLI/API inventories, and executable snippets.
232
+ If a site/export exists, generate it from canonical Markdown after checks,
233
+ preserve exact examples, and validate navigation/assets. Avoid a competing
234
+ manually maintained copy of the same documentation.
235
+
236
+ ## Packaging and release integrity
237
+
238
+ - Declare build metadata, supported Python versions, license, project identity,
239
+ and tool settings in pyproject.toml. Keep a real license file and accurate
240
+ compatibility claims; do not silently change the license or support matrix.
241
+ - Keep one authoritative version source or verify all maintained copies agree,
242
+ including runtime __version__ when present. Respect established version and
243
+ tag conventions; for new projects use a documented PEP 440-compatible policy
244
+ with compatibility-aware version increments, not a fixed release series.
245
+ - Use isolated/reproducible build tooling appropriate to the project. Build a
246
+ wheel and source distribution from the verified release revision; include
247
+ production code, required runtime resources, typing data, packaging metadata,
248
+ license, and README. Keep local secrets, reports, environments, development
249
+ scratch files, and unrelated artifacts out of distributions.
250
+ - A release is a separate requested operation. Prepare a nonempty dated
251
+ changelog section for its version, retaining unreleased work separately.
252
+ Check notes and metadata before promotion. Preserve the selected source SHA
253
+ and artifact identity through the release; never silently replace a version's
254
+ published tag or distribution.
255
+ - Do not publish, push, merge, delete branches, or contact collaborators merely
256
+ because this policy was installed. Follow the authorized task scope, preserve
257
+ unrelated changes, and honor existing approvals without asking again.
258
+
259
+ ## GitHub feature delivery
260
+
261
+ Use issue -> feature branch -> pull request -> squash merge -> branch cleanup.
262
+
263
+ 1. Create or reuse an issue in the configured GitHub repository for authorized
264
+ feature/bug work. Record scope, public behavior, and acceptance criteria.
265
+ Fetch the configured remote and base a feature branch on the current default
266
+ branch. Prefer codex/<issue-number>-<description> unless the project uses an
267
+ explicit alternative.
268
+ 2. Keep implementation, tests, and documentation together. Follow the change
269
+ workflow and full gate, commit, push, and open a PR against the default
270
+ branch. Link the issue with Closes #number. Describe the final behavior,
271
+ material limitations, and actual verification results.
272
+ 3. Wait for all applicable checks on the exact latest PR head, including push
273
+ and PR verification/builds. Honor required reviews and branch rules. Fix
274
+ failures and re-check the updated head; earlier green checks do not approve
275
+ new commits. Intentional workflow-condition exclusions are not missing
276
+ checks. Keep PR metadata current.
277
+ 4. When merging is within scope, squash merge with an expected-head-SHA guard.
278
+ Preserve draft/review-only requests. Confirm merge and issue closure.
279
+ Synchronize the local default branch and confirm the merged content.
280
+ 5. Verify no post-review work remains on the feature branch before deleting
281
+ its remote and local copies. Squash merges lose feature-commit ancestry;
282
+ inspect merged content before a necessary forced local deletion. Finish
283
+ on the default branch without discarding unrelated work.
284
+
285
+ ## GitHub version publication
286
+
287
+ Use version update -> verified PR -> squash merge -> release/<version> promotion
288
+ -> CI quality/build -> PyPI -> matching Git tag and GitHub Release.
289
+
290
+ 1. On a feature or dedicated release-preparation branch, update the canonical
291
+ version and all maintained copies, including runtime metadata. Finalize a
292
+ dated changelog section. Validate notes, run the full gate, and verify the
293
+ latest PR's CI before squash merging.
294
+ 2. Select the verified merged commit on `main`. Create `release/<version>` at
295
+ exactly that commit and push it. Never add release-only commits, force an
296
+ existing release branch, or publish a feature head.
297
+ 3. Verify version notes and default-branch ancestry, then require the complete
298
+ exact-commit quality gate. The configured release workflow checks quality
299
+ evidence, builds distributions, and publishes with Trusted Publishing. Use
300
+ least-privilege permissions, environment `pypi`, and serialized uploads.
301
+ 4. After PyPI succeeds, create `v<version>` and its GitHub Release at the same
302
+ source commit with changelog notes and the same wheel/source artifacts.
303
+ CI automation of this final stage remains a gap; the established authorized
304
+ local publishing command is the existing alternative. Never run competing
305
+ publication paths for the same immutable version.
306
+ 5. Verify completion, not dispatch: latest applicable CI results, successful
307
+ registry publication, tag target, published Release, attached artifacts, and
308
+ any applicable documentation deployment. Fetch the tag and report links.
309
+ If PyPI succeeds and Release creation fails, retry only the failed final
310
+ stage; never repeat a successful immutable-version upload. After uncertain
311
+ publication, inspect remote state before any retry.
312
+ 6. Keep the default and release branches. For a combined feature/release task,
313
+ clean up the implementation branch only after publication succeeds and
314
+ verification shows no unmerged work remains.
315
+
316
+ Apply this pipeline only when publication is part of the project and requested
317
+ task. A package deliberately not published to PyPI must state its actual release
318
+ destination instead. Missing credentials, environments, CI workflows, or required
319
+ approvals are concrete blockers to the affected stage; do not bypass branch or
320
+ registry controls or claim a release succeeded.
321
+
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## 0.1.2
4
+
5
+ Released 2026-09-11.
6
+
7
+ - Retain the original submitted command in finalized results, including failures
8
+ and commands stopped before execution.
9
+ - Add `CommandResult.stdout_str()` and `stderr_str()` to decode retained tails
10
+ with configurable encoding and error handling (UTF-8 with replacement by default).
11
+ - Document and verify configurable stdout/stderr tail bounds through
12
+ `CaptureConfig.tail_bytes`, including the existing 64 KiB per-stream default.
13
+ - Add readable, bounded representations across public records, mock scenarios,
14
+ sessions, and command handles, preserving secret-field exclusions.
15
+
16
+ ## 0.1.1
17
+
18
+ - Add CI, required coverage, PyPI, Python-version, and license badges to the README.
19
+ - Include branded, searchable documentation and review-ready wiki drafts.
20
+ - Deploy GitHub Pages from `main` and keep logo/edit links valid after branch cleanup.
21
+ - Add documentation build tooling and generated-site validation.
22
+ - Make release uploads independent of Windows progress-display encoding.
23
+
24
+ The package runtime API is unchanged from 0.1.0.
25
+
26
+ ## 0.1.0
27
+
28
+ - Persistent Bash, PowerShell 7, and cmd sessions for Python 3.14+.
29
+ - Structural process arguments and native scripts with persistent shell state.
30
+ - Managed commands, scoped handles, FIFO queuing, independent observation deadlines,
31
+ and bounded interruption and cleanup.
32
+ - Manual and planned stdin, literal prompt matching, bounded byte capture,
33
+ optional durable files, and independent output subscribers.
34
+ - Interactive/noninteractive Bash sudo with separate authentication channels.
35
+ - Strict deterministic mock scenarios through `SessionConfig._session_cls`.
36
+ - RHEL 8, Linux, and Windows contracts, installed-wheel checks, static analysis,
37
+ and 100% combined branch coverage as publication gates.
38
+
39
+ Active Windows elevation, terminal emulation, and strict cleanup of privileged
40
+ descendants are unsupported. Forced interruption invalidates the persistent
41
+ session; callers create a new session explicitly.
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: shell-next
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Persistent asynchronous shell sessions with deterministic test doubles
5
+ Project-URL: Documentation, https://gokurakujoudo.github.io/shell-next/
5
6
  Project-URL: Repository, https://github.com/gokurakujoudo/shell-next
6
7
  Project-URL: Issues, https://github.com/gokurakujoudo/shell-next/issues
7
8
  Author: shell-next contributors
@@ -22,12 +23,27 @@ Requires-Dist: pytest-cov>=7; extra == 'dev'
22
23
  Requires-Dist: pytest>=9; extra == 'dev'
23
24
  Requires-Dist: ruff>=0.15; extra == 'dev'
24
25
  Requires-Dist: twine>=6; extra == 'dev'
26
+ Provides-Extra: docs
27
+ Requires-Dist: mkdocs==1.6.1; extra == 'docs'
25
28
  Description-Content-Type: text/markdown
26
29
 
30
+ <p align="center">
31
+ <img src="https://raw.githubusercontent.com/gokurakujoudo/shell-next/main/shell-next-logo.png" alt="shell-next logo" width="300">
32
+ </p>
33
+
27
34
  # shell-next
28
35
 
36
+ [![CI](https://github.com/gokurakujoudo/shell-next/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/gokurakujoudo/shell-next/actions/workflows/ci.yml?query=branch%3Amain)
37
+ [![Coverage: 100% required](https://img.shields.io/badge/coverage-100%25%20required-brightgreen)](https://github.com/gokurakujoudo/shell-next/actions/workflows/ci.yml?query=branch%3Amain)
38
+ [![PyPI](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpypi.org%2Fpypi%2Fshell-next%2Fjson&query=%24.info.version&label=pypi&prefix=v&color=blue&cacheSeconds=300)](https://pypi.org/project/shell-next/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/shell-next)](https://pypi.org/project/shell-next/)
40
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/gokurakujoudo/shell-next/blob/main/LICENSE)
41
+
29
42
  Persistent asynchronous Bash, PowerShell, and cmd sessions for Python 3.14+.
30
43
 
44
+ [Documentation](https://gokurakujoudo.github.io/shell-next/) ·
45
+ [PyPI](https://pypi.org/project/shell-next/) · [Wiki drafts](wiki/README.md)
46
+
31
47
  ```python
32
48
  from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
33
49
 
@@ -36,7 +52,7 @@ async def inspect_repository(config: SessionConfig):
36
52
  async with use_shell_session(config) as shell:
37
53
  await shell.chdir("/srv/project")
38
54
  result = await shell.run(ProcessCommand("git", ("status", "--short")), check=True)
39
- return result.stdout.tail
55
+ return result.stdout_str()
40
56
  ```
41
57
 
42
58
  Use `ProcessCommand` for executable arguments that must remain structural. Use
@@ -92,7 +108,18 @@ and output in memory. Unmatched commands and unconsumed strict expectations fail
92
108
 
93
109
  ## Capture and capabilities
94
110
 
95
- Output is bytes. Each stream retains a bounded tail (64 KiB by default), and
111
+ Results retain the original `ProcessCommand` or `SessionScript` as `result.command`.
112
+ Use `result.stdout_str()` and `result.stderr_str()` for decoded tail text (UTF-8
113
+ with replacement by default), or access raw bytes through `result.stdout.tail`
114
+ and `result.stderr.tail`.
115
+
116
+ Public records have named-field reprs with readable enum names and abbreviated
117
+ large payloads. Sessions and command handles show current lifecycle state and
118
+ counts. Representations preserve hidden secret fields and do not query the shell.
119
+
120
+ Set `SessionConfig(capture=CaptureConfig(tail_bytes=4096))` to retain at most
121
+ 4 KiB per stream; the default is 65,536 bytes (64 KiB), and zero retains no tail.
122
+ Each stream retains its own bounded tail, and
96
123
  literal prompt matching uses a separate bounded window. Slow event subscribers
97
124
  receive an overflow error without blocking primary capture. Opt into full capture
98
125
  with `CaptureConfig(directory=existing_directory)`. Results distinguish received
@@ -1,7 +1,20 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/gokurakujoudo/shell-next/main/shell-next-logo.png" alt="shell-next logo" width="300">
3
+ </p>
4
+
1
5
  # shell-next
2
6
 
7
+ [![CI](https://github.com/gokurakujoudo/shell-next/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/gokurakujoudo/shell-next/actions/workflows/ci.yml?query=branch%3Amain)
8
+ [![Coverage: 100% required](https://img.shields.io/badge/coverage-100%25%20required-brightgreen)](https://github.com/gokurakujoudo/shell-next/actions/workflows/ci.yml?query=branch%3Amain)
9
+ [![PyPI](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpypi.org%2Fpypi%2Fshell-next%2Fjson&query=%24.info.version&label=pypi&prefix=v&color=blue&cacheSeconds=300)](https://pypi.org/project/shell-next/)
10
+ [![Python](https://img.shields.io/pypi/pyversions/shell-next)](https://pypi.org/project/shell-next/)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/gokurakujoudo/shell-next/blob/main/LICENSE)
12
+
3
13
  Persistent asynchronous Bash, PowerShell, and cmd sessions for Python 3.14+.
4
14
 
15
+ [Documentation](https://gokurakujoudo.github.io/shell-next/) ·
16
+ [PyPI](https://pypi.org/project/shell-next/) · [Wiki drafts](wiki/README.md)
17
+
5
18
  ```python
6
19
  from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
7
20
 
@@ -10,7 +23,7 @@ async def inspect_repository(config: SessionConfig):
10
23
  async with use_shell_session(config) as shell:
11
24
  await shell.chdir("/srv/project")
12
25
  result = await shell.run(ProcessCommand("git", ("status", "--short")), check=True)
13
- return result.stdout.tail
26
+ return result.stdout_str()
14
27
  ```
15
28
 
16
29
  Use `ProcessCommand` for executable arguments that must remain structural. Use
@@ -66,7 +79,18 @@ and output in memory. Unmatched commands and unconsumed strict expectations fail
66
79
 
67
80
  ## Capture and capabilities
68
81
 
69
- Output is bytes. Each stream retains a bounded tail (64 KiB by default), and
82
+ Results retain the original `ProcessCommand` or `SessionScript` as `result.command`.
83
+ Use `result.stdout_str()` and `result.stderr_str()` for decoded tail text (UTF-8
84
+ with replacement by default), or access raw bytes through `result.stdout.tail`
85
+ and `result.stderr.tail`.
86
+
87
+ Public records have named-field reprs with readable enum names and abbreviated
88
+ large payloads. Sessions and command handles show current lifecycle state and
89
+ counts. Representations preserve hidden secret fields and do not query the shell.
90
+
91
+ Set `SessionConfig(capture=CaptureConfig(tail_bytes=4096))` to retain at most
92
+ 4 KiB per stream; the default is 65,536 bytes (64 KiB), and zero retains no tail.
93
+ Each stream retains its own bounded tail, and
70
94
  literal prompt matching uses a separate bounded window. Slow event subscribers
71
95
  receive an overflow error without blocking primary capture. Opt into full capture
72
96
  with `CaptureConfig(directory=existing_directory)`. Results distinguish received
@@ -0,0 +1,105 @@
1
+ :root {
2
+ --shell-ink: #20303a;
3
+ --shell-blue: #007caa;
4
+ --shell-accent: #16b8ef;
5
+ }
6
+
7
+ .wy-side-nav-search,
8
+ .wy-nav-top {
9
+ background: var(--shell-ink);
10
+ }
11
+
12
+ .wy-side-nav-search > a img.logo {
13
+ width: 200px;
14
+ height: 130px;
15
+ max-width: 100%;
16
+ object-fit: cover;
17
+ border-radius: 12px;
18
+ margin: 4px auto 12px;
19
+ padding: 0;
20
+ }
21
+
22
+ .wy-side-nav-search input[type="text"] {
23
+ border-color: var(--shell-accent);
24
+ }
25
+
26
+ .wy-nav-content {
27
+ max-width: 1040px;
28
+ }
29
+
30
+ .wy-menu-vertical header,
31
+ .wy-menu-vertical p.caption {
32
+ color: var(--shell-accent);
33
+ }
34
+
35
+ .rst-content a {
36
+ color: var(--shell-blue);
37
+ }
38
+
39
+ .rst-content h1,
40
+ .rst-content h2,
41
+ .rst-content h3 {
42
+ color: var(--shell-ink);
43
+ }
44
+
45
+ .hero {
46
+ display: grid;
47
+ grid-template-columns: 220px 1fr;
48
+ align-items: center;
49
+ gap: 32px;
50
+ margin: 0 0 32px;
51
+ }
52
+
53
+ .hero img {
54
+ width: 220px;
55
+ border-radius: 18px;
56
+ }
57
+
58
+ .hero p {
59
+ font-size: 1.15rem;
60
+ line-height: 1.6;
61
+ }
62
+
63
+ .hero .version {
64
+ color: #52616b;
65
+ font-size: 0.85rem;
66
+ letter-spacing: 0.06em;
67
+ text-transform: uppercase;
68
+ }
69
+
70
+ .rst-content .button {
71
+ display: inline-block;
72
+ padding: 10px 16px;
73
+ margin: 4px 8px 4px 0;
74
+ border-radius: 6px;
75
+ color: white;
76
+ background: var(--shell-blue);
77
+ font-weight: 600;
78
+ }
79
+
80
+ .rst-content .button.secondary {
81
+ background: var(--shell-ink);
82
+ }
83
+
84
+ a:focus-visible,
85
+ button:focus-visible,
86
+ input:focus-visible {
87
+ outline: 3px solid var(--shell-accent);
88
+ outline-offset: 3px;
89
+ }
90
+
91
+ @media (max-width: 600px) {
92
+ .hero {
93
+ grid-template-columns: 1fr;
94
+ gap: 12px;
95
+ }
96
+
97
+ .hero img {
98
+ width: 170px;
99
+ }
100
+
101
+ .rst-content table.docutils {
102
+ display: block;
103
+ overflow-x: auto;
104
+ }
105
+ }