Files
chemenu/tools/chemenu/tests/test_instructions_shell.py
T
torbenandClaude Opus 5.5 f9056c04f7
CI / verify (push) Successful in 6m17s
CI / pwsh (push) Successful in 1m59s
Release / release (push) Successful in 35s
feat: docs/ carries the operator's how-to guides, a configuration reference and the architecture and telemetry explanations carved out of README.md, INSTALL.md, INSTALL-MCP.md and EVALS.md; the last two retired (#190)
Files changed:
- .gitea/workflows/ci.yml
- .gitignore
- .vibe/hooks.toml
- AGENTS.md
- CHANGES.md
- EVALS.md
- INSTALL-MCP.md
- INSTALL.md
- README.md
- VERSION
- docs/dev/development.md
- docs/explanation/README.md
- docs/explanation/architecture.md
- docs/explanation/language-boundaries.md
- docs/explanation/telemetry-and-evaluation.md
- docs/how-to/README.md
- docs/how-to/browse-with-obsidian.md
- docs/how-to/capture-repository-docs.md
- docs/how-to/export-guidelines.md
- docs/how-to/ingest-a-source.md
- docs/how-to/maintain-the-wiki.md
- docs/how-to/query-the-wiki.md
- docs/how-to/run-the-weekly-review.md
- docs/how-to/set-up-the-mcp-server.md
- docs/how-to/trace-and-score-sessions.md
- docs/how-to/troubleshoot.md
- docs/how-to/update-an-instance.md
- docs/reference/README.md
- docs/reference/commands/distribution-and-versioning.md
- docs/reference/commands/instance-health.md
- docs/reference/commands/raw-material-and-uploads.md
- docs/reference/commands/telemetry.md
- docs/reference/commands/types-instructions-and-docs.md
- docs/reference/configuration.md
- instructions/dev/dev-setup.md
- instructions/dev/doc-pull-through.md
- instructions/dev/stack-build/SKILL.md
- instructions/dev/testing-conventions.md
- instructions/dev/version-parts.md
- instructions/setup-instance.md
- instructions/upgrade-instance.md
- reports/CONTRACT.md
- tools/.coveragerc
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/eval_cmd.py
- tools/chemenu/commands/git_publish.py
- tools/chemenu/commands/upload_cmd.py
- tools/chemenu/commands/version_cmd.py
- tools/chemenu/evals/__init__.py
- tools/chemenu/evals/scorecard.py
- tools/chemenu/evals/trajectory.py
- tools/chemenu/telemetry/policy.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_git_publish.py
- tools/chemenu/tests/test_instructions_shell.py
- tools/chemenu/tests/test_pipeline_l0.py
- tools/chemenu/toc.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
2026-10-07 05:13:56 +02:00

136 lines
5.6 KiB
Python

"""The stack's shipped instructions read the same in bash, Git Bash and PowerShell 7.
`instructions/CONTRACT.md` § Writing an instruction states the rule; this is the check behind it.
An instruction is run by whichever harness the operator uses - Claude Code under Git Bash on
Windows, Copilot under PowerShell 7 - so a command block that only one shell reads sends the
other agent off to translate it, which is where the Weg-D install went wrong (Gitea #140, #153).
Scope: every instruction `dist export` ships, minus `instructions/migrations/`, which belong to
the release they shipped with. A test rather than an `instructions verify` rule because the rule
binds what the stack ships; an instance's own instructions are its own decision.
"""
from __future__ import annotations
import re
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[3]
INSTRUCTIONS = REPO / "instructions"
# The info strings of a block an agent runs; a json/yaml/markdown block is content, not a command.
COMMAND_FENCES = {"", "bash", "sh", "shell", "console", "powershell", "pwsh"}
FORBIDDEN = (
("heredoc", re.compile(r"<<")),
("export", re.compile(r"(^|[;&|]\s*)export\s")),
("command substitution", re.compile(r"\$\(")),
("shell variable", re.compile(r"\$\{|\$[A-Z_][A-Z0-9_]*\b")),
("inline environment", re.compile(r"^[A-Z_][A-Z0-9_]*=\S*\s+\S")),
("&&", re.compile(r"&&")),
("for loop", re.compile(r"^\s*for\s.*;\s*do\b")),
("cp", re.compile(r"(^|[;&|]\s*)cp\s")),
("cat >", re.compile(r"\bcat\s+>")),
("sha256sum", re.compile(r"\bsha256sum\b")),
("curl", re.compile(r"\bcurl\b")),
("tar", re.compile(r"(^|[;&|]\s*)tar\s")),
# Which name starts a real Python differs per shell and platform: on Windows `python3` - in
# PowerShell also `python` - can be the Microsoft Store's alias. A command goes through a
# launcher that finds the interpreter itself (`tools/wikitool`, `tools/bugreport`).
("bare interpreter", re.compile(r"(^|[;&|]\s*)(python3?|py)(\.exe)?\s")),
)
# The two sanctioned exceptions, each one line per shell (instructions/CONTRACT.md): the session
# id (D26) and the preflight download before an instance exists (E2). Keyed by exact line, so a
# second use of the same construct elsewhere still fails.
ALLOWED = {
("session-setup.md", 'export WIKITOOL_SESSION_ID="wiki-20261001-1430"'),
("session-setup.md", "$env:WIKITOOL_SESSION_ID = 'wiki-20261001-1430'"),
("setup-instance.md", "curl -fLO <browser_download_url of preflight.sh>"),
}
def shipped_instructions() -> list[Path]:
return sorted(
path
for path in INSTRUCTIONS.rglob("*.md")
if not {"dev", "migrations"} & set(path.relative_to(INSTRUCTIONS).parts[:-1])
)
def command_lines(path: Path):
"""`(line number, line)` for every line inside a command fence."""
fence = None
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
stripped = line.strip()
if stripped.startswith("```"):
fence = None if fence is not None else stripped[3:].strip()
continue
if fence is not None and fence in COMMAND_FENCES and stripped:
yield number, stripped
def test_the_scope_is_not_empty():
names = {path.name for path in shipped_instructions()}
assert {"setup-instance.md", "session-setup.md", "upgrade-instance.md"} <= names
assert not any("migrations" in path.parts for path in shipped_instructions())
@pytest.mark.parametrize("path", shipped_instructions(), ids=lambda p: str(p.relative_to(INSTRUCTIONS)))
def test_a_shipped_instruction_uses_no_shell_specific_syntax(path):
found = [
f"{path.relative_to(REPO)}:{number}: {name}: {line}"
for number, line in command_lines(path)
if (path.name, line) not in ALLOWED
for name, pattern in FORBIDDEN
if pattern.search(line)
]
assert not found, "\n".join(found)
def test_every_exception_is_still_in_use():
"""An allowance nobody uses any more is a gap waiting for the next construct."""
used = {
(path.name, line)
for path in shipped_instructions()
for _, line in command_lines(path)
}
assert ALLOWED <= used, ALLOWED - used
SHIPPED_ROOT_DOCS = ("AGENTS.md", "README.md", "INSTALL.md")
def shipped_docs() -> list[Path]:
"""The root documents an operator reads, plus every page `dist export` ships under `docs/` -
the how-to guides among them name the preflight call an operator types."""
docs = REPO / "docs"
pages = sorted(
path for path in docs.rglob("*.md") if path.relative_to(docs).parts[0] != "dev"
)
return [REPO / name for name in SHIPPED_ROOT_DOCS] + pages
# A line that starts the PowerShell preflight - bare (`.\tools\preflight.ps1`, `& preflight.ps1`)
# or through pwsh - as opposed to one that merely names the file (a comment, a download).
PREFLIGHT_CALL = re.compile(r"^(?:&\s*)?(?:\.[\\/])?(?:tools[\\/])?preflight\.ps1\b|^pwsh\b.*preflight\.ps1")
BYPASS = "pwsh -NoProfile -ExecutionPolicy Bypass -File "
@pytest.mark.parametrize(
"path",
shipped_docs() + shipped_instructions(),
ids=lambda p: str(p.relative_to(REPO)),
)
def test_every_powershell_preflight_call_carries_the_bypass(path):
"""Copilot started `.\\tools\\preflight.ps1` bare in the #151 hand check; in a checkout that
carries a Mark of the Web that call fails with PowerShell's own refusal and no guidance."""
if not path.is_file():
pytest.skip(f"{path.name} is not in this tree")
bare = [
f"{number}: {line}"
for number, line in command_lines(path)
if PREFLIGHT_CALL.match(line) and not line.startswith(BYPASS)
]
assert not bare, "\n".join(bare)