aepkit 0.6.0__tar.gz → 0.7.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.
Files changed (47) hide show
  1. {aepkit-0.6.0 → aepkit-0.7.0}/CHANGELOG.md +58 -9
  2. {aepkit-0.6.0/src/aepkit.egg-info → aepkit-0.7.0}/PKG-INFO +26 -12
  3. {aepkit-0.6.0 → aepkit-0.7.0}/README.md +21 -8
  4. {aepkit-0.6.0 → aepkit-0.7.0}/SKILL.md +13 -7
  5. {aepkit-0.6.0 → aepkit-0.7.0}/pyproject.toml +9 -4
  6. {aepkit-0.6.0/src/aepkit/skill → aepkit-0.7.0}/references/autonomous-mode.md +5 -1
  7. {aepkit-0.6.0/src/aepkit/skill → aepkit-0.7.0}/references/failure-modes-and-example.md +1 -1
  8. {aepkit-0.6.0/src/aepkit/skill → aepkit-0.7.0}/references/handoffs.md +1 -1
  9. aepkit-0.7.0/src/aepkit/__init__.py +1 -0
  10. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/cli.py +17 -7
  11. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/install_skill.py +1 -1
  12. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/lint.py +26 -19
  13. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/negcontrol.py +23 -6
  14. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/scaffold.py +4 -0
  15. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/skill/SKILL.md +13 -7
  16. {aepkit-0.6.0 → aepkit-0.7.0/src/aepkit/skill}/references/autonomous-mode.md +5 -1
  17. {aepkit-0.6.0 → aepkit-0.7.0/src/aepkit/skill}/references/failure-modes-and-example.md +1 -1
  18. {aepkit-0.6.0 → aepkit-0.7.0/src/aepkit/skill}/references/handoffs.md +1 -1
  19. aepkit-0.7.0/src/aepkit/status.py +314 -0
  20. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/templates/execution-guide.md +6 -3
  21. {aepkit-0.6.0 → aepkit-0.7.0/src/aepkit.egg-info}/PKG-INFO +26 -12
  22. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit.egg-info/requires.txt +4 -3
  23. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_install_skill.py +39 -40
  24. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_lint.py +87 -0
  25. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_negcontrol.py +42 -1
  26. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_scaffold.py +8 -0
  27. aepkit-0.7.0/tests/test_status.py +322 -0
  28. aepkit-0.6.0/src/aepkit/__init__.py +0 -1
  29. aepkit-0.6.0/src/aepkit/status.py +0 -122
  30. aepkit-0.6.0/tests/test_status.py +0 -112
  31. {aepkit-0.6.0 → aepkit-0.7.0}/LICENSE +0 -0
  32. {aepkit-0.6.0 → aepkit-0.7.0}/MANIFEST.in +0 -0
  33. {aepkit-0.6.0 → aepkit-0.7.0}/references/tasks-and-gates.md +0 -0
  34. {aepkit-0.6.0 → aepkit-0.7.0}/setup.cfg +0 -0
  35. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/__main__.py +0 -0
  36. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/py.typed +0 -0
  37. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/skill/references/tasks-and-gates.md +0 -0
  38. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/templates/compliance-log.md +0 -0
  39. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit/templates/plan-of-record.md +0 -0
  40. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit.egg-info/SOURCES.txt +0 -0
  41. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit.egg-info/dependency_links.txt +0 -0
  42. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit.egg-info/entry_points.txt +0 -0
  43. {aepkit-0.6.0 → aepkit-0.7.0}/src/aepkit.egg-info/top_level.txt +0 -0
  44. {aepkit-0.6.0 → aepkit-0.7.0}/tests/run.py +0 -0
  45. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_cli.py +0 -0
  46. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_readme_claims.py +0 -0
  47. {aepkit-0.6.0 → aepkit-0.7.0}/tests/test_skill_sync.py +0 -0
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-11-08
11
+
12
+ ### Added
13
+ - `aepkit status --json` prints the status reading as one JSON object: each ID with its
14
+ phase, kind, effective state, verdict, latest report, executor (a status board column
15
+ named `Executor`, `Worker`, `Agent`, `Owner` or `Assignee`) and any board/header
16
+ conflict; the phases with their titles from `execution-guide.md` and the plan's phase
17
+ table, an optional `Weight` column, and phases the guide has not broken down; IDs the
18
+ guide lists that the log has no entry for yet, marked `inLog: false`. The text output
19
+ is unchanged.
20
+ - `aeprogress`, an optional Claude Code mod in `integrations/claude-code/aeprogress/`:
21
+ a pane with the run's progress per phase, who is working on which ID (Claude Code
22
+ subagents, Orca workers, or any harness through a command), what awaits audit and what
23
+ needs attention, opened with `/aeprogress`. `.claude-plugin/marketplace.json` makes the
24
+ repository installable with
25
+ `/plugin install aeprogress --marketplace tBeltty/auditor-executor-protocol`.
26
+
27
+ ### Fixed
28
+ - `SKILL.md`, `references/handoffs.md`, `references/autonomous-mode.md` and
29
+ `references/failure-modes-and-example.md` spoke of "a plain `PASS`" on one task. `PASS`
30
+ is not a verdict (the verdicts are `APPROVED`, `CONDITIONAL` and `REJECTED`), and a log
31
+ that used it showed the task as awaiting audit in `aepkit status`. The four sentences
32
+ now say `APPROVED`.
33
+
10
34
  ## [0.6.0] - 2026-10-24
11
35
 
12
36
  ### Added
@@ -29,6 +53,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
29
53
  (`pipx install aepkit`, `aepkit`, `python -m aepkit`) to eliminate naming collisions with
30
54
  external `auditkit` packages and binaries.
31
55
 
56
+ ## [0.5.1] - 2026-10-04
57
+
58
+ ### Fixed
59
+ - `negcontrol --file` without `--break-cmd` is now a usage error (exit 2). The backup was taken
60
+ of a file already broken by hand, so a correct `--restore-cmd` was overwritten with the broken
61
+ copy and blamed for it.
62
+ - `negcontrol` fails the control when the test times out with the protection removed: a hang is
63
+ not a caught break. It used to print `OK` with a warning.
64
+ - `negcontrol --file <directory>` and `auditkit init <existing file>` print an error and exit 2
65
+ instead of crashing with a traceback.
66
+ - The execution guide's reporting format puts the verify output in a fenced block, so a report
67
+ written as the template shows no longer fails `lint`.
68
+ - `lint` no longer cuts a report short at a `#`, `---` or `**Field:**` line inside pasted output.
69
+ - `lint`'s phase-order check reads verdicts the way `status` does: the latest report header wins,
70
+ and a status board verdict that disagrees with it is a conflict, not an approval.
71
+ - `install-skill` auto-detects Claude Code through `CLAUDECODE`, the variable Claude Code sets.
72
+ - The install-skill tests no longer depend on the agent environment they run in.
73
+
74
+ ### Changed
75
+ - The release workflow runs the full CI before building and publishing.
76
+ - CI pins ruff, mypy, build and twine through the `dev` extra, and smoke-tests the built wheel in
77
+ a clean environment.
78
+ - README links are absolute, so they work on PyPI; `auditkit --help` and the installed skill
79
+ link to GitHub instead of "the repo root".
80
+ - `--annex-threshold` is documented as a total with a per-phase breakdown, which is what it
81
+ checks.
82
+ - `SECURITY.md` lists 0.5.x as supported; `CONTRIBUTING.md` documents the release steps.
83
+
32
84
  ## [0.5.0] - 2026-10-02
33
85
 
34
86
  ### Added
@@ -85,9 +137,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
85
137
  requires Python 3.11+.
86
138
 
87
139
  ### Fixed
88
- - `AGENTS.md` imported itself and said nothing, because `GEMINI.md` was a symlink to it. It is
89
- now a short, generic agent guide that points to `CONTRIBUTING.md`; tool-specific instruction
90
- files (`CLAUDE.md`, `GEMINI.md`) are no longer tracked.
91
140
  - README: the compliance log template, `install-skill` detection and `--global`/`--dest`, every
92
141
  command's options, exit codes, and the BSD-only `sed -i ''` in the `negcontrol` example now match
93
142
  the CLI.
@@ -193,7 +242,7 @@ Fixes from an independent review of 0.3.0.
193
242
  field is missing, empty, or only a template placeholder. The protocol already records
194
243
  these as `FAILED`; the check makes that mechanical.
195
244
  - README: install `auditkit` without cloning via `pipx install git+https://github.com/tBeltty/auditor-executor-protocol`.
196
- - Conventional Commits specification documented in `CONTRIBUTING.md` and enforced in `AGENTS.md`.
245
+ - Conventional Commits specification documented in `CONTRIBUTING.md`.
197
246
  - `SKILL.md`: optional Guided/Autonomous operating mode, with the autonomy charter, per-task
198
247
  model-tier assignment, the per-task cycle, and an Auditor-brief template for starting an
199
248
  autonomous run.
@@ -218,8 +267,6 @@ Fixes from an independent review of 0.3.0.
218
267
  - `auditkit install-skill` copies `references/` beside `SKILL.md`. Destinations that hold a
219
268
  single file (Cursor `.mdc`, or a `--dest` not named `SKILL.md`) get one bundled file.
220
269
  - The packaged skill moved from `src/auditkit/templates/SKILL.md` to `src/auditkit/skill/`.
221
- - `GEMINI.md` imports `AGENTS.md` instead of duplicating it, so the agent rules have one
222
- source of truth.
223
270
  - Bumped `actions/checkout` and `actions/setup-python` to v7 in GitHub Actions CI workflow.
224
271
  - Configured grouped updates in `.github/dependabot.yml` to bundle action and pip dependency bumps.
225
272
 
@@ -250,7 +297,6 @@ Fixes from an independent review of 0.3.0.
250
297
  - 90%+ test coverage enforcement across the repository.
251
298
  - GitHub Actions CI workflow hardening: explicit token permissions, dedicated lint/format/typecheck job, package distribution verification, and weekly Dependabot configuration.
252
299
  - Open-source governance files: `SECURITY.md`, `CONTRIBUTING.md`, `CHANGELOG.md`, PR and issue templates.
253
- - Agent rule specifications (`AGENTS.md`, `GEMINI.md`) establishing mandatory changelog updates and test verification gates on every task.
254
300
 
255
301
  ### Changed
256
302
  - Conformed all Python code to Ruff formatting and PEP 8 import conventions.
@@ -269,8 +315,10 @@ Fixes from an independent review of 0.3.0.
269
315
  - Multi-framework agent installation support for Antigravity, Claude Code, and Cursor.
270
316
  - Zero-dependency test runner (`tests/run.py`).
271
317
 
272
- [Unreleased]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.6.0...HEAD
273
- [0.6.0]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.5.0...v0.6.0
318
+ [Unreleased]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.7.0...HEAD
319
+ [0.7.0]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.6.0...v0.7.0
320
+ [0.6.0]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.5.1...v0.6.0
321
+ [0.5.1]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.5.0...v0.5.1
274
322
  [0.5.0]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.4.0...v0.5.0
275
323
  [0.4.0]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.3.9...v0.4.0
276
324
  [0.3.9]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.3.8...v0.3.9
@@ -284,3 +332,4 @@ Fixes from an independent review of 0.3.0.
284
332
  [0.3.1]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.3.0...v0.3.1
285
333
  [0.3.0]: https://github.com/tBeltty/auditor-executor-protocol/compare/v0.2.0...v0.3.0
286
334
  [0.2.0]: https://github.com/tBeltty/auditor-executor-protocol/releases/tag/v0.2.0
335
+ [0.1.0]: https://github.com/tBeltty/auditor-executor-protocol/tree/7e9701cdacf732fa63b28428eedd9dcc7250fa2f
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aepkit
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Mechanical support for the Auditor/Executor protocol: scaffold, lint, and negative-control the document set for a multi-phase, multi-agent execution.
5
5
  Author-email: tBeltty <jhonatan@tbelt.online>
6
6
  License-Expression: MIT
@@ -30,9 +30,10 @@ Requires-Dist: pytest-cov>=4.0; extra == "test"
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: pytest>=7.0; extra == "dev"
32
32
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
33
- Requires-Dist: ruff>=0.1.0; extra == "dev"
34
- Requires-Dist: mypy>=1.0.0; extra == "dev"
35
- Requires-Dist: build>=1.0.0; extra == "dev"
33
+ Requires-Dist: ruff==0.16.10; extra == "dev"
34
+ Requires-Dist: mypy==2.4.0; extra == "dev"
35
+ Requires-Dist: build==1.6.1; extra == "dev"
36
+ Requires-Dist: twine==7.0.0; extra == "dev"
36
37
  Dynamic: license-file
37
38
 
38
39
  # Auditor/Executor Protocol
@@ -43,13 +44,13 @@ Dynamic: license-file
43
44
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
45
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
45
46
  [![Checked with mypy](https://img.shields.io/badge/mypy-strict-blue)](https://mypy-lang.org/)
46
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](pyproject.toml)
47
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/tBeltty/auditor-executor-protocol/blob/main/pyproject.toml)
47
48
 
48
49
  A two-role protocol for running multi-phase work through AI agents without the plan drifting into open-ended discussion and without "all tests pass" masquerading as verification.
49
50
 
50
51
  The **Auditor** defines what "done" means and proves it independently. The **Executor** implements one numbered task at a time and logs command output. Neither role crosses into the other.
51
52
 
52
- [`SKILL.md`](SKILL.md) specifies the protocol core; [`references/`](references/) holds the parts an agent loads only when it reaches that step (task and gate writing, handoff templates, Autonomous mode, failure modes and a worked example). `aepkit` is a zero-dependency Python CLI that automates scaffolding, drift linting, negative controls, status tallies, and installing the skill into your agent.
53
+ [`SKILL.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/SKILL.md) specifies the protocol core; [`references/`](https://github.com/tBeltty/auditor-executor-protocol/tree/main/references) holds the parts an agent loads only when it reaches that step (task and gate writing, handoff templates, Autonomous mode, failure modes and a worked example). `aepkit` is a zero-dependency Python CLI that automates scaffolding, drift linting, negative controls, status tallies, and installing the skill into your agent.
53
54
 
54
55
  ---
55
56
 
@@ -147,13 +148,15 @@ Inspect open tasks and tallied verdicts:
147
148
  aepkit status docs/<task-name>
148
149
  ```
149
150
 
151
+ `aepkit status --json docs/<task-name>` prints the same reading as JSON, with each ID's phase, effective state and executor, and the phases with their titles from `execution-guide.md` (and weights from an optional `Weight` column in the plan's phase table), for scripts and tools to use instead of parsing the log.
152
+
150
153
  Cross-check document consistency:
151
154
 
152
155
  ```bash
153
156
  aepkit lint docs/<task-name>
154
157
  ```
155
158
 
156
- `aepkit lint` catches an empty guide, tasks without a report line, missing report entries, log entries with no matching task, `DONE` reports whose verify output is not pasted in a code block, gates without a stated negative control (hypothetical or waived ones do not count), work reported in a phase before every earlier phase is `APPROVED`, unfilled opportunity/shaping/non-goal fields in `plan-of-record.md` once work is reported `DONE` or audited, open rows in the deferred items ledger for a phase being closed, duplicated log paragraphs, and annex buildup past `--annex-threshold` (default 6, overall or per phase). It checks that evidence is present and in the right form, not that it is genuine; the Auditor's re-run does that.
159
+ `aepkit lint` catches an empty guide, tasks without a report line, missing report entries, log entries with no matching task, `DONE` reports whose verify output is not pasted in a code block, gates without a stated negative control (hypothetical or waived ones do not count), work reported in a phase before every earlier phase is `APPROVED`, unfilled opportunity/shaping/non-goal fields in `plan-of-record.md` once work is reported `DONE` or audited, open rows in the deferred items ledger for a phase being closed, duplicated log paragraphs, and annex buildup past `--annex-threshold` (default 6 in total, with a per-phase breakdown). It checks that evidence is present and in the right form, not that it is genuine; the Auditor's re-run does that.
157
160
 
158
161
  ### 5. Run a Negative Control
159
162
 
@@ -169,9 +172,20 @@ aepkit negcontrol \
169
172
 
170
173
  The `sed -i ''` form above is BSD/macOS sed; on GNU/Linux use `sed -i 's/.../.../' file`.
171
174
 
172
- `negcontrol` backs up the target file, applies the break mutation, confirms the test fails, restores the file with byte-level verification, confirms the test passes, and outputs a transcript ready for `compliance-log.md`. Without `--file`, pass `--restore-cmd` to undo the break yourself; with both, the backup still wins if the file is not byte-identical afterwards. `--timeout` limits each command in seconds.
175
+ `negcontrol` backs up the target file, applies the break mutation, confirms the test fails, restores the file with byte-level verification, confirms the test passes, and outputs a transcript ready for `compliance-log.md`. Without `--file`, pass `--restore-cmd` to undo the break yourself; with both, the backup still wins if the file is not byte-identical afterwards. `--file` needs `--break-cmd`, because the backup must be taken before the break. `--timeout` limits each command in seconds, and a test that times out instead of failing fails the control: a hang is not a caught break.
176
+
177
+ A failure only proves something if it is the failure you meant. A syntax error, a missing import or a mistyped test command also makes the test exit non-zero. `--expect` takes a regular expression that the failing run's output must match, and the control fails if it does not. Without `--expect`, output that looks like a syntax, import, collection or command-not-found error is flagged with a `WARNING` next to the `OK`, and you need to read the transcript to confirm the reason.
178
+
179
+ ### Optional: Progress Pane for Claude Code
180
+
181
+ `aeprogress` is a Claude Code mod that keeps a pane beside the chat with the run's overall percentage, a bar per phase, who is working on which task ID (Claude Code subagents, Orca workers, or any other harness through a command of your own), what awaits audit and what needs attention. It reads the log through `aepkit status --json` and refreshes after each turn, and every 20 seconds while the pane is open. The protocol does not depend on it.
182
+
183
+ ```
184
+ /plugin install aeprogress --marketplace tBeltty/auditor-executor-protocol
185
+ /aeprogress docs/<task-name>
186
+ ```
173
187
 
174
- A failure only proves something if it is the failure you meant. A syntax error, a missing import or a mistyped test command also makes the test exit non-zero. `--expect` takes a regular expression that the failing run's output must match, and the control fails if it does not. Without `--expect`, output that looks like a syntax, import, collection, timeout or command-not-found error is flagged with a `WARNING` next to the `OK`, and you need to read the transcript to confirm the reason.
188
+ It works in the terminal and in the desktop app's local sessions. Claude Code's mod API is early access, so a Claude Code release can break it. Setup, options and how the percentage is computed: [integrations/claude-code/aeprogress](https://github.com/tBeltty/auditor-executor-protocol/tree/main/integrations/claude-code/aeprogress).
175
189
 
176
190
  ---
177
191
 
@@ -183,7 +197,7 @@ A failure only proves something if it is the failure you meant. A syntax error,
183
197
  | `aepkit install-skill [dir]` | `--agent`, `--global`, `--dest`, `--force` | Provision `SKILL.md` and `references/` into Antigravity, Claude Code, or Cursor |
184
198
  | `aepkit lint <dir>` | `--annex-threshold` | Cross-check IDs both ways, flag `DONE` without evidence, detect missing negative controls, and flag log rot |
185
199
  | `aepkit negcontrol` | `--test-cmd` (required), `--file`, `--break-cmd`, `--restore-cmd`, `--expect`, `--timeout` | Run automated backup, break, fail, restore, and pass cycle |
186
- | `aepkit status <dir>` | | Combine status board verdicts with reports; list everything not `APPROVED` |
200
+ | `aepkit status <dir>` | `--json` | Combine status board verdicts with reports; list everything not `APPROVED` |
187
201
 
188
202
  `python -m aepkit` works the same as `aepkit`. Errors go to stderr.
189
203
 
@@ -212,11 +226,11 @@ pytest
212
226
 
213
227
  ## Contributing
214
228
 
215
- Contributions are welcome. [`CONTRIBUTING.md`](CONTRIBUTING.md) covers the development setup, the quality gates, and the PR checklist; participation follows the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). Report vulnerabilities privately as described in [`SECURITY.md`](SECURITY.md). Changes are listed in [`CHANGELOG.md`](CHANGELOG.md).
229
+ Contributions are welcome. [`CONTRIBUTING.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/CONTRIBUTING.md) covers the development setup, the quality gates, and the PR checklist; participation follows the [`CODE_OF_CONDUCT.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/CODE_OF_CONDUCT.md). Report vulnerabilities privately as described in [`SECURITY.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/SECURITY.md). Changes are listed in [`CHANGELOG.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/CHANGELOG.md).
216
230
 
217
231
  ## License
218
232
 
219
- MIT (see [`LICENSE`](LICENSE)).
233
+ MIT (see [`LICENSE`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/LICENSE)).
220
234
 
221
235
  ---
222
236
 
@@ -6,13 +6,13 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
8
8
  [![Checked with mypy](https://img.shields.io/badge/mypy-strict-blue)](https://mypy-lang.org/)
9
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](pyproject.toml)
9
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/tBeltty/auditor-executor-protocol/blob/main/pyproject.toml)
10
10
 
11
11
  A two-role protocol for running multi-phase work through AI agents without the plan drifting into open-ended discussion and without "all tests pass" masquerading as verification.
12
12
 
13
13
  The **Auditor** defines what "done" means and proves it independently. The **Executor** implements one numbered task at a time and logs command output. Neither role crosses into the other.
14
14
 
15
- [`SKILL.md`](SKILL.md) specifies the protocol core; [`references/`](references/) holds the parts an agent loads only when it reaches that step (task and gate writing, handoff templates, Autonomous mode, failure modes and a worked example). `aepkit` is a zero-dependency Python CLI that automates scaffolding, drift linting, negative controls, status tallies, and installing the skill into your agent.
15
+ [`SKILL.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/SKILL.md) specifies the protocol core; [`references/`](https://github.com/tBeltty/auditor-executor-protocol/tree/main/references) holds the parts an agent loads only when it reaches that step (task and gate writing, handoff templates, Autonomous mode, failure modes and a worked example). `aepkit` is a zero-dependency Python CLI that automates scaffolding, drift linting, negative controls, status tallies, and installing the skill into your agent.
16
16
 
17
17
  ---
18
18
 
@@ -110,13 +110,15 @@ Inspect open tasks and tallied verdicts:
110
110
  aepkit status docs/<task-name>
111
111
  ```
112
112
 
113
+ `aepkit status --json docs/<task-name>` prints the same reading as JSON, with each ID's phase, effective state and executor, and the phases with their titles from `execution-guide.md` (and weights from an optional `Weight` column in the plan's phase table), for scripts and tools to use instead of parsing the log.
114
+
113
115
  Cross-check document consistency:
114
116
 
115
117
  ```bash
116
118
  aepkit lint docs/<task-name>
117
119
  ```
118
120
 
119
- `aepkit lint` catches an empty guide, tasks without a report line, missing report entries, log entries with no matching task, `DONE` reports whose verify output is not pasted in a code block, gates without a stated negative control (hypothetical or waived ones do not count), work reported in a phase before every earlier phase is `APPROVED`, unfilled opportunity/shaping/non-goal fields in `plan-of-record.md` once work is reported `DONE` or audited, open rows in the deferred items ledger for a phase being closed, duplicated log paragraphs, and annex buildup past `--annex-threshold` (default 6, overall or per phase). It checks that evidence is present and in the right form, not that it is genuine; the Auditor's re-run does that.
121
+ `aepkit lint` catches an empty guide, tasks without a report line, missing report entries, log entries with no matching task, `DONE` reports whose verify output is not pasted in a code block, gates without a stated negative control (hypothetical or waived ones do not count), work reported in a phase before every earlier phase is `APPROVED`, unfilled opportunity/shaping/non-goal fields in `plan-of-record.md` once work is reported `DONE` or audited, open rows in the deferred items ledger for a phase being closed, duplicated log paragraphs, and annex buildup past `--annex-threshold` (default 6 in total, with a per-phase breakdown). It checks that evidence is present and in the right form, not that it is genuine; the Auditor's re-run does that.
120
122
 
121
123
  ### 5. Run a Negative Control
122
124
 
@@ -132,9 +134,20 @@ aepkit negcontrol \
132
134
 
133
135
  The `sed -i ''` form above is BSD/macOS sed; on GNU/Linux use `sed -i 's/.../.../' file`.
134
136
 
135
- `negcontrol` backs up the target file, applies the break mutation, confirms the test fails, restores the file with byte-level verification, confirms the test passes, and outputs a transcript ready for `compliance-log.md`. Without `--file`, pass `--restore-cmd` to undo the break yourself; with both, the backup still wins if the file is not byte-identical afterwards. `--timeout` limits each command in seconds.
137
+ `negcontrol` backs up the target file, applies the break mutation, confirms the test fails, restores the file with byte-level verification, confirms the test passes, and outputs a transcript ready for `compliance-log.md`. Without `--file`, pass `--restore-cmd` to undo the break yourself; with both, the backup still wins if the file is not byte-identical afterwards. `--file` needs `--break-cmd`, because the backup must be taken before the break. `--timeout` limits each command in seconds, and a test that times out instead of failing fails the control: a hang is not a caught break.
138
+
139
+ A failure only proves something if it is the failure you meant. A syntax error, a missing import or a mistyped test command also makes the test exit non-zero. `--expect` takes a regular expression that the failing run's output must match, and the control fails if it does not. Without `--expect`, output that looks like a syntax, import, collection or command-not-found error is flagged with a `WARNING` next to the `OK`, and you need to read the transcript to confirm the reason.
140
+
141
+ ### Optional: Progress Pane for Claude Code
142
+
143
+ `aeprogress` is a Claude Code mod that keeps a pane beside the chat with the run's overall percentage, a bar per phase, who is working on which task ID (Claude Code subagents, Orca workers, or any other harness through a command of your own), what awaits audit and what needs attention. It reads the log through `aepkit status --json` and refreshes after each turn, and every 20 seconds while the pane is open. The protocol does not depend on it.
144
+
145
+ ```
146
+ /plugin install aeprogress --marketplace tBeltty/auditor-executor-protocol
147
+ /aeprogress docs/<task-name>
148
+ ```
136
149
 
137
- A failure only proves something if it is the failure you meant. A syntax error, a missing import or a mistyped test command also makes the test exit non-zero. `--expect` takes a regular expression that the failing run's output must match, and the control fails if it does not. Without `--expect`, output that looks like a syntax, import, collection, timeout or command-not-found error is flagged with a `WARNING` next to the `OK`, and you need to read the transcript to confirm the reason.
150
+ It works in the terminal and in the desktop app's local sessions. Claude Code's mod API is early access, so a Claude Code release can break it. Setup, options and how the percentage is computed: [integrations/claude-code/aeprogress](https://github.com/tBeltty/auditor-executor-protocol/tree/main/integrations/claude-code/aeprogress).
138
151
 
139
152
  ---
140
153
 
@@ -146,7 +159,7 @@ A failure only proves something if it is the failure you meant. A syntax error,
146
159
  | `aepkit install-skill [dir]` | `--agent`, `--global`, `--dest`, `--force` | Provision `SKILL.md` and `references/` into Antigravity, Claude Code, or Cursor |
147
160
  | `aepkit lint <dir>` | `--annex-threshold` | Cross-check IDs both ways, flag `DONE` without evidence, detect missing negative controls, and flag log rot |
148
161
  | `aepkit negcontrol` | `--test-cmd` (required), `--file`, `--break-cmd`, `--restore-cmd`, `--expect`, `--timeout` | Run automated backup, break, fail, restore, and pass cycle |
149
- | `aepkit status <dir>` | | Combine status board verdicts with reports; list everything not `APPROVED` |
162
+ | `aepkit status <dir>` | `--json` | Combine status board verdicts with reports; list everything not `APPROVED` |
150
163
 
151
164
  `python -m aepkit` works the same as `aepkit`. Errors go to stderr.
152
165
 
@@ -175,11 +188,11 @@ pytest
175
188
 
176
189
  ## Contributing
177
190
 
178
- Contributions are welcome. [`CONTRIBUTING.md`](CONTRIBUTING.md) covers the development setup, the quality gates, and the PR checklist; participation follows the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). Report vulnerabilities privately as described in [`SECURITY.md`](SECURITY.md). Changes are listed in [`CHANGELOG.md`](CHANGELOG.md).
191
+ Contributions are welcome. [`CONTRIBUTING.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/CONTRIBUTING.md) covers the development setup, the quality gates, and the PR checklist; participation follows the [`CODE_OF_CONDUCT.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/CODE_OF_CONDUCT.md). Report vulnerabilities privately as described in [`SECURITY.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/SECURITY.md). Changes are listed in [`CHANGELOG.md`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/CHANGELOG.md).
179
192
 
180
193
  ## License
181
194
 
182
- MIT (see [`LICENSE`](LICENSE)).
195
+ MIT (see [`LICENSE`](https://github.com/tBeltty/auditor-executor-protocol/blob/main/LICENSE)).
183
196
 
184
197
  ---
185
198
 
@@ -188,7 +188,6 @@ done before task 1 is drafted:**
188
188
  | Rule | Look for |
189
189
  |---|---|
190
190
  | Version bump per change | A version field (`package.json`, `pyproject.toml`, a `VERSION` file) and any script that already bumps it |
191
- | No AI co-author trailers | `git log --grep="Co-Authored-By"` — existing trailers (or their consistent absence) |
192
191
  | Fetch-before-push | Multiple contributors or branches in `git log`/`git branch -a` — a solo, single-branch repo needs this less |
193
192
  | Quality gate green before push | CI config (`.github/workflows/`, `.gitlab-ci.yml`) or a `test`/`lint`/`build` script already wired together |
194
193
  | i18n/locale parity | A locales/translations directory with more than one language file |
@@ -299,7 +298,7 @@ report's job is to point at the evidence, not to argue for a verdict.
299
298
  for the whole feature.
300
299
  8. **Before sending the verdict, check what it's missing, not just what it says.** This
301
300
  applies to every verdict, not only a full-phase `APPROVED` or a `CONDITIONAL`/
302
- `REJECTED` with an obvious remediation — a plain `PASS` on one task, mid-phase, with
301
+ `REJECTED` with an obvious remediation — a plain `APPROVED` on one task, mid-phase, with
303
302
  something else already outstanding (an earlier remediation, the next task) needs the
304
303
  same discipline: it does not end the message on its own. If anything is left to do —
305
304
  a remediation handoff, the next task's handoff, or a re-statement of a handoff already
@@ -423,7 +422,7 @@ If `aepkit` is installed, prefer it over doing these by hand:
423
422
  compliance log in both directions, flag `DONE` reports with no pasted verify output
424
423
  (output counts only inside a fenced code block; prose such as "all tests pass" or a lone
425
424
  "n/a" is not output), flag work reported in a phase before every earlier phase is
426
- `APPROVED`, flag near-duplicate paragraphs, warn on annex count per phase, flag gates with no
425
+ `APPROVED`, flag near-duplicate paragraphs, warn on annex count (listed per phase), flag gates with no
427
426
  stated negative control (it must name the action that removes the protection and the
428
427
  failure that follows; a negated, deferred, hypothetical, or waived mention such as "n/a",
429
428
  "TBD", or "we could remove it" does not count), and—once work has started (`DONE` or a
@@ -437,12 +436,19 @@ If `aepkit` is installed, prefer it over doing these by hand:
437
436
  file up, runs the break command, runs the test (expects failure), restores from the
438
437
  backup, runs the test again (expects success), and prints a paste-ready transcript.
439
438
  With `--restore-cmd`, the file must still end byte-identical to the backup or it is
440
- restored from it; `--timeout` bounds each command. Pass `--expect "<regex>"` with the
439
+ restored from it. `--file` needs `--break-cmd`, since the backup must come before the
440
+ break. `--timeout` bounds each command, and a test that times out instead of failing
441
+ fails the control. Pass `--expect "<regex>"` with the
441
442
  failure the break should cause (an assertion message, a 403): a test that fails for
442
443
  another reason, such as a syntax error or a missing import, then fails the control
443
444
  instead of passing it. Without `--expect`, such output is only flagged as a warning.
444
445
  - `aepkit status <dir>` — combines the status board verdicts with the latest report
445
446
  per ID and prints what's still open. Only `APPROVED` closes an item; a `DONE` with no
446
- verdict is listed as awaiting audit.
447
-
448
- See the repo's `README.md` for install instructions.
447
+ verdict is listed as awaiting audit. `--json` prints the same reading, with the phases
448
+ from the guide and the plan, for tools to use instead of parsing the log themselves.
449
+ - Optional, Claude Code only: the `aeprogress` mod keeps a pane beside the chat with the
450
+ run's progress per phase and who is working on which ID (subagents, Orca workers, or
451
+ any harness through a command). The protocol does not depend on it. See
452
+ `integrations/claude-code/aeprogress/` in the repository.
453
+
454
+ Install instructions: https://github.com/tBeltty/auditor-executor-protocol#quickstart
@@ -45,12 +45,15 @@ test = [
45
45
  "pytest>=7.0",
46
46
  "pytest-cov>=4.0",
47
47
  ]
48
+ # Exact pins, so a new linter rule or build-tool release cannot turn CI red or change a
49
+ # published build without a commit; Dependabot bumps them.
48
50
  dev = [
49
51
  "pytest>=7.0",
50
52
  "pytest-cov>=4.0",
51
- "ruff>=0.1.0",
52
- "mypy>=1.0.0",
53
- "build>=1.0.0",
53
+ "ruff==0.16.10",
54
+ "mypy==2.4.0",
55
+ "build==1.6.1",
56
+ "twine==7.0.0",
54
57
  ]
55
58
 
56
59
  [project.scripts]
@@ -68,6 +71,8 @@ where = ["src"]
68
71
  [tool.ruff]
69
72
  line-length = 100
70
73
  target-version = "py311"
74
+ # agent-produced run directories and experiment scripts are data, not package code
75
+ extend-exclude = ["benchmarks"]
71
76
 
72
77
  [tool.ruff.lint]
73
78
  select = ["E", "F", "W", "I", "UP", "B", "PL", "RUF"]
@@ -82,7 +87,7 @@ ignore = [
82
87
  ]
83
88
 
84
89
  [tool.ruff.lint.per-file-ignores]
85
- "tests/*" = ["RUF001", "BLE001"]
90
+ "tests/*" = ["RUF001"]
86
91
 
87
92
  [tool.mypy]
88
93
  python_version = "3.11"
@@ -47,6 +47,10 @@ it from a governance decision in the plan of record. It has four parts:
47
47
  made: <decision-entry IDs, or "none">. Next: <phase/task already launched>.
48
48
  ```
49
49
 
50
+ Between reports, an owner on Claude Code can follow the run in the optional
51
+ `aeprogress` pane (`integrations/claude-code/aeprogress/` in the repository), which
52
+ reads the compliance log and never messages the Auditor. The run does not depend on it.
53
+
50
54
  Harness permission prompts that need the owner go in **one batched request**, not one
51
55
  at a time. When every phase is `APPROVED` and the plan of record declares no further
52
56
  phase, say so as its own line — see "Closing the run" in `SKILL.md`.
@@ -91,7 +95,7 @@ each handoff. The shape that has worked here:
91
95
  its first task.
92
96
 
93
97
  Do not end a turn between a verdict and the next launch unless something on the
94
- critical-risk list is open. A run that stops to report a `PASS` and wait is Guided
98
+ critical-risk list is open. A run that stops to report an `APPROVED` and wait is Guided
95
99
  mode by accident.
96
100
 
97
101
  ### Starting the Auditor (the Auditor brief)
@@ -23,7 +23,7 @@ this file.
23
23
  - **Silent model inheritance.** A subagent launched without an explicit model runs on
24
24
  the Auditor's own model. Nothing fails; the rule is just broken on every task. Pass
25
25
  it every time.
26
- - **Guided by habit.** In Autonomous mode, the Auditor ends a turn after a `PASS` to
26
+ - **Guided by habit.** In Autonomous mode, the Auditor ends a turn after an `APPROVED` to
27
27
  "report," or asks the owner a question the charter already lets it decide. Each one
28
28
  costs the owner a message and adds nothing.
29
29
  - **A defect ships and nobody owns re-checking its blast radius.** A task changes what
@@ -120,7 +120,7 @@ Auditor's own rule 4 ("The Auditor is bound by rule 4 too" in `SKILL.md`).
120
120
 
121
121
  **No verdict ends the loop by itself if anything is left to run — and "anything left to
122
122
  run" is not only "a phase remains" or "a remediation order is needed."** It also covers
123
- the most common case of all: a plain `PASS` on one task, mid-phase, with something else
123
+ the most common case of all: a plain `APPROVED` on one task, mid-phase, with something else
124
124
  already outstanding — an earlier remediation, the next task in line. That case has no
125
125
  name of its own in the Verdicts table of `SKILL.md`, which is exactly why it's the easiest one
126
126
  to ship without a handoff: closing with a status sentence that *describes* what's still
@@ -0,0 +1 @@
1
+ __version__ = "0.7.0"
@@ -1,9 +1,11 @@
1
1
  """aepkit — mechanical support for the Auditor/Executor protocol.
2
2
 
3
- See SKILL.md in the repo root for the protocol itself. This CLI operationalizes the
4
- parts of it that are checklist work, not judgment: scaffolding the document set,
5
- cross-checking it for drift, running a negative control end to end, and reporting
6
- status.
3
+ The protocol itself is SKILL.md:
4
+ https://github.com/tBeltty/auditor-executor-protocol/blob/main/SKILL.md
5
+
6
+ This CLI operationalizes the parts of it that are checklist work, not judgment:
7
+ scaffolding the document set, cross-checking it for drift, running a negative control
8
+ end to end, and reporting status.
7
9
  """
8
10
 
9
11
  from __future__ import annotations
@@ -15,7 +17,9 @@ from . import __version__, install_skill, lint, negcontrol, scaffold, status
15
17
 
16
18
 
17
19
  def build_parser() -> argparse.ArgumentParser:
18
- parser = argparse.ArgumentParser(prog="aepkit", description=__doc__)
20
+ parser = argparse.ArgumentParser(
21
+ prog="aepkit", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
22
+ )
19
23
  parser.add_argument("--version", action="version", version=f"aepkit {__version__}")
20
24
  sub = parser.add_subparsers(dest="command", required=True)
21
25
 
@@ -39,7 +43,7 @@ def build_parser() -> argparse.ArgumentParser:
39
43
  type=int,
40
44
  metavar="N",
41
45
  default=lint.DEFAULT_ANNEX_THRESHOLD,
42
- help="flag more annexes than this, overall or per phase (default: %(default)s)",
46
+ help="flag more annexes than this in total, listed per phase (default: %(default)s)",
43
47
  )
44
48
 
45
49
  p_neg = sub.add_parser("negcontrol", help="run a backup/break/test/restore/test cycle")
@@ -85,6 +89,12 @@ def build_parser() -> argparse.ArgumentParser:
85
89
  "status", help="tally verdicts and reports in the compliance log; only APPROVED closes"
86
90
  )
87
91
  p_status.add_argument("target_dir", help="directory holding compliance-log.md")
92
+ p_status.add_argument(
93
+ "--json",
94
+ dest="as_json",
95
+ action="store_true",
96
+ help="print the reading as JSON, with phases from the execution guide and the plan",
97
+ )
88
98
 
89
99
  p_install = sub.add_parser(
90
100
  "install-skill", help="provision SKILL.md and references/ into an agent skills directory"
@@ -137,7 +147,7 @@ def main(argv: list[str] | None = None) -> int:
137
147
  expect=args.expect,
138
148
  )
139
149
  if args.command == "status":
140
- return status.run(args.target_dir)
150
+ return status.run(args.target_dir, as_json=args.as_json)
141
151
  return install_skill.run(
142
152
  target_dir=args.target_dir,
143
153
  agent=args.agent,
@@ -61,7 +61,7 @@ def _detect_agent(target: Path) -> str:
61
61
  # 2. Check runtime environment variables
62
62
  if os.environ.get("ANTIGRAVITY_AGENT") or os.environ.get("GEMINI_CLI"):
63
63
  return "antigravity"
64
- if os.environ.get("CLAUDE_CODE") or os.environ.get("CLAUDE_PROJECT_DIR"):
64
+ if any(os.environ.get(v) for v in ("CLAUDECODE", "CLAUDE_CODE", "CLAUDE_PROJECT_DIR")):
65
65
  return "claude"
66
66
  if os.environ.get("CURSOR_PROJECT_DIR"):
67
67
  return "cursor"
@@ -13,6 +13,8 @@ import sys
13
13
  from collections import Counter, defaultdict
14
14
  from pathlib import Path
15
15
 
16
+ from .status import VERDICTS, _cells, board_verdicts
17
+
16
18
  REPORT_ID_RE = re.compile(r"\*\*Report:\*\*\s*`?([A-Za-z0-9_.\-]+)`?")
17
19
  GUIDE_HEADER_RE = re.compile(r"^###\s+([A-Za-z0-9_.\-]+)\s+[\u2014\u2013\-]\s*(.*)$", re.MULTILINE)
18
20
  LOG_HEADER_RE = re.compile(
@@ -95,7 +97,6 @@ FENCE_LINE_RE = re.compile(r"^\s*(```|~~~)[\w-]*\s*$", re.MULTILINE)
95
97
  FENCED_BLOCK_RE = re.compile(r"^\s*(```|~~~)[\w-]*\s*\n(.*?)^\s*\1\s*$", re.MULTILINE | re.DOTALL)
96
98
  PHASE_RE = re.compile(r"^P(\d+)-", re.IGNORECASE)
97
99
  DEFERRED_PHASE_RE = re.compile(r"\b(?:P|Phase\s*)(\d+)\b", re.IGNORECASE)
98
- VERDICTS = {"APPROVED", "CONDITIONAL", "REJECTED"}
99
100
  DETERMINISTIC_BYPASS_RE = re.compile(
100
101
  r"\b(?:n/?a|not\s+applicable)\b[^\n]{0,60}\b"
101
102
  r"(?:deterministic|bug\s*fix|fix|compliance|security|patch|migration)\b",
@@ -170,6 +171,11 @@ def _is_gate(task_id: str, title: str) -> bool:
170
171
  return bool(GATE_ID_RE.search(task_id)) or "gate" in title.lower()
171
172
 
172
173
 
174
+ def _mask_fences(text: str) -> str:
175
+ """text with every fenced block blanked out, keeping offsets and line breaks."""
176
+ return FENCED_BLOCK_RE.sub(lambda b: re.sub(r"[^\n]", " ", b.group(0)), text)
177
+
178
+
173
179
  def _done_without_evidence(log_text: str) -> list[str]:
174
180
  """IDs reported DONE whose **Verify output:** field is missing, empty, or only defers.
175
181
 
@@ -183,15 +189,16 @@ def _done_without_evidence(log_text: str) -> list[str]:
183
189
  if m.group(2).upper() != "DONE":
184
190
  continue
185
191
  rest = log_text[m.end() :]
186
- end = SECTION_END_RE.search(rest)
187
- body = rest[: end.start()] if end else rest
188
- field = VERIFY_FIELD_RE.search(body)
192
+ # A "#", "---" or "**Field:**" line inside pasted output does not end the report.
193
+ masked = _mask_fences(rest)
194
+ end = SECTION_END_RE.search(masked)
195
+ body_end = end.start() if end else len(rest)
196
+ field = VERIFY_FIELD_RE.search(masked, 0, body_end)
189
197
  if not field:
190
198
  missing.append(m.group(1))
191
199
  continue
192
- after = body[field.end() :]
193
- nxt = NEXT_FIELD_RE.search(after)
194
- evidence = after[: nxt.start()] if nxt else after
200
+ nxt = NEXT_FIELD_RE.search(masked, field.end(), body_end)
201
+ evidence = rest[field.end() : nxt.start() if nxt else body_end]
195
202
  blocks = [
196
203
  PLACEHOLDER_RE.sub("", b.group(2)).strip() for b in FENCED_BLOCK_RE.finditer(evidence)
197
204
  ]
@@ -202,18 +209,23 @@ def _done_without_evidence(log_text: str) -> list[str]:
202
209
 
203
210
  def _phase_order_violations(guide_text: str, log_text: str) -> list[str]:
204
211
  """IDs reported DONE in a phase that started before every earlier phase was APPROVED."""
205
- from .status import _board_verdicts
206
-
207
212
  ids = [task_id for task_id, _, _ in _guide_sections(guide_text) if TASK_ID_RE.search(task_id)]
208
- verdicts = {k: v for k, v in _board_verdicts(log_text).items() if v in VERDICTS}
213
+ board = {k: v for k, v in board_verdicts(log_text).items() if v in VERDICTS}
214
+ # As in `aepkit status`: the last report header per ID wins, and a board verdict that
215
+ # disagrees with the header verdict is a conflict, which does not count as APPROVED.
216
+ header_verdicts: dict[str, str] = {}
209
217
  reports: dict[str, str] = {}
210
218
  for m in LOG_HEADER_RE.finditer(log_text):
211
219
  task_id, value = m.group(1), m.group(2).upper()
212
220
  ids.append(task_id)
213
221
  if value in VERDICTS:
214
- verdicts.setdefault(task_id, value)
222
+ header_verdicts[task_id] = value
215
223
  else:
216
224
  reports[task_id] = value
225
+ verdicts = {**header_verdicts, **board}
226
+ for task_id, value in header_verdicts.items():
227
+ if board.get(task_id, value) != value:
228
+ verdicts[task_id] = "CONFLICT"
217
229
  phases: defaultdict[int, set[str]] = defaultdict(set)
218
230
  for task_id in ids:
219
231
  found = PHASE_RE.match(task_id)
@@ -291,9 +303,7 @@ def _plan_gaps(plan_text: str) -> list[str]:
291
303
  def _open_deferred_items(plan_text: str, known_ids: set[str], log_text: str) -> list[str]:
292
304
  """Flag deferred-ledger rows whose closing task ID does not exist or remains empty when
293
305
  its target phase is being closed."""
294
- from .status import _board_verdicts, _cells
295
-
296
- verdicts = {k: v for k, v in _board_verdicts(log_text).items() if v in VERDICTS}
306
+ verdicts = {k: v for k, v in board_verdicts(log_text).items() if v in VERDICTS}
297
307
  reports: dict[str, str] = {}
298
308
  for m in LOG_HEADER_RE.finditer(log_text):
299
309
  tid, val = m.group(1), m.group(2).upper()
@@ -476,10 +486,8 @@ def run(target_dir: str, annex_threshold: int = DEFAULT_ANNEX_THRESHOLD) -> int:
476
486
  # 3b. Plan of record: opportunity/shaping/non-goals (once work starts) and deferred items.
477
487
  plan_path = target / "plan-of-record.md"
478
488
  if plan_path.is_file():
479
- from .status import _board_verdicts
480
-
481
489
  plan_text = _read(plan_path)
482
- board = _board_verdicts(log_text)
490
+ board = board_verdicts(log_text)
483
491
  work_started = any(
484
492
  v == "DONE" or v in VERDICTS for v in (*log_status.values(), *board.values())
485
493
  )
@@ -528,8 +536,7 @@ def run(target_dir: str, annex_threshold: int = DEFAULT_ANNEX_THRESHOLD) -> int:
528
536
  else:
529
537
  phase_counts["General"] += 1
530
538
 
531
- phase_exceeded = {p: c for p, c in phase_counts.items() if c > annex_threshold}
532
- if len(annex_files) > annex_threshold or phase_exceeded:
539
+ if len(annex_files) > annex_threshold:
533
540
  problems += 1
534
541
  print(
535
542
  f"[annex count] {len(annex_files)} annexes exceeds the threshold of "