pretext-sim 0.3.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.
- pretext_sim-0.3.2/.editorconfig +15 -0
- pretext_sim-0.3.2/.env.example +9 -0
- pretext_sim-0.3.2/.github/ISSUE_TEMPLATE/bug_report.yml +38 -0
- pretext_sim-0.3.2/.github/ISSUE_TEMPLATE/config.yml +1 -0
- pretext_sim-0.3.2/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- pretext_sim-0.3.2/.github/dependabot.yml +13 -0
- pretext_sim-0.3.2/.github/pull_request_template.md +11 -0
- pretext_sim-0.3.2/.github/workflows/ci.yml +32 -0
- pretext_sim-0.3.2/.github/workflows/release.yml +69 -0
- pretext_sim-0.3.2/.gitignore +12 -0
- pretext_sim-0.3.2/.pre-commit-config.yaml +21 -0
- pretext_sim-0.3.2/AGENTS.md +177 -0
- pretext_sim-0.3.2/CHANGELOG.md +73 -0
- pretext_sim-0.3.2/CODE_OF_CONDUCT.md +25 -0
- pretext_sim-0.3.2/CONTRIBUTING.md +53 -0
- pretext_sim-0.3.2/LICENSE +21 -0
- pretext_sim-0.3.2/PKG-INFO +259 -0
- pretext_sim-0.3.2/README.md +227 -0
- pretext_sim-0.3.2/SECURITY.md +23 -0
- pretext_sim-0.3.2/docs/demo.png +0 -0
- pretext_sim-0.3.2/docs/demo.svg +296 -0
- pretext_sim-0.3.2/docs/running-an-exercise.md +72 -0
- pretext_sim-0.3.2/docs/social.png +0 -0
- pretext_sim-0.3.2/persona.yaml +12 -0
- pretext_sim-0.3.2/pyproject.toml +77 -0
- pretext_sim-0.3.2/sample-output.md +46 -0
- pretext_sim-0.3.2/scripts/capture_corpus.py +52 -0
- pretext_sim-0.3.2/scripts/changelog_section.py +27 -0
- pretext_sim-0.3.2/src/pretext/__init__.py +34 -0
- pretext_sim-0.3.2/src/pretext/cli.py +555 -0
- pretext_sim-0.3.2/src/pretext/client.py +220 -0
- pretext_sim-0.3.2/src/pretext/data/example-profile.yaml +12 -0
- pretext_sim-0.3.2/src/pretext/models.py +326 -0
- pretext_sim-0.3.2/src/pretext/prompt.py +126 -0
- pretext_sim-0.3.2/src/pretext/render.py +247 -0
- pretext_sim-0.3.2/tests/conftest.py +10 -0
- pretext_sim-0.3.2/tests/fixtures/corpus/deepseek-v4-1-flash.json +88 -0
- pretext_sim-0.3.2/tests/fixtures/corpus/venice-uncensored-1-2.json +71 -0
- pretext_sim-0.3.2/tests/fixtures/profiles/exec-assistant.yaml +6 -0
- pretext_sim-0.3.2/tests/fixtures/profiles/finance.yaml +7 -0
- pretext_sim-0.3.2/tests/fixtures/profiles/helpdesk.yaml +5 -0
- pretext_sim-0.3.2/tests/fixtures/profiles/new-hire.yaml +6 -0
- pretext_sim-0.3.2/tests/fixtures/responses/dana.json +85 -0
- pretext_sim-0.3.2/tests/test_cli.py +572 -0
- pretext_sim-0.3.2/tests/test_client.py +426 -0
- pretext_sim-0.3.2/tests/test_corpus.py +61 -0
- pretext_sim-0.3.2/tests/test_models.py +305 -0
- pretext_sim-0.3.2/tests/test_prompt.py +92 -0
- pretext_sim-0.3.2/tests/test_render.py +128 -0
- pretext_sim-0.3.2/tests/test_scripts.py +30 -0
- pretext_sim-0.3.2/uv.lock +1330 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
indent_style = space
|
|
9
|
+
indent_size = 4
|
|
10
|
+
|
|
11
|
+
[*.{yml,yaml,json}]
|
|
12
|
+
indent_size = 2
|
|
13
|
+
|
|
14
|
+
[*.md]
|
|
15
|
+
trim_trailing_whitespace = false
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy to .env. Flags override these; these override the built-in defaults.
|
|
2
|
+
PRETEXT_BASE_URL=https://api.openai.com/v1
|
|
3
|
+
PRETEXT_MODEL=gpt-4o-mini
|
|
4
|
+
PRETEXT_API_KEY=sk-replace-me
|
|
5
|
+
# PRETEXT_TEMPERATURE=0.7
|
|
6
|
+
# PRETEXT_TIMEOUT=60
|
|
7
|
+
# PRETEXT_RETRIES=3
|
|
8
|
+
# PRETEXT_MAX_TOKENS=2400
|
|
9
|
+
# PRETEXT_SEED=7
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something in pretext broke or returned the wrong thing
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Please redact your API key and anything confidential in pasted output.
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: what
|
|
11
|
+
attributes:
|
|
12
|
+
label: What happened
|
|
13
|
+
description: Include the command, the exit code, and the full error.
|
|
14
|
+
validations:
|
|
15
|
+
required: true
|
|
16
|
+
- type: textarea
|
|
17
|
+
id: expected
|
|
18
|
+
attributes:
|
|
19
|
+
label: What you expected
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
- type: input
|
|
23
|
+
id: version
|
|
24
|
+
attributes:
|
|
25
|
+
label: Version
|
|
26
|
+
description: Output of `pretext --version`.
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
- type: input
|
|
30
|
+
id: endpoint
|
|
31
|
+
attributes:
|
|
32
|
+
label: Endpoint and model
|
|
33
|
+
description: Which provider or proxy, and which model name. No keys.
|
|
34
|
+
- type: textarea
|
|
35
|
+
id: profile
|
|
36
|
+
attributes:
|
|
37
|
+
label: Profile
|
|
38
|
+
description: The YAML profile, if the bug depends on it.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Propose a capability, output format, or prompt change
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: Problem
|
|
9
|
+
description: What training scenario cannot be run today?
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: proposal
|
|
14
|
+
attributes:
|
|
15
|
+
label: Proposal
|
|
16
|
+
description: What should pretext do instead? Note the schema or CLI surface if relevant.
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: alternatives
|
|
21
|
+
attributes:
|
|
22
|
+
label: Alternatives considered
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## What and why
|
|
2
|
+
|
|
3
|
+
<!-- The problem this solves, not a file list. -->
|
|
4
|
+
|
|
5
|
+
## Checks
|
|
6
|
+
|
|
7
|
+
- [ ] `pytest` passes
|
|
8
|
+
- [ ] `ruff check src tests` passes
|
|
9
|
+
- [ ] New behaviour has a test, including the failure path where relevant
|
|
10
|
+
- [ ] No API key, real company name, or personal data leaked into fixtures
|
|
11
|
+
- [ ] Prompt/output changes verified with `--dry-run` or a real endpoint
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ${{ matrix.os }}
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
os: [ubuntu-latest]
|
|
18
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: astral-sh/setup-uv@v7
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python }}
|
|
25
|
+
enable-cache: true
|
|
26
|
+
- run: uv sync --extra dev --locked
|
|
27
|
+
- run: uv run ruff check src tests
|
|
28
|
+
- run: uv run ruff format --check src tests
|
|
29
|
+
- run: uv run mypy
|
|
30
|
+
- run: uv run pytest --cov=pretext --cov-fail-under=97
|
|
31
|
+
- run: uv run pretext example -o /tmp/persona.yaml && uv run pretext validate /tmp/persona.yaml
|
|
32
|
+
- run: uv run pretext generate /tmp/persona.yaml --api-key x --dry-run > /dev/null
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v7
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- run: uv sync --extra dev --locked
|
|
23
|
+
|
|
24
|
+
- name: Check the tag matches the package version
|
|
25
|
+
run: |
|
|
26
|
+
version=$(uv run python -c "import pretext; print(pretext.__version__)")
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
if [ "$version" != "$tag" ]; then
|
|
29
|
+
echo "tag $tag does not match version $version" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
- run: uv run ruff check src tests
|
|
34
|
+
- run: uv run ruff format --check src tests
|
|
35
|
+
- run: uv run mypy
|
|
36
|
+
- run: uv run pytest
|
|
37
|
+
|
|
38
|
+
- run: uv build
|
|
39
|
+
|
|
40
|
+
- name: Publish the release
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ github.token }}
|
|
43
|
+
run: |
|
|
44
|
+
version="${GITHUB_REF_NAME#v}"
|
|
45
|
+
notes=$(uv run python scripts/changelog_section.py "$version")
|
|
46
|
+
gh release create "$GITHUB_REF_NAME" \
|
|
47
|
+
--title "$GITHUB_REF_NAME" \
|
|
48
|
+
--notes "$notes" \
|
|
49
|
+
dist/*
|
|
50
|
+
|
|
51
|
+
pypi:
|
|
52
|
+
needs: release
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
environment:
|
|
55
|
+
name: pypi
|
|
56
|
+
url: https://pypi.org/p/pretext-sim
|
|
57
|
+
permissions:
|
|
58
|
+
id-token: write
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
|
|
62
|
+
- uses: astral-sh/setup-uv@v7
|
|
63
|
+
with:
|
|
64
|
+
python-version: "3.12"
|
|
65
|
+
|
|
66
|
+
- run: uv sync --extra dev --locked
|
|
67
|
+
- run: uv build
|
|
68
|
+
|
|
69
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.17.3
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
- id: ruff-format
|
|
7
|
+
args: [--check]
|
|
8
|
+
|
|
9
|
+
- repo: local
|
|
10
|
+
hooks:
|
|
11
|
+
- id: mypy
|
|
12
|
+
name: mypy
|
|
13
|
+
entry: uv run mypy
|
|
14
|
+
language: system
|
|
15
|
+
pass_filenames: false
|
|
16
|
+
|
|
17
|
+
- id: pytest
|
|
18
|
+
name: pytest
|
|
19
|
+
entry: uv run pytest
|
|
20
|
+
language: system
|
|
21
|
+
pass_filenames: false
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
CLI that turns a YAML persona into a labeled social-engineering simulation
|
|
4
|
+
(pretext, opening message, escalation path, defender red flags) via any
|
|
5
|
+
OpenAI-compatible chat completions endpoint.
|
|
6
|
+
|
|
7
|
+
## Setup and commands
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
uv sync --extra dev
|
|
11
|
+
uv run pytest # suite is offline, httpx.MockTransport only
|
|
12
|
+
uv run ruff check src tests
|
|
13
|
+
uv run mypy
|
|
14
|
+
uv run pretext --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`uv` is the only supported installer and `uv.lock` is committed; run tooling through
|
|
18
|
+
`uv run` so CI and your machine resolve the same versions.
|
|
19
|
+
There is no build step and no Makefile. `pytest` must pass with no network and no
|
|
20
|
+
API key, keep it that way.
|
|
21
|
+
|
|
22
|
+
## Layout and data flow
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
profile.yaml ──load_profile──> Profile ──prompt.build_payload_messages──> messages
|
|
26
|
+
│
|
|
27
|
+
client.Client.generate ──httpx──> /chat/completions
|
|
28
|
+
│
|
|
29
|
+
extract_json ──> Simulation.model_validate ──> render.*
|
|
30
|
+
│
|
|
31
|
+
rich panel / markdown / json / file
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| File | Role |
|
|
35
|
+
| ---- | ---- |
|
|
36
|
+
| `models.py` | `Profile`, `Simulation`, `Settings`; error classes; env/flag/`.env` resolution; YAML + JSON loaders |
|
|
37
|
+
| `prompt.py` | System prompt carrying the JSON schema, and the user prompt |
|
|
38
|
+
| `client.py` | httpx POST, retry policy, JSON extraction, response validation |
|
|
39
|
+
| `render.py` | Rich panels (section-gated), markdown/JSON serializers, `DISCLAIMER` |
|
|
40
|
+
| `docs/` | README demo and social preview (generated), plus the runner guide |
|
|
41
|
+
| `cli.py` | Typer commands: `generate`, `batch`, `validate`, `example`, `config` |
|
|
42
|
+
| `data/example-profile.yaml` | Fictional persona used by `pretext example` and the README |
|
|
43
|
+
|
|
44
|
+
`tests/fixtures/profiles/` holds four personas covering distinct roles; the prompt
|
|
45
|
+
tests run every one and assert the payload carries the profile fields.
|
|
46
|
+
|
|
47
|
+
`resolve_settings` is the single place precedence lives: flag, then `PRETEXT_*`
|
|
48
|
+
env, then `./.env`, then `OPENAI_API_KEY`, then defaults. Provider presets live in
|
|
49
|
+
`PROVIDERS` and lose to an explicit `--base-url`; local providers (`LOCAL_PROVIDERS`)
|
|
50
|
+
get the placeholder key `local` so no key is needed. Note `_env` takes the full
|
|
51
|
+
variable name, `pick` adds the prefix. Every new option needs a
|
|
52
|
+
row there, an env name, a CLI flag, and a `Settings` field, else it silently never
|
|
53
|
+
resolves.
|
|
54
|
+
|
|
55
|
+
`doctor` runs its probes through `Client.list_models` and `Client.ping_json` so they
|
|
56
|
+
stay testable via `MockTransport`; `--full` is the only part that calls `generate`.
|
|
57
|
+
`batch` mirrors `generate`'s endpoint flags, skips `_`-prefixed files, isolates
|
|
58
|
+
per-profile failures, and exits 1 if any profile failed. Keep the summary on stderr
|
|
59
|
+
and never abort the run on the first failure.
|
|
60
|
+
|
|
61
|
+
## Contract rules
|
|
62
|
+
|
|
63
|
+
- **stdout is payload, stderr is diagnostics.** The `err` console is
|
|
64
|
+
`Console(stderr=True)`. Warnings, spinner, `wrote …`, and errors go to stderr so
|
|
65
|
+
`pretext generate -f json | jq` stays parseable. Rendering goes to `out`.
|
|
66
|
+
Breaking this is the most likely regression: assert on `result.stdout` vs
|
|
67
|
+
`result.output` deliberately in tests.
|
|
68
|
+
- **Validate before rendering.** Model output goes through `Simulation` and every
|
|
69
|
+
output format runs off that object. Never print raw model text or a partially
|
|
70
|
+
filled panel; a schema failure must raise `APIError` with the pydantic details.
|
|
71
|
+
`Simulation` forbids extra keys and requires `escalation_path` stages to be
|
|
72
|
+
exactly `1..n`, so a model that invents a `stage 2` gap fails instead of
|
|
73
|
+
producing a broken table.
|
|
74
|
+
- **Errors are `PretextError` subclasses** (`ConfigError`, `ProfileError`,
|
|
75
|
+
`APIError`). The CLI catches them, prints to stderr, exits 1. Do not let raw
|
|
76
|
+
`httpx` or `ValidationError` escape to the user, and do not catch them per
|
|
77
|
+
command in a new way.
|
|
78
|
+
- **The persona is the target, not the sender.** The prompt says so explicitly
|
|
79
|
+
because models default to having the attacker introduce themselves as the
|
|
80
|
+
persona. The opening must name the sending address or number in the body, since
|
|
81
|
+
`Subject:`/`From:` header lines are stripped by a validator on
|
|
82
|
+
`opening_message` (`HEADER_PREFIXES`). Keep that validator if you touch the
|
|
83
|
+
prompt.
|
|
84
|
+
- **The disclaimer travels with the exercise.** `render.DISCLAIMER` appears in the
|
|
85
|
+
rich footer, the markdown footer, and the JSON `disclaimer` key. A new output
|
|
86
|
+
format must include it.
|
|
87
|
+
- **Secrets never print.** `Settings.api_key` is `repr=False`; `pretext config`
|
|
88
|
+
shows `set`/`missing`. Key must never reach `--format json` or a log line.
|
|
89
|
+
|
|
90
|
+
## Gotchas
|
|
91
|
+
|
|
92
|
+
- `Settings` is frozen and `HttpUrl`-typed. `non-https` is a warning, not an error,
|
|
93
|
+
because local proxies are the main dev path.
|
|
94
|
+
- Retries cover only `{408,409,425,429,500,502,503,504}` and transport errors;
|
|
95
|
+
`_request` re-enters `_decode` on the last attempt so the final 4xx/5xx detail
|
|
96
|
+
reaches the user. `Retry-After` is honored to 30s. Don't add retries for 400/401.
|
|
97
|
+
- `extract_json` tolerates code fences, prose, and list-shaped `content` parts
|
|
98
|
+
because proxies vary. Keep the fallback: endpoints may ignore
|
|
99
|
+
`response_format: json_object`.
|
|
100
|
+
- Empty model content is an error, not an empty simulation. Overlong artifacts are
|
|
101
|
+
trimmed to 300 chars in a `mode="before"` validator; constraints run before
|
|
102
|
+
`mode="after"` validators, so trim early or the run fails.
|
|
103
|
+
- `--dry-run` still resolves settings, so it needs a key; it prints the prompt to
|
|
104
|
+
stdout via `typer.echo` (unwrapped) and exits 0. `validate` needs no key.
|
|
105
|
+
- File output with the default rich format renders into a `Console.capture()`
|
|
106
|
+
block; the render must happen inside the `with`, not after it.
|
|
107
|
+
- Typer options use `= typer.Option(None, ...)` defaults; resource resolution
|
|
108
|
+
happens in the command body, never in the signature. `Channel | None` is written
|
|
109
|
+
as a string annotation. Enum values are the wire values in the prompt schema,
|
|
110
|
+
so adding a channel means touching `Channel` and `SYSTEM_PROMPT` together.
|
|
111
|
+
- Profile YAML forbids unknown keys on purpose (typo detection), and optional
|
|
112
|
+
strings are blank-to-`None`. Tests rely on both.
|
|
113
|
+
- Company `Acme`/`example` and non-HTTPS endpoints trigger warnings; both are
|
|
114
|
+
benign in tests.
|
|
115
|
+
- Everything in the example profile is fictional. Keep real vendor names out of
|
|
116
|
+
the prompt's sample output.
|
|
117
|
+
|
|
118
|
+
## Docs media
|
|
119
|
+
|
|
120
|
+
`docs/demo.png` comes from `docs/demo.svg`, which is a `Console(record=True)` export
|
|
121
|
+
of `render_simulation` with `sections=("pretext", "opening", "escalation")` over the
|
|
122
|
+
Dana fixture. Convert with `rsvg-convert -z 1.2`, then `magick -trim +repage -strip`.
|
|
123
|
+
Regenerate after any render change or the README advertises stale output.
|
|
124
|
+
`docs/social.png` is a hand-written SVG render; GitHub exposes no REST endpoint for
|
|
125
|
+
the social preview (404 on every version), so it is uploaded in Settings → General.
|
|
126
|
+
|
|
127
|
+
## Repo hygiene
|
|
128
|
+
|
|
129
|
+
CI (`.github/workflows/ci.yml`) runs ruff, pytest, and a dry-run smoke on Python
|
|
130
|
+
3.10-3.13; the local equivalents are `ruff check src tests` and `pytest`. Test
|
|
131
|
+
files are exempt from E501, source files are not.
|
|
132
|
+
|
|
133
|
+
Releasing means bumping `__version__` in `__init__.py` and `version` in
|
|
134
|
+
`pyproject.toml`, adding a `CHANGELOG.md` section (the tag must match the version),
|
|
135
|
+
then tagging `vX.Y.Z`, which triggers `release.yml`. PyPI upload is a second job in
|
|
136
|
+
that same file using trusted publishing (environment `pypi`) and stays red until the
|
|
137
|
+
trusted publisher exists on PyPI. It cannot live in its own workflow: a release
|
|
138
|
+
created with `GITHUB_TOKEN` fires no `release` event. `SECURITY.md` and `CONTRIBUTING.md`
|
|
139
|
+
are user-facing; changes to the schema or CLI need a line there when behaviour moves.
|
|
140
|
+
|
|
141
|
+
## Tests
|
|
142
|
+
|
|
143
|
+
`conftest.py` chdirs every test into a fresh temp directory, so the developer's
|
|
144
|
+
real `.env` never leaks into resolution; keep that fixture autouse.
|
|
145
|
+
|
|
146
|
+
`tests/test_client.py` drives the client through `httpx.MockTransport` and
|
|
147
|
+
monkeypatches `time.sleep`; add cases there rather than hitting the network.
|
|
148
|
+
`tests/test_cli.py` swaps `cli.Client` for a stub, so command behaviour is tested
|
|
149
|
+
without HTTP. Cover the failure path (retry exhaustion, schema violation, bad
|
|
150
|
+
key) whenever you touch the client or schema. `tests/test_models.py` owns
|
|
151
|
+
precedence and validation edge cases.
|
|
152
|
+
|
|
153
|
+
`--from-file` bypasses the API entirely for rendering work. It accepts our own
|
|
154
|
+
export, a bare simulation, or a chat body, and needs no key; the profile is taken
|
|
155
|
+
from the export or must be passed explicitly. `tests/fixtures/responses/dana.json`
|
|
156
|
+
is a real capture used by the replay and render tests. Keep a fixture in sync when
|
|
157
|
+
you change the schema, or those tests are worthless.
|
|
158
|
+
|
|
159
|
+
Cross-provider smoke: gpt-4o-mini, gemini-2.5-flash, claude-haiku-4.5,
|
|
160
|
+
llama-3.3-70b, venice-uncensored-1-2 and qwen3-235b-a22b all held the JSON contract.
|
|
161
|
+
Re-check one non-OpenAI model after a prompt change; prose quality varies far more
|
|
162
|
+
than schema adherence. Venice accepts `response_format: json_object` like the rest.
|
|
163
|
+
|
|
164
|
+
`tests/fixtures/corpus/` holds real outputs from several models, captured with
|
|
165
|
+
`uv run python scripts/capture_corpus.py MODEL`. `tests/test_corpus.py` asserts the
|
|
166
|
+
structural promises (distinct non-adjacent channels, an artifact per stage, openings
|
|
167
|
+
that are written not described). Re-capture after a prompt change and read the diff;
|
|
168
|
+
do not treat the prose as golden.
|
|
169
|
+
|
|
170
|
+
`sample-output.md` is kept as the quality bar. Prompt changes should be compared
|
|
171
|
+
against it; a run costs well under
|
|
172
|
+
a cent, so use `--usage` and a real endpoint instead of guessing.
|
|
173
|
+
|
|
174
|
+
Manual end-to-end without a key: run any throwaway HTTP server that returns
|
|
175
|
+
`{"choices":[{"message":{"content":"<json>"}}]}` and point
|
|
176
|
+
`--base-url http://127.0.0.1:PORT/v1` at it. `--dry-run` is the fast check for a
|
|
177
|
+
prompt edit.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes. This project follows semantic versioning.
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
- Distribution renamed `pretext-sim` (the `pretext` name belongs to PreTeXt); the
|
|
8
|
+
command stays `pretext`
|
|
9
|
+
- PyPI publish workflow on release, via trusted publishing
|
|
10
|
+
|
|
11
|
+
- `docs/running-an-exercise.md`: authorization, delivery, scoring, debrief
|
|
12
|
+
- Model corpus with structural invariant tests, and clear errors when a thinking
|
|
13
|
+
model runs out of output budget
|
|
14
|
+
- `schema` dumps the JSON Schema for integrators and drift checks
|
|
15
|
+
- `--provider` preset for openai, openrouter, venice, ollama and lmstudio
|
|
16
|
+
- `doctor` probes config, TLS, reachability, model availability and JSON mode,
|
|
17
|
+
with `--full` to test schema adherence
|
|
18
|
+
- `batch` generates one exercise per profile in a directory, continuing past failures
|
|
19
|
+
- `--dry-run --out` writes the prompt instead of ignoring `--out`
|
|
20
|
+
- Verified against Venice (venice-uncensored-1-2, qwen3-235b-a22b-instruct)
|
|
21
|
+
|
|
22
|
+
## 0.3.2
|
|
23
|
+
|
|
24
|
+
- PyPI upload runs inside the release workflow
|
|
25
|
+
|
|
26
|
+
## 0.3.1
|
|
27
|
+
|
|
28
|
+
- PyPI packaging under the `pretext-sim` name
|
|
29
|
+
|
|
30
|
+
## 0.3.0
|
|
31
|
+
|
|
32
|
+
- `doctor` diagnoses configuration, TLS, reachability, model availability and JSON
|
|
33
|
+
mode, with `--full` for schema adherence
|
|
34
|
+
- `batch` generates one exercise per profile and reports failures via the exit code
|
|
35
|
+
- `--provider` preset for openai, openrouter, venice, ollama and lmstudio
|
|
36
|
+
- `schema` dumps the JSON Schema of the output contract
|
|
37
|
+
- `--dry-run --out` writes the prompt
|
|
38
|
+
- Thinking models that exhaust their budget now say so instead of returning empty
|
|
39
|
+
- Long objectives trimmed like artifacts
|
|
40
|
+
- mypy, `ruff format`, a 97% coverage gate and a release workflow in CI
|
|
41
|
+
- `docs/running-an-exercise.md`: authorization, delivery, scoring, debrief
|
|
42
|
+
- Model corpus with structural invariant tests
|
|
43
|
+
|
|
44
|
+
## 0.2.0
|
|
45
|
+
|
|
46
|
+
- `--from-file` replays a saved export, bare simulation, or chat body with no API
|
|
47
|
+
call and no key
|
|
48
|
+
- `--sections` renders a subset of the exercise
|
|
49
|
+
- `--usage` reports token counts on stderr
|
|
50
|
+
- Openings must land on the ask and identify the sender; "sent from X, referencing Y"
|
|
51
|
+
narration rejected in the prompt
|
|
52
|
+
- Opening-message validator strips `Subject:`/`From:` header lines the model repeats
|
|
53
|
+
- Overlong stage artifacts are trimmed instead of failing the whole run
|
|
54
|
+
- Prompt now forbids the attacker impersonating the persona, requires an in-body
|
|
55
|
+
sending address, distinct non-adjacent channels, and a new prop per stage
|
|
56
|
+
- Fixture personas, prompt invariant tests, and render tests over a captured response
|
|
57
|
+
- Tests pin terminal width; the suite is deterministic across widths
|
|
58
|
+
- OpenRouter-tested across four models; `uv.lock` committed and Dependabot moved to
|
|
59
|
+
the `uv` ecosystem
|
|
60
|
+
- Coverage gate at 97% (currently 99%) and fuzz coverage for JSON extraction
|
|
61
|
+
|
|
62
|
+
## 0.1.0
|
|
63
|
+
|
|
64
|
+
First release.
|
|
65
|
+
|
|
66
|
+
- `generate`, `validate`, `example`, and `config` commands
|
|
67
|
+
- YAML profile with strict validation and blank-to-null optionals
|
|
68
|
+
- OpenAI-compatible httpx client with retry/backoff on 429/5xx and transport errors
|
|
69
|
+
- pydantic-validated simulation schema with ordered escalation stages
|
|
70
|
+
- Rich terminal, Markdown, and JSON output; `--out` file export
|
|
71
|
+
- `--dry-run` prints resolved config and the exact prompt, sends nothing
|
|
72
|
+
- Config precedence across flags, `PRETEXT_*` env, `.env`, `OPENAI_API_KEY`
|
|
73
|
+
- Diagnostics on stderr so piped JSON stays clean
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Code of conduct
|
|
2
|
+
|
|
3
|
+
## Expected
|
|
4
|
+
|
|
5
|
+
- Assume good faith. Most disagreements here are about prompt wording or schema
|
|
6
|
+
shape, not competence.
|
|
7
|
+
- Critique the change, not the person.
|
|
8
|
+
- Make room for people newer to Python, Typer, or red-team work. Explain the why.
|
|
9
|
+
- Keep review turnaround reasonable; a stalled PR should get a note, not silence.
|
|
10
|
+
|
|
11
|
+
## Not allowed
|
|
12
|
+
|
|
13
|
+
- Harassment, gatekeeping, or dismissiveness in issues and reviews.
|
|
14
|
+
- Pasted output containing secrets, customer names, or personal data.
|
|
15
|
+
- Claiming a generated pretext is verified when it has not been checked.
|
|
16
|
+
|
|
17
|
+
## Enforcement
|
|
18
|
+
|
|
19
|
+
Maintainers may edit, label, or close contributions that ignore this document.
|
|
20
|
+
Repeat problems lead to a block. Escalate by opening an issue mentioning a
|
|
21
|
+
maintainer.
|
|
22
|
+
|
|
23
|
+
## Scope
|
|
24
|
+
|
|
25
|
+
Applies to the repository, its issues, its pull requests, and any project chat.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. Bugs, prompt quality reports, and new output formats are all
|
|
4
|
+
welcome.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
uv sync --extra dev
|
|
10
|
+
uv run pytest --cov=pretext # CI fails under 97% coverage
|
|
11
|
+
uv run ruff check src tests
|
|
12
|
+
uv run mypy
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The suite is offline: `httpx.MockTransport` in `tests/test_client.py` and a stubbed
|
|
16
|
+
`Client` in `tests/test_cli.py`. Do not add tests that need a key or the network.
|
|
17
|
+
`tests/conftest.py` chdirs every test into a temp directory so your own `.env`
|
|
18
|
+
cannot leak in; keep that isolation.
|
|
19
|
+
|
|
20
|
+
## Ground rules
|
|
21
|
+
|
|
22
|
+
- Output must stay validated. Model text is rendered only after it passes
|
|
23
|
+
`Simulation`, never before.
|
|
24
|
+
- stdout carries the payload, stderr carries diagnostics. `-f json` has to remain
|
|
25
|
+
pipeable.
|
|
26
|
+
- New config options need all four touch points: `Settings` field, `resolve_settings`
|
|
27
|
+
precedence row, `PRETEXT_*` env name, CLI flag.
|
|
28
|
+
- A new output format must include `render.DISCLAIMER`.
|
|
29
|
+
- Keep fixtures fictional and free of real company names, domains, and keys.
|
|
30
|
+
- Any prompt change: check `--dry-run` output and, if you have an endpoint, one
|
|
31
|
+
real generation before opening the PR.
|
|
32
|
+
|
|
33
|
+
## Hooks
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
uv run pre-commit install # ruff, mypy, pytest before each commit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Pull requests
|
|
40
|
+
|
|
41
|
+
One concern per PR. Describe the training scenario that motivated it. Fill in the
|
|
42
|
+
checklist. If a change alters the JSON schema, say so in the PR and update
|
|
43
|
+
`SYSTEM_PROMPT` and `Simulation` together.
|
|
44
|
+
|
|
45
|
+
## Commit messages
|
|
46
|
+
|
|
47
|
+
Present tense, imperative, first line under 72 characters, explaining why the
|
|
48
|
+
change exists. Conventional prefixes (`feat:`, `fix:`, `docs:`) are fine but not
|
|
49
|
+
required.
|
|
50
|
+
|
|
51
|
+
## Reporting security issues
|
|
52
|
+
|
|
53
|
+
See `SECURITY.md`. Never paste an API key into an issue.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pretext contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|