antemortem 0.2.0__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.
@@ -0,0 +1,75 @@
1
+ # ── Local prototype workspace (design notes, scratch, not for distribution) ──
2
+ .design/
3
+ .gstack/
4
+ drafts/
5
+ scratch/
6
+ *.draft.md
7
+
8
+ # ── Python ──
9
+ __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+ *.so
13
+ .Python
14
+
15
+ # ── Build / dist ──
16
+ build/
17
+ develop-eggs/
18
+ dist/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # ── Virtual environments ──
34
+ .venv/
35
+ venv/
36
+ env/
37
+ ENV/
38
+
39
+ # ── Packaging / tooling ──
40
+ .uv/
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+ # ── Testing ──
45
+ .pytest_cache/
46
+ .coverage
47
+ .coverage.*
48
+ htmlcov/
49
+ .tox/
50
+ .nox/
51
+ coverage.xml
52
+ *.cover
53
+ .cache
54
+
55
+ # ── Type checking ──
56
+ .mypy_cache/
57
+ .pyre/
58
+ .pytype/
59
+ .ruff_cache/
60
+
61
+ # ── IDE ──
62
+ .idea/
63
+ .vscode/
64
+ *.swp
65
+ *.swo
66
+
67
+ # ── OS ──
68
+ .DS_Store
69
+ Thumbs.db
70
+
71
+ # ── Secrets / local config ──
72
+ .env
73
+ .env.local
74
+ .env.*.local
75
+ *.pem
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] - 2026-04-21
10
+
11
+ Initial public release of the Antemortem CLI.
12
+
13
+ ### Added
14
+
15
+ - `antemortem init <name>` — scaffold a new antemortem document from the basic template, or `--enhanced` for the enhanced template (calibration dimensions, fine-grained classification, skeptic pass, decision-first output).
16
+ - `antemortem run <doc>` — run LLM-assisted classification on an antemortem document using Claude Opus 4.7. Reads the spec, traps, and referenced files; returns REAL / GHOST / NEW labels with `file:line` citations; writes a JSON audit artifact.
17
+ - `antemortem lint <doc>` — validate an antemortem document's structured schema and verify all `file:line` citations exist in the current repository. Exit 0 on pass, 1 on fail. Suitable for CI.
18
+ - Embedded templates (basic + enhanced) vendored from [hibou04-ops/Antemortem](https://github.com/hibou04-ops/Antemortem) v0.1.1 under MIT.
19
+ - Pydantic v2 schemas for classifications, new traps, and spec mutations — shared end-to-end across `run` and `lint`.
20
+ - Prompt caching on the system prompt for Claude API calls, targeting the Opus 4.7 4096-token minimum to ensure cache hits.
21
+
22
+ ### Scope boundary — not in v0.2
23
+
24
+ - Multi-model support (GPT, Gemini, etc.) — intentional, Claude Opus 4.7 only.
25
+ - GitHub Action / CI integration templates — follow in v0.3.
26
+ - HTML report rendering — follow in v0.3.
27
+ - Web dashboard / viewer — out of scope.
28
+ - Database-backed history — out of scope; files only.
29
+
30
+ ### Rationale
31
+
32
+ Antemortem as a discipline was released as methodology-only in [Antemortem v0.1 / v0.1.1](https://github.com/hibou04-ops/Antemortem). The CLI operationalizes the protocol: scaffold, run, lint — three commands, one week to a disciplined antemortem doc. v0.2 ships the CLI; the methodology repo stays the source of truth for the protocol itself.
33
+
34
+ [Unreleased]: https://github.com/hibou04-ops/antemortem-cli/compare/v0.2.0...HEAD
35
+ [0.2.0]: https://github.com/hibou04-ops/antemortem-cli/releases/tag/v0.2.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hibou04-ops
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.
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: antemortem
3
+ Version: 0.2.0
4
+ Summary: CLI for the Antemortem pre-implementation reconnaissance discipline — scaffold, run, and lint antemortem documents with Claude.
5
+ Project-URL: Homepage, https://github.com/hibou04-ops/antemortem-cli
6
+ Project-URL: Repository, https://github.com/hibou04-ops/antemortem-cli
7
+ Project-URL: Methodology, https://github.com/hibou04-ops/Antemortem
8
+ Project-URL: Issues, https://github.com/hibou04-ops/antemortem-cli/issues
9
+ Author: hibou04-ops
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 hibou04-ops
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: antemortem,anthropic,claude,code-review,pre-mortem,reconnaissance,risk-classification
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Environment :: Console
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Topic :: Software Development :: Quality Assurance
42
+ Requires-Python: >=3.11
43
+ Requires-Dist: anthropic>=0.40.0
44
+ Requires-Dist: pydantic>=2.6.0
45
+ Requires-Dist: python-frontmatter>=1.1.0
46
+ Requires-Dist: typer>=0.12.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
49
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # antemortem-cli
53
+
54
+ ![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)
55
+ ![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)
56
+ ![Version](https://img.shields.io/badge/version-0.2.0-blue)
57
+ ![Status](https://img.shields.io/badge/status-alpha-orange)
58
+
59
+ *CLI for the [Antemortem](https://github.com/hibou04-ops/Antemortem) pre-implementation reconnaissance discipline.*
60
+
61
+ An antemortem is what you do before you build. You put the planned change under stress *on paper*, use an LLM to read the existing code thoroughly, enumerate traps, classify each as REAL / GHOST / NEW with primary-source `file:line` citations, and revise your risk and your spec before writing a single line. This CLI automates the scaffolding, the classification pass, and the schema lint — so the discipline is a single command, not a workflow you have to remember.
62
+
63
+ The methodology lives at [hibou04-ops/Antemortem](https://github.com/hibou04-ops/Antemortem). This repo is the tooling that runs it.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install antemortem
69
+ ```
70
+
71
+ Requires Python 3.11+ and an `ANTHROPIC_API_KEY` environment variable for `run`.
72
+
73
+ ## Three commands
74
+
75
+ ### `antemortem init` — scaffold a document
76
+
77
+ ```bash
78
+ antemortem init my-feature
79
+ # → Created antemortem/my-feature.md (basic template)
80
+
81
+ antemortem init my-migration --enhanced
82
+ # → Created antemortem/my-migration.md (enhanced template)
83
+ ```
84
+
85
+ Copies the official Antemortem template with YAML frontmatter (`name`, `date`, `scope`, `reversibility`, `status: draft`). Output path: `./antemortem/<name>.md`.
86
+
87
+ ### `antemortem run` — LLM-assisted classification
88
+
89
+ ```bash
90
+ antemortem run antemortem/my-feature.md --repo ../target-repo
91
+ # Reading 6 files from ../target-repo ...
92
+ # Calling claude-opus-4-7 (cached system prompt, 4.2k input / 1.1k output) ...
93
+ # Classified 7 traps: 3 GHOST, 3 REAL, 1 NEW
94
+ # Citations written with file:line references.
95
+ # Updated: antemortem/my-feature.md
96
+ # Artifact: antemortem/my-feature.json
97
+ ```
98
+
99
+ Reads the doc, extracts spec + traps + `files_to_read`, loads files from `--repo`, calls Claude Opus 4.7 with prompt caching on the frozen system prompt (~90% cost reduction on repeated runs), writes classifications back with `file:line` citations. Also emits a JSON artifact for `lint` and downstream tooling.
100
+
101
+ ### `antemortem lint` — validate the schema and citations
102
+
103
+ ```bash
104
+ antemortem lint antemortem/my-feature.md
105
+ # PASS — 7/7 traps classified, 7/7 citations present, 7/7 citations verify file:line.
106
+
107
+ antemortem lint antemortem/broken.md
108
+ # FAIL:
109
+ # - trap#3: classification missing
110
+ # - trap#5: citation format invalid ("see walk_forward.py" → expected "path:line")
111
+ # - trap#6: cited file antemortem/ghost.py does not exist in --repo
112
+ # exit 1
113
+ ```
114
+
115
+ Validates structured schema (all sections present, classifications complete) and verifies every `file:line` exists on disk. Exit 0 on pass, 1 on fail. Use in CI to block PRs with missing or fabricated citations.
116
+
117
+ ## Model and cost
118
+
119
+ Uses Claude Opus 4.7 via the Anthropic SDK. Typical cost per `run`:
120
+
121
+ | Scenario | Cost |
122
+ |---|---|
123
+ | First run (writes system prompt to cache) | ~$0.18 |
124
+ | Cached run (system prompt + files reused within 5 min) | ~$0.11 |
125
+ | 100 iterations during development | $11–18 |
126
+
127
+ Cache miss indicators are surfaced in the CLI output so silent invalidators (e.g. non-deterministic prompt state) fail loud, not silent.
128
+
129
+ ## Why a CLI (and not just "ask Claude to review my plan")
130
+
131
+ The discipline is two guardrails:
132
+
133
+ 1. **Enumerate traps before the LLM sees them.** Prevents anchoring on the model's framing. The CLI surfaces this as a required section in the scaffolded document.
134
+ 2. **Require `file:line` citations.** Prevents accepting the model's vibes. The CLI enforces this as a Pydantic schema field, and `lint` verifies the citations exist on disk (no hallucinated line numbers).
135
+
136
+ Without these guardrails, you have traded one form of hand-waving for another.
137
+
138
+ ## Project status
139
+
140
+ v0.2.0 is the **initial release**. Alpha — API and output formats may change in v0.2.x as prompts iterate on real repos. Semver applies after v1.0.
141
+
142
+ Full changelog: [CHANGELOG.md](CHANGELOG.md).
143
+
144
+ ## Relation to other projects
145
+
146
+ - [`Antemortem`](https://github.com/hibou04-ops/Antemortem) — the methodology, templates, and case studies. This CLI implements the `docs/methodology.md` protocol.
147
+ - [`omega-lock`](https://github.com/hibou04-ops/omega-lock) — first shipped case study of the discipline (audit submodule built with a 15-minute antemortem recon). Cited in Antemortem v0.1's case studies.
148
+
149
+ ## License
150
+
151
+ MIT. See [LICENSE](LICENSE).
152
+
153
+ ## Citing
154
+
155
+ ```
156
+ Antemortem CLI v0.2 — tooling for the Antemortem pre-implementation reconnaissance discipline.
157
+ https://github.com/hibou04-ops/antemortem-cli, 2026.
158
+ ```
@@ -0,0 +1,107 @@
1
+ # antemortem-cli
2
+
3
+ ![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)
4
+ ![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)
5
+ ![Version](https://img.shields.io/badge/version-0.2.0-blue)
6
+ ![Status](https://img.shields.io/badge/status-alpha-orange)
7
+
8
+ *CLI for the [Antemortem](https://github.com/hibou04-ops/Antemortem) pre-implementation reconnaissance discipline.*
9
+
10
+ An antemortem is what you do before you build. You put the planned change under stress *on paper*, use an LLM to read the existing code thoroughly, enumerate traps, classify each as REAL / GHOST / NEW with primary-source `file:line` citations, and revise your risk and your spec before writing a single line. This CLI automates the scaffolding, the classification pass, and the schema lint — so the discipline is a single command, not a workflow you have to remember.
11
+
12
+ The methodology lives at [hibou04-ops/Antemortem](https://github.com/hibou04-ops/Antemortem). This repo is the tooling that runs it.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install antemortem
18
+ ```
19
+
20
+ Requires Python 3.11+ and an `ANTHROPIC_API_KEY` environment variable for `run`.
21
+
22
+ ## Three commands
23
+
24
+ ### `antemortem init` — scaffold a document
25
+
26
+ ```bash
27
+ antemortem init my-feature
28
+ # → Created antemortem/my-feature.md (basic template)
29
+
30
+ antemortem init my-migration --enhanced
31
+ # → Created antemortem/my-migration.md (enhanced template)
32
+ ```
33
+
34
+ Copies the official Antemortem template with YAML frontmatter (`name`, `date`, `scope`, `reversibility`, `status: draft`). Output path: `./antemortem/<name>.md`.
35
+
36
+ ### `antemortem run` — LLM-assisted classification
37
+
38
+ ```bash
39
+ antemortem run antemortem/my-feature.md --repo ../target-repo
40
+ # Reading 6 files from ../target-repo ...
41
+ # Calling claude-opus-4-7 (cached system prompt, 4.2k input / 1.1k output) ...
42
+ # Classified 7 traps: 3 GHOST, 3 REAL, 1 NEW
43
+ # Citations written with file:line references.
44
+ # Updated: antemortem/my-feature.md
45
+ # Artifact: antemortem/my-feature.json
46
+ ```
47
+
48
+ Reads the doc, extracts spec + traps + `files_to_read`, loads files from `--repo`, calls Claude Opus 4.7 with prompt caching on the frozen system prompt (~90% cost reduction on repeated runs), writes classifications back with `file:line` citations. Also emits a JSON artifact for `lint` and downstream tooling.
49
+
50
+ ### `antemortem lint` — validate the schema and citations
51
+
52
+ ```bash
53
+ antemortem lint antemortem/my-feature.md
54
+ # PASS — 7/7 traps classified, 7/7 citations present, 7/7 citations verify file:line.
55
+
56
+ antemortem lint antemortem/broken.md
57
+ # FAIL:
58
+ # - trap#3: classification missing
59
+ # - trap#5: citation format invalid ("see walk_forward.py" → expected "path:line")
60
+ # - trap#6: cited file antemortem/ghost.py does not exist in --repo
61
+ # exit 1
62
+ ```
63
+
64
+ Validates structured schema (all sections present, classifications complete) and verifies every `file:line` exists on disk. Exit 0 on pass, 1 on fail. Use in CI to block PRs with missing or fabricated citations.
65
+
66
+ ## Model and cost
67
+
68
+ Uses Claude Opus 4.7 via the Anthropic SDK. Typical cost per `run`:
69
+
70
+ | Scenario | Cost |
71
+ |---|---|
72
+ | First run (writes system prompt to cache) | ~$0.18 |
73
+ | Cached run (system prompt + files reused within 5 min) | ~$0.11 |
74
+ | 100 iterations during development | $11–18 |
75
+
76
+ Cache miss indicators are surfaced in the CLI output so silent invalidators (e.g. non-deterministic prompt state) fail loud, not silent.
77
+
78
+ ## Why a CLI (and not just "ask Claude to review my plan")
79
+
80
+ The discipline is two guardrails:
81
+
82
+ 1. **Enumerate traps before the LLM sees them.** Prevents anchoring on the model's framing. The CLI surfaces this as a required section in the scaffolded document.
83
+ 2. **Require `file:line` citations.** Prevents accepting the model's vibes. The CLI enforces this as a Pydantic schema field, and `lint` verifies the citations exist on disk (no hallucinated line numbers).
84
+
85
+ Without these guardrails, you have traded one form of hand-waving for another.
86
+
87
+ ## Project status
88
+
89
+ v0.2.0 is the **initial release**. Alpha — API and output formats may change in v0.2.x as prompts iterate on real repos. Semver applies after v1.0.
90
+
91
+ Full changelog: [CHANGELOG.md](CHANGELOG.md).
92
+
93
+ ## Relation to other projects
94
+
95
+ - [`Antemortem`](https://github.com/hibou04-ops/Antemortem) — the methodology, templates, and case studies. This CLI implements the `docs/methodology.md` protocol.
96
+ - [`omega-lock`](https://github.com/hibou04-ops/omega-lock) — first shipped case study of the discipline (audit submodule built with a 15-minute antemortem recon). Cited in Antemortem v0.1's case studies.
97
+
98
+ ## License
99
+
100
+ MIT. See [LICENSE](LICENSE).
101
+
102
+ ## Citing
103
+
104
+ ```
105
+ Antemortem CLI v0.2 — tooling for the Antemortem pre-implementation reconnaissance discipline.
106
+ https://github.com/hibou04-ops/antemortem-cli, 2026.
107
+ ```
@@ -0,0 +1,80 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "antemortem"
7
+ version = "0.2.0"
8
+ description = "CLI for the Antemortem pre-implementation reconnaissance discipline — scaffold, run, and lint antemortem documents with Claude."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { file = "LICENSE" }
12
+ authors = [
13
+ { name = "hibou04-ops" },
14
+ ]
15
+ keywords = [
16
+ "antemortem",
17
+ "reconnaissance",
18
+ "pre-mortem",
19
+ "risk-classification",
20
+ "claude",
21
+ "anthropic",
22
+ "code-review",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Environment :: Console",
27
+ "Intended Audience :: Developers",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Software Development :: Quality Assurance",
34
+ ]
35
+ dependencies = [
36
+ "typer>=0.12.0",
37
+ "pydantic>=2.6.0",
38
+ "anthropic>=0.40.0",
39
+ "python-frontmatter>=1.1.0",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "pytest>=8.0.0",
45
+ "pytest-cov>=4.1.0",
46
+ ]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/hibou04-ops/antemortem-cli"
50
+ Repository = "https://github.com/hibou04-ops/antemortem-cli"
51
+ Methodology = "https://github.com/hibou04-ops/Antemortem"
52
+ Issues = "https://github.com/hibou04-ops/antemortem-cli/issues"
53
+
54
+ [project.scripts]
55
+ antemortem = "antemortem.cli:app"
56
+
57
+ [tool.hatch.build.targets.wheel]
58
+ packages = ["src/antemortem"]
59
+
60
+ [tool.hatch.build.targets.sdist]
61
+ include = [
62
+ "src/antemortem",
63
+ "README.md",
64
+ "LICENSE",
65
+ "CHANGELOG.md",
66
+ "pyproject.toml",
67
+ ]
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ python_files = ["test_*.py"]
72
+ addopts = "-ra -q"
73
+
74
+ [tool.ruff]
75
+ line-length = 100
76
+ target-version = "py311"
77
+
78
+ [tool.ruff.lint]
79
+ select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM"]
80
+ ignore = ["E501"]
@@ -0,0 +1,6 @@
1
+ """Antemortem CLI — tooling for the Antemortem pre-implementation reconnaissance discipline.
2
+
3
+ See https://github.com/hibou04-ops/Antemortem for the methodology.
4
+ """
5
+
6
+ __version__ = "0.2.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m antemortem`."""
2
+
3
+ from antemortem.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,159 @@
1
+ """Anthropic Claude API wrapper for the classification step.
2
+
3
+ The ``run_classification`` function is the single boundary between the CLI
4
+ and the Anthropic SDK. Everything else in the package is framework-free and
5
+ testable without the network. Tests mock the ``client`` argument.
6
+
7
+ Caching strategy:
8
+ - The system prompt is rendered with ``cache_control={"type": "ephemeral"}``
9
+ on a top-level system text block. On Opus 4.7 this requires the prompt to
10
+ exceed the 4096-token cacheable-prefix minimum; we size ``SYSTEM_PROMPT``
11
+ accordingly and verify via ``usage.cache_read_input_tokens`` on each call.
12
+ - The user payload carries no caching control — it's volatile (different
13
+ traps each run). A second breakpoint on the files block is a v0.2.1
14
+ optimization when we add iterative-run UX.
15
+
16
+ Model and sampling:
17
+ - ``claude-opus-4-7`` is the only supported model in v0.2 (matches Antemortem
18
+ discipline + enforces a known behavioral contract for the prompt).
19
+ - No ``temperature`` / ``top_p`` / ``top_k`` — removed on Opus 4.7.
20
+ - ``thinking={"type": "adaptive"}`` — off by default on 4.7; explicitly
21
+ enabled because classification benefits from multi-file chain tracing.
22
+ - ``output_config={"effort": "high"}`` — minimum recommended for
23
+ intelligence-sensitive work per Anthropic's migration guide.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any, Protocol
29
+
30
+ from antemortem.prompts import SYSTEM_PROMPT
31
+ from antemortem.schema import AntemortemOutput
32
+
33
+ MODEL = "claude-opus-4-7"
34
+ DEFAULT_MAX_TOKENS = 16000
35
+
36
+
37
+ class _MessagesNamespace(Protocol):
38
+ """Duck-typed interface for the subset of ``anthropic.messages`` we use.
39
+
40
+ Accepting a Protocol rather than the concrete class keeps the hot path
41
+ testable without importing ``anthropic`` in unit tests.
42
+ """
43
+
44
+ def parse(self, **kwargs: Any) -> Any: ... # noqa: D401
45
+
46
+
47
+ class _AnthropicLike(Protocol):
48
+ """Minimal interface we need from an Anthropic client instance."""
49
+
50
+ messages: _MessagesNamespace
51
+
52
+
53
+ def _build_user_content(
54
+ spec: str,
55
+ traps_table_md: str,
56
+ files: list[tuple[str, str]],
57
+ ) -> str:
58
+ """Render the user-turn payload as the prompt expects."""
59
+ file_blocks: list[str] = []
60
+ for path, content in sorted(files, key=lambda item: item[0]):
61
+ # Normalize path separators so cache keys don't drift on Windows.
62
+ normalized = path.replace("\\", "/")
63
+ file_blocks.append(f'<file path="{normalized}">\n{content}\n</file>')
64
+ files_section = "\n".join(file_blocks)
65
+ return (
66
+ f"<files>\n{files_section}\n</files>\n\n"
67
+ f"<spec>\n{spec.strip()}\n</spec>\n\n"
68
+ f"<traps>\n{traps_table_md.strip()}\n</traps>"
69
+ )
70
+
71
+
72
+ def _usage_to_dict(usage: Any) -> dict[str, int]:
73
+ """Extract token counts from the SDK's usage object into a plain dict."""
74
+ return {
75
+ "input_tokens": getattr(usage, "input_tokens", 0) or 0,
76
+ "output_tokens": getattr(usage, "output_tokens", 0) or 0,
77
+ "cache_creation_input_tokens": getattr(usage, "cache_creation_input_tokens", 0) or 0,
78
+ "cache_read_input_tokens": getattr(usage, "cache_read_input_tokens", 0) or 0,
79
+ }
80
+
81
+
82
+ def run_classification(
83
+ client: _AnthropicLike,
84
+ spec: str,
85
+ traps_table_md: str,
86
+ files: list[tuple[str, str]],
87
+ *,
88
+ max_tokens: int = DEFAULT_MAX_TOKENS,
89
+ ) -> tuple[AntemortemOutput, dict[str, int]]:
90
+ """Call Claude Opus 4.7 to classify traps against provided files.
91
+
92
+ Parameters
93
+ ----------
94
+ client:
95
+ An ``anthropic.Anthropic`` instance (or duck-typed equivalent for
96
+ tests).
97
+ spec:
98
+ Text of the change description from the antemortem document.
99
+ traps_table_md:
100
+ The pre-recon Traps table as a markdown string (raw, including
101
+ header row).
102
+ files:
103
+ List of ``(path, content)`` pairs. Sorted internally so cache
104
+ behavior is deterministic regardless of caller ordering.
105
+ max_tokens:
106
+ Upper bound on output tokens. Defaults to 16000 — the ~8k-output
107
+ typical classification response has ample headroom.
108
+
109
+ Returns
110
+ -------
111
+ A tuple ``(output, usage)``:
112
+
113
+ - ``output``: a validated ``AntemortemOutput`` instance.
114
+ - ``usage``: ``{"input_tokens", "output_tokens", "cache_creation_input_tokens", "cache_read_input_tokens"}``.
115
+ """
116
+ user_content = _build_user_content(spec, traps_table_md, files)
117
+
118
+ response = client.messages.parse(
119
+ model=MODEL,
120
+ max_tokens=max_tokens,
121
+ thinking={"type": "adaptive"},
122
+ output_config={"effort": "high"},
123
+ system=[
124
+ {
125
+ "type": "text",
126
+ "text": SYSTEM_PROMPT,
127
+ "cache_control": {"type": "ephemeral"},
128
+ }
129
+ ],
130
+ messages=[{"role": "user", "content": user_content}],
131
+ output_format=AntemortemOutput,
132
+ )
133
+
134
+ stop_reason = getattr(response, "stop_reason", None)
135
+ if stop_reason == "refusal":
136
+ text = ""
137
+ for block in getattr(response, "content", []) or []:
138
+ if getattr(block, "type", None) == "text":
139
+ text = getattr(block, "text", "")
140
+ break
141
+ raise RuntimeError(
142
+ "Claude refused the classification request. This usually means the "
143
+ "spec or traps contain content flagged by safety filters. "
144
+ f"Response text: {text!r}"
145
+ )
146
+
147
+ parsed: AntemortemOutput | None = getattr(response, "parsed_output", None)
148
+ if parsed is None:
149
+ raise RuntimeError(
150
+ "SDK returned no parsed_output. This indicates a schema mismatch or "
151
+ "a malformed response. Raw stop_reason: "
152
+ f"{stop_reason!r}"
153
+ )
154
+ if not isinstance(parsed, AntemortemOutput):
155
+ # Some SDK versions may pass through a dict — coerce defensively.
156
+ parsed = AntemortemOutput.model_validate(parsed)
157
+
158
+ usage = _usage_to_dict(getattr(response, "usage", None))
159
+ return parsed, usage