diff --git a/README.md b/README.md
index 9db14fd..00e1985 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ agentskills.io-compatible.
Suppose you ask your agent:
-> Create a Python 3.10+ command-line project named `invoice-checker`. Use a
+> Create a Python 3.11+ command-line project named `invoice-checker`. Use a
> `src/` layout, add tests and GitHub Actions, and leave it ready to build.
The workflow guides the agent to:
@@ -83,7 +83,12 @@ Make the skill discoverable by your agent.
Hermes Agent
-**Recommended for development — clone the repo and add to `external_dirs`:**
+**Install directly from GitHub:**
+```bash
+hermes skills install CodeSigils/python-project-workflow-skill/skills/python-project-workflow
+```
+
+**For development, clone the repo and add its `skills/` directory to `external_dirs`:**
```yaml
skills:
external_dirs:
@@ -91,10 +96,6 @@ skills:
```
Every commit is immediately reflected without reinstalling.
-The repository is not currently indexed in the Hermes hub, so no hub-install
-command is advertised. Clone plus `external_dirs` is the verified Hermes path
-until registration.
-
*Other agents: see sections below for their native setup commands.*
@@ -102,6 +103,7 @@ until registration.
Claude Code
```bash
+mkdir -p ~/.claude/skills
cp -r skills/python-project-workflow ~/.claude/skills/
```
@@ -110,6 +112,7 @@ cp -r skills/python-project-workflow ~/.claude/skills/
Codex
```bash
+mkdir -p ~/.agents/skills
cp -r skills/python-project-workflow ~/.agents/skills/
```
@@ -118,6 +121,7 @@ cp -r skills/python-project-workflow ~/.agents/skills/
Gemini CLI / .agents/ path
```bash
+mkdir -p .agents/skills
cp -r skills/python-project-workflow .agents/skills/
```
@@ -126,6 +130,7 @@ cp -r skills/python-project-workflow .agents/skills/
OpenCode
```bash
+mkdir -p .opencode/skills
cp -r skills/python-project-workflow .opencode/skills/
```
@@ -233,7 +238,7 @@ python-project-workflow/
│ ├── sync-payload.sh # Synchronizes or checks the runtime payload mirror
│ ├── test-validate-ci.py # Regression tests for CI policy enforcement
│ ├── test-sync-payload.py # Regression tests for payload drift behavior
-│ ├── validate-ci.py # Enforces CI routing and toolchain policy
+│ ├── validate-ci.py # Enforces CI routing, required gates, toolchain policy, and action pins
│ ├── validate.py # Checks skill structure, metadata, and references
│ └── verify-urls.py # Checks documented external links on a schedule
└── skills/
diff --git a/SECURITY.md b/SECURITY.md
index f6bcdd3..7f0e48c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -28,8 +28,9 @@ metadata, and the canonical-to-payload mirror process. These components must
preserve the declared shipping boundary, avoid exposing credentials, and prevent
unreviewed content from entering the runtime payload.
-CI validates payload structure, canonical-reference parity, portable
-instructions, security directives, and the absence of known unsafe probes. A
+CI validates payload structure, regular-file-only canonical and runtime entries,
+canonical-reference parity, portable instructions, security directives, and the
+absence of known unsafe probes. A
passing validator is evidence for these defined checks, not a claim that the
repository or generated projects are free of vulnerabilities.
@@ -64,4 +65,4 @@ summarizes the trust boundary; it does not replace those runtime instructions.
from heuristic or filename checks.
- [x] The shipped payload contains no destructive reset, forced-push, or secret-file dumping commands.
-Last reviewed: 2026-07-16.
+Last reviewed: 2026-07-17.
diff --git a/references/drift-classes.md b/references/drift-classes.md
index 5871f4f..0b22a03 100644
--- a/references/drift-classes.md
+++ b/references/drift-classes.md
@@ -10,7 +10,7 @@ difference is critical because they require fundamentally different fixes.
| Class | Name | What Drifts | Root Cause | Detection | Fix |
|-------|------|-------------|------------|-----------|-----|
-| **A** | **Payload drift** | Mirrored payload references differ from their canonical root sources, or an undeclared payload file appears | Human edited a canonical reference but forgot to run sync, edited a mirrored reference directly, or left an orphan | Payload sync check (`sync-payload.sh --ci`) fails | Run `bash scripts/sync-payload.sh` to synchronize declared mirrors and remove orphans |
+| **A** | **Payload drift** | Mirrored payload references differ from their canonical root sources, an undeclared payload entry appears, or a canonical/runtime entry is a symlink instead of a regular file | Human edited a canonical reference but forgot to run sync, edited a mirrored reference directly, left an orphan, or introduced a link across the shipping boundary | Payload sync check (`sync-payload.sh --ci`) fails | Run `bash scripts/sync-payload.sh` to synchronize declared mirrors and remove payload-side links/orphans; replace canonical source links manually |
| **B** | **Mirror staleness** | Installed copy predates a restructuring | Agent loads a static copy that does not auto-update | `diff -rq /skills// /skills//` shows mismatch | Re-install from source or use a client-supported live source directory |
---
@@ -41,12 +41,13 @@ an explicitly allowed authored-in-place file. No scripts currently ship.
3. Added a new source type without updating the manifest or sync policy
4. Renamed/deleted a source file but the old copy remains in payload
5. CI payload sync check was disabled or bypassed
+6. Replaced a canonical source or payload entry with a symlink
### Detection
- **CI gate**: `bash scripts/sync-payload.sh --ci` exits 1 on any drift
- **Local check**: Run the same command locally before committing
-- **Manifest validation**: the script verifies declared sources and flags payload orphans
+- **Manifest validation**: the script requires regular canonical source files and flags payload symlinks and orphans
### Remediation
@@ -147,6 +148,7 @@ prevention for mirror staleness.**
| Committing a mirrored reference change without its root source | Creates reference drift | Keep canonical and mirrored reference content identical through the sync command |
| Installing skill once and never updating | Mirror staleness guaranteed | Use a live source directory for development; schedule re-installs for production |
| Linking individual files instead of the skill directory | Fragile; breaks on restructure | Link or configure the complete `skills/` directory when the client supports it |
+| Committing symlinks inside the canonical or runtime payload trees | Can escape the shipping boundary or become broken after copying only the payload | Keep every shipped and canonical reference entry as a regular file; use client-level directory configuration only outside the payload |
---
diff --git a/references/lint-format-typing-testing.md b/references/lint-format-typing-testing.md
index bdd7355..6d6452b 100644
--- a/references/lint-format-typing-testing.md
+++ b/references/lint-format-typing-testing.md
@@ -50,7 +50,7 @@ When testing cross-platform compatibility:
- Use `nox` or `tox` with matrices covering different Python versions and operating systems
- GitHub Actions example:
- `strategy.matrix: {os: [ubuntu-latest, windows-latest, macos-latest], python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]}`
+ `strategy.matrix: {os: [ubuntu-latest, windows-latest, macos-latest], python-version: [\"3.11\", \"3.12\", \"3.13\", \"3.14\"]}`
- Test console scripts/installation on all target platforms
- Consider using `cibuildwheel` for building wheels across platforms
- Test path handling with `pathlib` to avoid platform-specific issues
@@ -112,7 +112,8 @@ positives or negatives.
## Special Cases
-- **Jupyter notebooks**: use `nbqa ruff check notebook.ipynb`. Keep core logic in `.py` files.
+- **Jupyter notebooks**: Ruff supports notebooks directly; use `ruff check notebook.ipynb` and
+ `ruff format --check notebook.ipynb`. Keep core logic in `.py` files.
- **Cython**: MyPy may need config to understand `.pyx` files; invoke build system before type checking.
- **Generated files**: exclude from lint/format/type checking via `.ruffignore` or tool config. Document ownership.
diff --git a/references/pyproject-template.md b/references/pyproject-template.md
index af91977..72f545c 100644
--- a/references/pyproject-template.md
+++ b/references/pyproject-template.md
@@ -2,7 +2,7 @@
This file provides a minimal, modern `pyproject.toml` using PEP 621 metadata. Adjust the values to fit your project.
-> **Freshness:** Packaging metadata and Python EOL guidance were reviewed on 2026-07-16.
+> **Freshness:** Packaging metadata and Python EOL guidance were reviewed on 2026-07-17.
> Verify against official docs before using in production.
> See [ruff docs](https://docs.astral.sh/ruff/), [mypy docs](https://mypy.readthedocs.io/),
> [pytest docs](https://docs.pytest.org/), [uv docs](https://docs.astral.sh/uv/) for current versions.
@@ -23,7 +23,7 @@ name = "your-package"
version = "0.1.0"
description = "A short description of your package."
readme = "README.md"
-requires-python = ">=3.10" # or >=3.12 for controlled applications; preserve ecosystem floors
+requires-python = ">=3.11" # or >=3.12 for controlled applications; preserve ecosystem floors
authors = [
{name = "Your Name", email = "you@example.com"}
]
@@ -78,7 +78,7 @@ This tells setuptools to look for packages under the `src/` directory.
```toml
[tool.ruff]
-target-version = "py310" # Align with requires-python
+target-version = "py311" # Align with requires-python
line-length = 88
[tool.ruff.lint]
@@ -94,7 +94,7 @@ quote-style = "double"
```toml
[tool.mypy]
-python_version = "3.10" # Target the minimum version in requires-python
+python_version = "3.11" # Target the minimum version in requires-python
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
@@ -134,23 +134,30 @@ When authoring cross-platform Python packages:
- If you control the deployment environment (e.g., internal tool, web service), consider `requires-python = ">=3.12"` to
use the latest features.
-- If deploying to older LTS systems (e.g., Ubuntu 22.04 with Python 3.10), use `>=3.10`.
+- If deployment must use a distribution's system Python, set the floor to the oldest supported image you actually
+ deploy. For example, Ubuntu 26.04 LTS currently defaults to Python 3.14; older supported images may require a lower
+ floor.
### For Libraries
-- Aim for broad compatibility: `>=3.10` is the current default when no ecosystem constraint says otherwise.
+- Aim for broad compatibility without starting on a near-EOL interpreter: `>=3.11` is the current default when no
+ ecosystem constraint says otherwise.
+- For new projects, do not select Python 3.10 by default: it is in source-only security maintenance and reaches
+ end-of-life in October 2026. Use `>=3.11` or a newer floor that fits the deployment environment.
+- For existing projects that still declare 3.10, preserve the contract unless a compatibility-breaking migration is in
+ scope. Document the constraint and plan a tested migration before changing `requires-python` or the CI matrix.
- For new projects, do not select Python 3.8: it reached end-of-life in October 2024 and no longer receives security
- fixes. Use `>=3.10` or a newer floor that fits the deployment environment.
+ fixes. Use `>=3.11` or a newer floor that fits the deployment environment.
- For existing projects that still declare 3.8, preserve the contract unless a compatibility-breaking migration is in
scope. Document the legacy constraint, warn that 3.8 is unsupported upstream, and plan a tested migration before
changing `requires-python` or the CI matrix.
-- Review this floor as Python 3.10 approaches end-of-life in October 2026.
### Dependency Floor
- When choosing a dependency version, check its `requires-python` to ensure it doesn't declare a higher floor than your
project.
-- Use tools like `pip-index-versions` or `pipdeptree` to audit dependency compatibility.
+- Use `python -m pip index versions ` to inspect available releases and `uv tree` or `pipdeptree` to audit the
+ resolved dependency graph.
## Notes
diff --git a/scripts/validate.py b/scripts/validate.py
index ac608ef..bbdfb9d 100755
--- a/scripts/validate.py
+++ b/scripts/validate.py
@@ -166,6 +166,16 @@ def check_skill() -> None:
if not contains_markdown_phrase(body, directive):
fail(f"{rel(SKILL)} missing Python 3.8 EOL policy: {directive}")
+ python_310_directives = [
+ "For new projects, do not select Python 3.10 by default",
+ "reaches end-of-life in October 2026",
+ "For an existing project that still declares Python 3.10",
+ "do not silently raise `requires-python` or remove 3.10 from CI.",
+ ]
+ for directive in python_310_directives:
+ if not contains_markdown_phrase(body, directive):
+ fail(f"{rel(SKILL)} missing Python 3.10 EOL policy: {directive}")
+
for command in UV_COMMANDS:
if command not in body:
fail(f"{rel(SKILL)} missing uv-managed verification command: {command}")
@@ -210,6 +220,9 @@ def check_references() -> None:
'license-files = ["LICENSE*"]',
"setuptools>=77.0.3",
"[dependency-groups]",
+ 'requires-python = ">=3.11"',
+ 'target-version = "py311"',
+ 'python_version = "3.11"',
):
if required not in pyproject:
fail(f"pyproject template missing current packaging metadata: {required}")
@@ -222,12 +235,16 @@ def check_references() -> None:
fail(f"tooling reference missing uv-managed command: {command}")
if "python -m uv" in tooling:
fail("tooling reference must invoke the uv executable directly")
+ if "nbqa ruff check" in tooling:
+ fail("tooling reference must use Ruff's native notebook support")
drift = read_text_checked(REF_DIR / "drift-classes.md")
if "Edit source (root `SKILL.md`" in drift:
fail("drift guide points to nonexistent root SKILL.md")
if "Its `SKILL.md` is authored in place" not in drift:
fail("drift guide does not describe mixed payload ownership")
+ if "flags payload symlinks and orphans" not in drift:
+ fail("drift guide does not describe regular-file payload enforcement")
safe_editing = read_text_checked(REF_DIR / "safe-editing.md")
for platform_marker in ('terminal("cat")', "write_file", "sed -i 's/"):
@@ -253,6 +270,7 @@ def check_readme() -> None:
"agentskills.io-compatible",
"skills/python-project-workflow/",
"Shipping boundary: `skills/python-project-workflow/` is the runtime payload",
+ "hermes skills install CodeSigils/python-project-workflow-skill/skills/python-project-workflow",
]
for phrase in required:
if not contains_markdown_phrase(readme, phrase):
@@ -269,6 +287,7 @@ def check_security() -> None:
"effective `.gitignore` behavior",
"tracked sensitive files",
"redact sensitive values from reports",
+ "regular-file-only canonical and runtime entries",
]
for phrase in required:
if not contains_markdown_phrase(security, phrase):
diff --git a/skills/python-project-workflow/SKILL.md b/skills/python-project-workflow/SKILL.md
index 18a8922..5516010 100644
--- a/skills/python-project-workflow/SKILL.md
+++ b/skills/python-project-workflow/SKILL.md
@@ -209,17 +209,24 @@ declared Python version range.
- If you control the deployment environment (e.g., internal tool, web service), consider `requires-python = ">=3.12"` to
use the latest features.
-- If deploying to older LTS systems (e.g., Ubuntu 22.04 with Python 3.10), use `>=3.10`.
+- If deployment must use a distribution's system Python, set the floor to the oldest supported image you actually
+ deploy. For example, Ubuntu 26.04 LTS currently defaults to Python 3.14; older supported images may require a lower
+ floor.
### For Libraries
-- Aim for broad compatibility: `>=3.10` is the current default when no ecosystem constraint says otherwise.
+- Aim for broad compatibility without starting on a near-EOL interpreter: `>=3.11` is the current default when no
+ ecosystem constraint says otherwise.
+- For new projects, do not select Python 3.10 by default: it is in source-only security maintenance and reaches
+ end-of-life in October 2026. Use `>=3.11` or a newer floor that fits the deployment environment.
+- For an existing project that still declares Python 3.10, preserve the current contract unless the user authorizes a
+ compatibility-breaking change. Identify the constraint keeping it in scope and recommend a tested migration plan; do
+ not silently raise `requires-python` or remove 3.10 from CI.
- For new projects, do not select Python 3.8: it reached end-of-life in October 2024 and no longer receives security
- fixes. Use `>=3.10` or a newer floor that fits the deployment environment.
+ fixes. Use `>=3.11` or a newer floor that fits the deployment environment.
- For an existing project that still declares Python 3.8, preserve the current contract unless the user authorizes a
compatibility-breaking change. Warn that 3.8 is unsupported upstream, identify the constraint keeping it in scope,
and recommend a tested migration plan. Do not silently raise `requires-python` or remove 3.8 from CI.
-- Review this floor as Python 3.10 approaches end-of-life in October 2026.
## Core Python Footguns
diff --git a/skills/python-project-workflow/references/drift-classes.md b/skills/python-project-workflow/references/drift-classes.md
index 5871f4f..0b22a03 100644
--- a/skills/python-project-workflow/references/drift-classes.md
+++ b/skills/python-project-workflow/references/drift-classes.md
@@ -10,7 +10,7 @@ difference is critical because they require fundamentally different fixes.
| Class | Name | What Drifts | Root Cause | Detection | Fix |
|-------|------|-------------|------------|-----------|-----|
-| **A** | **Payload drift** | Mirrored payload references differ from their canonical root sources, or an undeclared payload file appears | Human edited a canonical reference but forgot to run sync, edited a mirrored reference directly, or left an orphan | Payload sync check (`sync-payload.sh --ci`) fails | Run `bash scripts/sync-payload.sh` to synchronize declared mirrors and remove orphans |
+| **A** | **Payload drift** | Mirrored payload references differ from their canonical root sources, an undeclared payload entry appears, or a canonical/runtime entry is a symlink instead of a regular file | Human edited a canonical reference but forgot to run sync, edited a mirrored reference directly, left an orphan, or introduced a link across the shipping boundary | Payload sync check (`sync-payload.sh --ci`) fails | Run `bash scripts/sync-payload.sh` to synchronize declared mirrors and remove payload-side links/orphans; replace canonical source links manually |
| **B** | **Mirror staleness** | Installed copy predates a restructuring | Agent loads a static copy that does not auto-update | `diff -rq /skills// /skills//` shows mismatch | Re-install from source or use a client-supported live source directory |
---
@@ -41,12 +41,13 @@ an explicitly allowed authored-in-place file. No scripts currently ship.
3. Added a new source type without updating the manifest or sync policy
4. Renamed/deleted a source file but the old copy remains in payload
5. CI payload sync check was disabled or bypassed
+6. Replaced a canonical source or payload entry with a symlink
### Detection
- **CI gate**: `bash scripts/sync-payload.sh --ci` exits 1 on any drift
- **Local check**: Run the same command locally before committing
-- **Manifest validation**: the script verifies declared sources and flags payload orphans
+- **Manifest validation**: the script requires regular canonical source files and flags payload symlinks and orphans
### Remediation
@@ -147,6 +148,7 @@ prevention for mirror staleness.**
| Committing a mirrored reference change without its root source | Creates reference drift | Keep canonical and mirrored reference content identical through the sync command |
| Installing skill once and never updating | Mirror staleness guaranteed | Use a live source directory for development; schedule re-installs for production |
| Linking individual files instead of the skill directory | Fragile; breaks on restructure | Link or configure the complete `skills/` directory when the client supports it |
+| Committing symlinks inside the canonical or runtime payload trees | Can escape the shipping boundary or become broken after copying only the payload | Keep every shipped and canonical reference entry as a regular file; use client-level directory configuration only outside the payload |
---
diff --git a/skills/python-project-workflow/references/lint-format-typing-testing.md b/skills/python-project-workflow/references/lint-format-typing-testing.md
index bdd7355..6d6452b 100644
--- a/skills/python-project-workflow/references/lint-format-typing-testing.md
+++ b/skills/python-project-workflow/references/lint-format-typing-testing.md
@@ -50,7 +50,7 @@ When testing cross-platform compatibility:
- Use `nox` or `tox` with matrices covering different Python versions and operating systems
- GitHub Actions example:
- `strategy.matrix: {os: [ubuntu-latest, windows-latest, macos-latest], python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]}`
+ `strategy.matrix: {os: [ubuntu-latest, windows-latest, macos-latest], python-version: [\"3.11\", \"3.12\", \"3.13\", \"3.14\"]}`
- Test console scripts/installation on all target platforms
- Consider using `cibuildwheel` for building wheels across platforms
- Test path handling with `pathlib` to avoid platform-specific issues
@@ -112,7 +112,8 @@ positives or negatives.
## Special Cases
-- **Jupyter notebooks**: use `nbqa ruff check notebook.ipynb`. Keep core logic in `.py` files.
+- **Jupyter notebooks**: Ruff supports notebooks directly; use `ruff check notebook.ipynb` and
+ `ruff format --check notebook.ipynb`. Keep core logic in `.py` files.
- **Cython**: MyPy may need config to understand `.pyx` files; invoke build system before type checking.
- **Generated files**: exclude from lint/format/type checking via `.ruffignore` or tool config. Document ownership.
diff --git a/skills/python-project-workflow/references/pyproject-template.md b/skills/python-project-workflow/references/pyproject-template.md
index af91977..72f545c 100644
--- a/skills/python-project-workflow/references/pyproject-template.md
+++ b/skills/python-project-workflow/references/pyproject-template.md
@@ -2,7 +2,7 @@
This file provides a minimal, modern `pyproject.toml` using PEP 621 metadata. Adjust the values to fit your project.
-> **Freshness:** Packaging metadata and Python EOL guidance were reviewed on 2026-07-16.
+> **Freshness:** Packaging metadata and Python EOL guidance were reviewed on 2026-07-17.
> Verify against official docs before using in production.
> See [ruff docs](https://docs.astral.sh/ruff/), [mypy docs](https://mypy.readthedocs.io/),
> [pytest docs](https://docs.pytest.org/), [uv docs](https://docs.astral.sh/uv/) for current versions.
@@ -23,7 +23,7 @@ name = "your-package"
version = "0.1.0"
description = "A short description of your package."
readme = "README.md"
-requires-python = ">=3.10" # or >=3.12 for controlled applications; preserve ecosystem floors
+requires-python = ">=3.11" # or >=3.12 for controlled applications; preserve ecosystem floors
authors = [
{name = "Your Name", email = "you@example.com"}
]
@@ -78,7 +78,7 @@ This tells setuptools to look for packages under the `src/` directory.
```toml
[tool.ruff]
-target-version = "py310" # Align with requires-python
+target-version = "py311" # Align with requires-python
line-length = 88
[tool.ruff.lint]
@@ -94,7 +94,7 @@ quote-style = "double"
```toml
[tool.mypy]
-python_version = "3.10" # Target the minimum version in requires-python
+python_version = "3.11" # Target the minimum version in requires-python
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
@@ -134,23 +134,30 @@ When authoring cross-platform Python packages:
- If you control the deployment environment (e.g., internal tool, web service), consider `requires-python = ">=3.12"` to
use the latest features.
-- If deploying to older LTS systems (e.g., Ubuntu 22.04 with Python 3.10), use `>=3.10`.
+- If deployment must use a distribution's system Python, set the floor to the oldest supported image you actually
+ deploy. For example, Ubuntu 26.04 LTS currently defaults to Python 3.14; older supported images may require a lower
+ floor.
### For Libraries
-- Aim for broad compatibility: `>=3.10` is the current default when no ecosystem constraint says otherwise.
+- Aim for broad compatibility without starting on a near-EOL interpreter: `>=3.11` is the current default when no
+ ecosystem constraint says otherwise.
+- For new projects, do not select Python 3.10 by default: it is in source-only security maintenance and reaches
+ end-of-life in October 2026. Use `>=3.11` or a newer floor that fits the deployment environment.
+- For existing projects that still declare 3.10, preserve the contract unless a compatibility-breaking migration is in
+ scope. Document the constraint and plan a tested migration before changing `requires-python` or the CI matrix.
- For new projects, do not select Python 3.8: it reached end-of-life in October 2024 and no longer receives security
- fixes. Use `>=3.10` or a newer floor that fits the deployment environment.
+ fixes. Use `>=3.11` or a newer floor that fits the deployment environment.
- For existing projects that still declare 3.8, preserve the contract unless a compatibility-breaking migration is in
scope. Document the legacy constraint, warn that 3.8 is unsupported upstream, and plan a tested migration before
changing `requires-python` or the CI matrix.
-- Review this floor as Python 3.10 approaches end-of-life in October 2026.
### Dependency Floor
- When choosing a dependency version, check its `requires-python` to ensure it doesn't declare a higher floor than your
project.
-- Use tools like `pip-index-versions` or `pipdeptree` to audit dependency compatibility.
+- Use `python -m pip index versions ` to inspect available releases and `uv tree` or `pipdeptree` to audit the
+ resolved dependency graph.
## Notes