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.
Files changed (51) hide show
  1. pretext_sim-0.3.2/.editorconfig +15 -0
  2. pretext_sim-0.3.2/.env.example +9 -0
  3. pretext_sim-0.3.2/.github/ISSUE_TEMPLATE/bug_report.yml +38 -0
  4. pretext_sim-0.3.2/.github/ISSUE_TEMPLATE/config.yml +1 -0
  5. pretext_sim-0.3.2/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
  6. pretext_sim-0.3.2/.github/dependabot.yml +13 -0
  7. pretext_sim-0.3.2/.github/pull_request_template.md +11 -0
  8. pretext_sim-0.3.2/.github/workflows/ci.yml +32 -0
  9. pretext_sim-0.3.2/.github/workflows/release.yml +69 -0
  10. pretext_sim-0.3.2/.gitignore +12 -0
  11. pretext_sim-0.3.2/.pre-commit-config.yaml +21 -0
  12. pretext_sim-0.3.2/AGENTS.md +177 -0
  13. pretext_sim-0.3.2/CHANGELOG.md +73 -0
  14. pretext_sim-0.3.2/CODE_OF_CONDUCT.md +25 -0
  15. pretext_sim-0.3.2/CONTRIBUTING.md +53 -0
  16. pretext_sim-0.3.2/LICENSE +21 -0
  17. pretext_sim-0.3.2/PKG-INFO +259 -0
  18. pretext_sim-0.3.2/README.md +227 -0
  19. pretext_sim-0.3.2/SECURITY.md +23 -0
  20. pretext_sim-0.3.2/docs/demo.png +0 -0
  21. pretext_sim-0.3.2/docs/demo.svg +296 -0
  22. pretext_sim-0.3.2/docs/running-an-exercise.md +72 -0
  23. pretext_sim-0.3.2/docs/social.png +0 -0
  24. pretext_sim-0.3.2/persona.yaml +12 -0
  25. pretext_sim-0.3.2/pyproject.toml +77 -0
  26. pretext_sim-0.3.2/sample-output.md +46 -0
  27. pretext_sim-0.3.2/scripts/capture_corpus.py +52 -0
  28. pretext_sim-0.3.2/scripts/changelog_section.py +27 -0
  29. pretext_sim-0.3.2/src/pretext/__init__.py +34 -0
  30. pretext_sim-0.3.2/src/pretext/cli.py +555 -0
  31. pretext_sim-0.3.2/src/pretext/client.py +220 -0
  32. pretext_sim-0.3.2/src/pretext/data/example-profile.yaml +12 -0
  33. pretext_sim-0.3.2/src/pretext/models.py +326 -0
  34. pretext_sim-0.3.2/src/pretext/prompt.py +126 -0
  35. pretext_sim-0.3.2/src/pretext/render.py +247 -0
  36. pretext_sim-0.3.2/tests/conftest.py +10 -0
  37. pretext_sim-0.3.2/tests/fixtures/corpus/deepseek-v4-1-flash.json +88 -0
  38. pretext_sim-0.3.2/tests/fixtures/corpus/venice-uncensored-1-2.json +71 -0
  39. pretext_sim-0.3.2/tests/fixtures/profiles/exec-assistant.yaml +6 -0
  40. pretext_sim-0.3.2/tests/fixtures/profiles/finance.yaml +7 -0
  41. pretext_sim-0.3.2/tests/fixtures/profiles/helpdesk.yaml +5 -0
  42. pretext_sim-0.3.2/tests/fixtures/profiles/new-hire.yaml +6 -0
  43. pretext_sim-0.3.2/tests/fixtures/responses/dana.json +85 -0
  44. pretext_sim-0.3.2/tests/test_cli.py +572 -0
  45. pretext_sim-0.3.2/tests/test_client.py +426 -0
  46. pretext_sim-0.3.2/tests/test_corpus.py +61 -0
  47. pretext_sim-0.3.2/tests/test_models.py +305 -0
  48. pretext_sim-0.3.2/tests/test_prompt.py +92 -0
  49. pretext_sim-0.3.2/tests/test_render.py +128 -0
  50. pretext_sim-0.3.2/tests/test_scripts.py +30 -0
  51. 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,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: uv
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ groups:
8
+ python:
9
+ patterns: ["*"]
10
+ - package-ecosystem: github-actions
11
+ directory: /
12
+ schedule:
13
+ interval: monthly
@@ -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,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+ .env
10
+ .crush/
11
+ .empryo/
12
+ .coverage
@@ -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.