stacktrace-cli 0.0.1__tar.gz → 0.1.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 (181) hide show
  1. stacktrace_cli-0.1.0/.agents/skills/release-stacktrace/SKILL.md +233 -0
  2. stacktrace_cli-0.1.0/.codex/hooks.json +34 -0
  3. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/autofix.yml +1 -1
  4. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/claude.yml +10 -4
  5. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/CLAUDE.md +45 -0
  6. stacktrace_cli-0.1.0/PKG-INFO +228 -0
  7. stacktrace_cli-0.1.0/README.md +207 -0
  8. stacktrace_cli-0.1.0/docs/adrs/0001-session-telemetry-as-input.md +116 -0
  9. stacktrace_cli-0.1.0/docs/adrs/0002-session-collection-in-openaidr.md +159 -0
  10. stacktrace_cli-0.1.0/docs/adrs/0003-runtime-edges.md +97 -0
  11. stacktrace_cli-0.1.0/docs/adrs/0004-three-stage-detector.md +157 -0
  12. stacktrace_cli-0.1.0/docs/adrs/0005-detection-family-and-report-assembly.md +167 -0
  13. stacktrace_cli-0.1.0/docs/adrs/0006-trust-boundary-and-detection-upload.md +179 -0
  14. stacktrace_cli-0.1.0/docs/adrs/0007-proprietary-package-on-open-dependencies.md +108 -0
  15. stacktrace_cli-0.1.0/docs/adrs/0008-console-in-separate-demo-package.md +77 -0
  16. stacktrace_cli-0.1.0/docs/adrs/0010-detection-severity-and-confidence-ladders.md +144 -0
  17. stacktrace_cli-0.1.0/docs/adrs/0011-verdict-cache.md +110 -0
  18. stacktrace_cli-0.1.0/docs/adrs/0012-observation-evidence-kinds-and-transport.md +116 -0
  19. stacktrace_cli-0.1.0/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +194 -0
  20. stacktrace_cli-0.1.0/docs/adrs/0014-declared-project-mapping.md +121 -0
  21. stacktrace_cli-0.1.0/docs/adrs/0015-narrow-the-security-catalogue.md +120 -0
  22. stacktrace_cli-0.1.0/docs/adrs/0016-stall-grouping-is-session-wide.md +94 -0
  23. stacktrace_cli-0.1.0/docs/adrs/0017-a-declined-repeat-is-a-stall.md +100 -0
  24. stacktrace_cli-0.1.0/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +97 -0
  25. stacktrace_cli-0.1.0/docs/adrs/0019-delegated-commands-are-openaca-objects.md +85 -0
  26. stacktrace_cli-0.1.0/docs/adrs/0020-openaca-consumption-boundary.md +126 -0
  27. stacktrace_cli-0.1.0/docs/adrs/0021-two-command-kinds.md +99 -0
  28. stacktrace_cli-0.1.0/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +90 -0
  29. stacktrace_cli-0.1.0/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +84 -0
  30. stacktrace_cli-0.1.0/docs/adrs/0024-the-upload-carries-observations.md +86 -0
  31. stacktrace_cli-0.1.0/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +92 -0
  32. stacktrace_cli-0.1.0/docs/adrs/INDEX.md +60 -0
  33. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/docs/adrs/TEMPLATE.md +2 -0
  34. stacktrace_cli-0.1.0/docs/plans/002-session-input.md +403 -0
  35. stacktrace_cli-0.1.0/docs/plans/003-correlation.md +890 -0
  36. stacktrace_cli-0.1.0/docs/plans/004-detector.md +522 -0
  37. stacktrace_cli-0.1.0/docs/plans/005-remote-sync.md +1346 -0
  38. stacktrace_cli-0.1.0/docs/plans/006-cli-composition.md +899 -0
  39. stacktrace_cli-0.1.0/docs/plans/007-detection-upload.md +313 -0
  40. stacktrace_cli-0.1.0/docs/releases/v0.1.0.md +74 -0
  41. stacktrace_cli-0.1.0/docs/specs/aidr.md +477 -0
  42. stacktrace_cli-0.1.0/docs/specs/cli-composition.md +375 -0
  43. stacktrace_cli-0.1.0/docs/specs/correlation.md +585 -0
  44. stacktrace_cli-0.1.0/docs/specs/detection-upload.md +441 -0
  45. stacktrace_cli-0.1.0/docs/specs/detector.md +1076 -0
  46. stacktrace_cli-0.1.0/docs/specs/remote-sync.md +606 -0
  47. stacktrace_cli-0.1.0/docs/specs/session-input.md +175 -0
  48. stacktrace_cli-0.1.0/pyproject.toml +94 -0
  49. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/src/stacktrace_cli/__init__.py +1 -1
  50. stacktrace_cli-0.1.0/src/stacktrace_cli/__main__.py +16 -0
  51. stacktrace_cli-0.1.0/src/stacktrace_cli/cli.py +314 -0
  52. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/__init__.py +1 -0
  53. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/acquire.py +373 -0
  54. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/composition.py +375 -0
  55. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/join.py +380 -0
  56. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/observed.py +1836 -0
  57. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/orchestrate.py +417 -0
  58. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/project_map.py +81 -0
  59. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/record.py +279 -0
  60. stacktrace_cli-0.1.0/src/stacktrace_cli/correlate/render.py +766 -0
  61. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/__init__.py +1 -0
  62. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/analyzer.py +207 -0
  63. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/cache.py +483 -0
  64. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/deterministic.py +606 -0
  65. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/finding.py +247 -0
  66. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/markers.py +158 -0
  67. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/priors.py +502 -0
  68. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/__init__.py +36 -0
  69. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/exclusions.md +36 -0
  70. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/framing.md +23 -0
  71. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +12 -0
  72. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/stacktrace-injected-instruction-followed.md +14 -0
  73. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/prompts/v1/stacktrace-intent-drift.md +11 -0
  74. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/reasoning.py +1180 -0
  75. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/render.py +310 -0
  76. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/rules.py +136 -0
  77. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/run.py +453 -0
  78. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/secrets.py +203 -0
  79. stacktrace_cli-0.1.0/src/stacktrace_cli/detector/verdict.py +266 -0
  80. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/__init__.py +1 -0
  81. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/cli.py +475 -0
  82. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/client.py +393 -0
  83. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/config.py +106 -0
  84. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/detect_payload.py +262 -0
  85. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/payload.py +435 -0
  86. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/policy.py +88 -0
  87. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/redact.py +649 -0
  88. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/spool.py +446 -0
  89. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/sync.py +558 -0
  90. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/sync_detect.py +409 -0
  91. stacktrace_cli-0.1.0/src/stacktrace_cli/remote/upload_contract.py +973 -0
  92. stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/__init__.py +1 -0
  93. stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/access.py +79 -0
  94. stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/outcome.py +65 -0
  95. stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/protocols.py +204 -0
  96. stacktrace_cli-0.1.0/src/stacktrace_cli/sessions/render.py +295 -0
  97. stacktrace_cli-0.1.0/tests/__init__.py +0 -0
  98. stacktrace_cli-0.1.0/tests/agent_bom_fixture.py +252 -0
  99. stacktrace_cli-0.1.0/tests/detector_session_fixture.py +137 -0
  100. stacktrace_cli-0.1.0/tests/remote/__init__.py +0 -0
  101. stacktrace_cli-0.1.0/tests/remote/helpers.py +126 -0
  102. stacktrace_cli-0.1.0/tests/remote/test_cli.py +826 -0
  103. stacktrace_cli-0.1.0/tests/remote/test_client.py +545 -0
  104. stacktrace_cli-0.1.0/tests/remote/test_config.py +118 -0
  105. stacktrace_cli-0.1.0/tests/remote/test_detect_activity.py +168 -0
  106. stacktrace_cli-0.1.0/tests/remote/test_detect_cli.py +72 -0
  107. stacktrace_cli-0.1.0/tests/remote/test_detect_client.py +59 -0
  108. stacktrace_cli-0.1.0/tests/remote/test_detect_contract_is_exhaustive.py +832 -0
  109. stacktrace_cli-0.1.0/tests/remote/test_detect_gate_matches_the_cloud.py +351 -0
  110. stacktrace_cli-0.1.0/tests/remote/test_detect_layers_compose.py +199 -0
  111. stacktrace_cli-0.1.0/tests/remote/test_detect_redaction.py +142 -0
  112. stacktrace_cli-0.1.0/tests/remote/test_detect_spool.py +329 -0
  113. stacktrace_cli-0.1.0/tests/remote/test_detect_upload_contract.py +180 -0
  114. stacktrace_cli-0.1.0/tests/remote/test_detect_wire_payload.py +185 -0
  115. stacktrace_cli-0.1.0/tests/remote/test_facade_contract.py +213 -0
  116. stacktrace_cli-0.1.0/tests/remote/test_payload.py +937 -0
  117. stacktrace_cli-0.1.0/tests/remote/test_policy.py +259 -0
  118. stacktrace_cli-0.1.0/tests/remote/test_redact_payload.py +985 -0
  119. stacktrace_cli-0.1.0/tests/remote/test_seam.py +260 -0
  120. stacktrace_cli-0.1.0/tests/remote/test_spool.py +693 -0
  121. stacktrace_cli-0.1.0/tests/remote/test_sync.py +1718 -0
  122. stacktrace_cli-0.1.0/tests/remote/test_sync_detect.py +576 -0
  123. stacktrace_cli-0.1.0/tests/remote/test_upload_contract.py +853 -0
  124. stacktrace_cli-0.1.0/tests/test_acquire.py +457 -0
  125. stacktrace_cli-0.1.0/tests/test_bom_shape.py +196 -0
  126. stacktrace_cli-0.1.0/tests/test_call_status_conventions.py +101 -0
  127. stacktrace_cli-0.1.0/tests/test_cli.py +330 -0
  128. stacktrace_cli-0.1.0/tests/test_composition.py +520 -0
  129. stacktrace_cli-0.1.0/tests/test_correlate_orchestrate.py +457 -0
  130. stacktrace_cli-0.1.0/tests/test_correlate_render.py +760 -0
  131. stacktrace_cli-0.1.0/tests/test_correlated_session.py +278 -0
  132. stacktrace_cli-0.1.0/tests/test_detector_analyzer_contract.py +243 -0
  133. stacktrace_cli-0.1.0/tests/test_detector_analyzer_live.py +158 -0
  134. stacktrace_cli-0.1.0/tests/test_detector_cache.py +633 -0
  135. stacktrace_cli-0.1.0/tests/test_detector_deterministic.py +367 -0
  136. stacktrace_cli-0.1.0/tests/test_detector_end_to_end.py +278 -0
  137. stacktrace_cli-0.1.0/tests/test_detector_finding.py +199 -0
  138. stacktrace_cli-0.1.0/tests/test_detector_liveness.py +698 -0
  139. stacktrace_cli-0.1.0/tests/test_detector_markers.py +150 -0
  140. stacktrace_cli-0.1.0/tests/test_detector_priors.py +710 -0
  141. stacktrace_cli-0.1.0/tests/test_detector_reasoning.py +1517 -0
  142. stacktrace_cli-0.1.0/tests/test_detector_render.py +325 -0
  143. stacktrace_cli-0.1.0/tests/test_detector_run.py +809 -0
  144. stacktrace_cli-0.1.0/tests/test_detector_secrets.py +292 -0
  145. stacktrace_cli-0.1.0/tests/test_detector_verdict.py +465 -0
  146. stacktrace_cli-0.1.0/tests/test_fingerprint_covers_what_the_analyzer_reads.py +383 -0
  147. stacktrace_cli-0.1.0/tests/test_help_sections.py +147 -0
  148. stacktrace_cli-0.1.0/tests/test_join.py +255 -0
  149. stacktrace_cli-0.1.0/tests/test_observed.py +747 -0
  150. stacktrace_cli-0.1.0/tests/test_openaca_contract.py +66 -0
  151. stacktrace_cli-0.1.0/tests/test_openaidr_contract.py +173 -0
  152. stacktrace_cli-0.1.0/tests/test_option_arity_matches_the_binary.py +519 -0
  153. stacktrace_cli-0.1.0/tests/test_project_map.py +270 -0
  154. stacktrace_cli-0.1.0/tests/test_readme_examples_are_real_output.py +69 -0
  155. stacktrace_cli-0.1.0/tests/test_release_readiness.py +59 -0
  156. stacktrace_cli-0.1.0/tests/test_seam_boundary.py +104 -0
  157. stacktrace_cli-0.1.0/tests/test_sessions_access.py +251 -0
  158. stacktrace_cli-0.1.0/tests/test_sessions_end_to_end.py +92 -0
  159. stacktrace_cli-0.1.0/tests/test_sessions_protocol_typing.py +18 -0
  160. stacktrace_cli-0.1.0/tests/test_sessions_protocols.py +59 -0
  161. stacktrace_cli-0.1.0/tests/test_sessions_render.py +503 -0
  162. stacktrace_cli-0.1.0/tests/test_shell_separators_match_the_shell.py +1288 -0
  163. stacktrace_cli-0.1.0/tests/test_untrusted_content_never_travels.py +278 -0
  164. stacktrace_cli-0.1.0/tests/test_verification_subcommands_match_the_binary.py +185 -0
  165. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/uv.lock +46 -6
  166. stacktrace_cli-0.0.1/PKG-INFO +0 -39
  167. stacktrace_cli-0.0.1/README.md +0 -22
  168. stacktrace_cli-0.0.1/docs/adrs/INDEX.md +0 -28
  169. stacktrace_cli-0.0.1/pyproject.toml +0 -50
  170. stacktrace_cli-0.0.1/src/stacktrace_cli/__main__.py +0 -24
  171. stacktrace_cli-0.0.1/tests/test_cli.py +0 -20
  172. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.claude/settings.json +0 -0
  173. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.claude/skills/release-stacktrace/SKILL.md +0 -0
  174. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/ci.yml +0 -0
  175. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.github/workflows/publish-pypi.yml +0 -0
  176. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/.gitignore +0 -0
  177. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/AGENTS.md +0 -0
  178. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/docs/adrs/HOOK-PROMPT.md +0 -0
  179. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/docs/releases/v0.0.1.md +0 -0
  180. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/scripts/git-hooks/pre-push +0 -0
  181. {stacktrace_cli-0.0.1 → stacktrace_cli-0.1.0}/scripts/install-hooks.sh +0 -0
@@ -0,0 +1,233 @@
1
+ ---
2
+ name: release-stacktrace
3
+ description: Use when cutting a new stacktrace-cli PyPI release. Walks through version bump, release-notes drafting, pre-flight checks, tag, and push — enforces the publish-pypi.yml notes-file gate so the GitHub Releases page can't silently drift from PyPI.
4
+ ---
5
+
6
+ # release-stacktrace
7
+
8
+ End-to-end release procedure for the `stacktrace-cli` distribution.
9
+ `.github/workflows/publish-pypi.yml` enforces the machine-checkable gates
10
+ (tag on main, tag matches both version strings, notes file present, four
11
+ CI gates green). This skill is the human-side counterpart: it drafts the
12
+ notes with judgment, runs the pre-flight checks, and pushes the tag.
13
+
14
+ **Announce at start:** "I'm using the release-stacktrace skill to cut a new
15
+ stacktrace-cli release."
16
+
17
+ ## When to invoke
18
+
19
+ User says any of: "cut a release", "ship 0.0.N", "release stacktrace",
20
+ "tag a new version", or starts editing `pyproject.toml`'s version field
21
+ in this repo.
22
+
23
+ ## Inputs
24
+
25
+ 1. **Target version** (e.g. `0.1.0`). Inferred from `pyproject.toml` if
26
+ the user already bumped it; otherwise ask.
27
+ 2. **Theme** — the H1 line of the notes file. Ask the user; a commit log
28
+ can't infer it.
29
+
30
+ ## Procedure
31
+
32
+ ### Step 1. Pre-flight checks
33
+
34
+ Run from the repo root. Stop and ask if any fails — do not "fix" a dirty
35
+ tree or out-of-sync main automatically, those signal in-flight work.
36
+
37
+ ```bash
38
+ git rev-parse --abbrev-ref HEAD # must be main
39
+ git status --porcelain # must be empty
40
+ git fetch --tags origin
41
+ git rev-parse --verify "v<version>" 2>/dev/null # MUST FAIL — tag must not exist
42
+ test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
43
+ ```
44
+
45
+ `tests/test_release_readiness.py -m release_gate` is **not** run here: it
46
+ requires `openaca`/`openaidr` pinned exactly, which is not true of a normal
47
+ checkout (both float on `>=` between releases) and is only made true by
48
+ Step 4. Running it against an unpinned tree would fail on every release, not
49
+ just a broken one. It runs at the end of Step 4, once its precondition holds.
50
+
51
+ PyPI versions are immutable, including yanked ones. If `<version>` is
52
+ already on PyPI, the only path forward is a new version number:
53
+
54
+ ```bash
55
+ curl -s https://pypi.org/pypi/stacktrace-cli/json | python3 -c 'import json,sys; print(sorted(json.load(sys.stdin)["releases"]))'
56
+ ```
57
+
58
+ ### Step 2. Gather the commit log
59
+
60
+ ```bash
61
+ prev_tag=$(git describe --tags --abbrev=0) # empty on the first release
62
+ git log --oneline "${prev_tag}..HEAD"
63
+ ```
64
+
65
+ Raw material only — do not dump it into the notes file verbatim.
66
+
67
+ ### Step 3. Draft `docs/releases/v<version>.md`
68
+
69
+ Follow the shape of the existing files in `docs/releases/`:
70
+
71
+ ```markdown
72
+ # <version> — <theme>
73
+
74
+ ## Highlights
75
+
76
+ - **<Theme A>.** 1-3 sentences pitched at someone who installs the CLI.
77
+ Reference ADRs by number when a design doc backs the change.
78
+ - **Bug fixes.** Combine small fixes into one bullet, comma-separated.
79
+
80
+ ## Install
81
+
82
+ `uv tool install stacktrace-cli==<version>` or `pip install stacktrace-cli==<version>`.
83
+
84
+ ## Compatibility
85
+
86
+ <Behavior changes existing users will notice. Say "pre-alpha, no
87
+ back-compat hedging" while that's still true.>
88
+ ```
89
+
90
+ **Judgment guidelines (apply ruthlessly):**
91
+
92
+ - **Group by theme, not by commit.** Five commits adding one capability
93
+ are one bullet.
94
+ - **Lead with user-visible impact**, not implementation.
95
+ - **Cut chore/format/docs-only and release-machinery commits.** They
96
+ aren't release-visible.
97
+ - **Acknowledge breaking changes explicitly** under `## Compatibility` —
98
+ a renamed subcommand or changed output shape goes there.
99
+
100
+ Show the draft to the user and iterate until they agree it captures what
101
+ shipped.
102
+
103
+ ### Step 4. Bump the version, pin workspace dependencies, and gate
104
+
105
+ `pyproject.toml`'s `version` and `src/stacktrace_cli/__init__.py`'s
106
+ `__version__` are separate sources; the workflow fails the build if they
107
+ disagree with the tag. Move them together:
108
+
109
+ ```bash
110
+ sed -i.bak 's/^version = ".*"/version = "<version>"/' pyproject.toml && rm pyproject.toml.bak
111
+ sed -i.bak 's/^__version__ = ".*"/__version__ = "<version>"/' src/stacktrace_cli/__init__.py && rm src/stacktrace_cli/__init__.py.bak
112
+ ```
113
+
114
+ `openaca` and `openaidr` are developed alongside this package and float on
115
+ `>=` during normal development, but a prior release will have left them
116
+ pinned exactly with `==` (see below), which would stop `uv lock` from ever
117
+ resolving a newer release. Loosen both back to a floor first — the
118
+ replacement matches either operator, so this is safe to run whether the tree
119
+ is currently floating or still pinned from last time:
120
+
121
+ ```bash
122
+ sed -i.bak -E 's/^ "openaca(>=|==)[^"]*"/ "openaca>=0"/' pyproject.toml && rm pyproject.toml.bak
123
+ sed -i.bak -E 's/^ "openaidr(>=|==)[^"]*"/ "openaidr>=0"/' pyproject.toml && rm pyproject.toml.bak
124
+ uv lock # refresh uv.lock, now free to pick the newest release
125
+ uv run stacktrace --version # sanity: prints <version>
126
+ ```
127
+
128
+ Now pin both to whatever `uv lock` just resolved — the one point in the
129
+ procedure where the lockfile has just picked the newest available versions
130
+ and a release is actually being cut:
131
+
132
+ ```bash
133
+ openaca_version=$(grep -A1 '^name = "openaca"' uv.lock | grep version | cut -d'"' -f2)
134
+ openaidr_version=$(grep -A1 '^name = "openaidr"' uv.lock | grep version | cut -d'"' -f2)
135
+ sed -i.bak -E "s/^ \"openaca(>=|==)[^\"]*\"/ \"openaca==${openaca_version}\"/" pyproject.toml && rm pyproject.toml.bak
136
+ sed -i.bak -E "s/^ \"openaidr(>=|==)[^\"]*\"/ \"openaidr==${openaidr_version}\"/" pyproject.toml && rm pyproject.toml.bak
137
+ uv lock # refresh uv.lock for the exact pin
138
+ uv run pytest tests/test_release_readiness.py -m release_gate
139
+ ```
140
+
141
+ The gate is not covered by `.github/workflows/publish-pypi.yml`, which runs
142
+ the default `uv run pytest -q` — and `pyproject.toml`'s `addopts` excludes
143
+ `release_gate` from that default run on purpose (it fails throughout normal
144
+ development, before this step pins the floors). This skill is the only place
145
+ it runs before a tag is pushed, so skipping it here is the one way a version
146
+ with an unpinned `openaidr`/`openaca` requirement reaches PyPI. Stop and ask
147
+ if it fails.
148
+
149
+ ### Step 5. Release-prep commit + PR
150
+
151
+ If the user's original request did not explicitly ask to cut, ship, or
152
+ publish a release, stop before the push and ask. Editing the version
153
+ field is enough to invoke this skill; it is not permission to publish.
154
+
155
+ ```bash
156
+ git checkout -b release/<version>
157
+ git add pyproject.toml uv.lock src/stacktrace_cli/__init__.py docs/releases/v<version>.md
158
+ git status --porcelain # inspect anything still unstaged; stage intentionally or stop
159
+ git commit -m "release: <version> — <theme>"
160
+ git push -u origin release/<version>
161
+ ```
162
+
163
+ Opening the PR needs its own explicit ask. Then:
164
+
165
+ ```bash
166
+ gh pr create --title "release: <version>" --body "Release prep for <version>.
167
+
168
+ - Bumps \`pyproject.toml\` and \`__version__\` to <version>
169
+ - Adds release notes at \`docs/releases/v<version>.md\`
170
+ - After merge: tag \`v<version>\` on main and push to trigger PyPI publish + GitHub Release"
171
+ ```
172
+
173
+ Surface the PR URL. **Stop here.** The user reviews and merges.
174
+
175
+ ### Step 6. After merge: tag + push
176
+
177
+ After the user confirms the PR merged, on a clean main:
178
+
179
+ ```bash
180
+ git checkout main
181
+ git pull --ff-only
182
+ git tag v<version>
183
+ git push origin v<version>
184
+ ```
185
+
186
+ The tag-triggered run registers asynchronously, so `--limit 1` can grab a
187
+ stale earlier run. Poll for the run tied to the tagged commit:
188
+
189
+ ```bash
190
+ tag_sha=$(git rev-parse "v<version>^{commit}")
191
+ run_id=""
192
+ until [ -n "$run_id" ]; do
193
+ run_id=$(gh run list --repo stacktrace-ai/stacktrace \
194
+ --workflow publish-pypi.yml --commit "$tag_sha" \
195
+ --json databaseId --jq '.[0].databaseId // empty')
196
+ [ -z "$run_id" ] && sleep 5
197
+ done
198
+ gh run watch --repo stacktrace-ai/stacktrace "$run_id"
199
+ ```
200
+
201
+ ### Step 7. Verify
202
+
203
+ ```bash
204
+ curl -s https://pypi.org/pypi/stacktrace-cli/json | python3 -c 'import json,sys; print(sorted(json.load(sys.stdin)["releases"]))'
205
+ gh release view v<version> --repo stacktrace-ai/stacktrace --json name,body
206
+ uvx --from stacktrace-cli==<version> stacktrace --version # installs from PyPI, must print <version>
207
+ ```
208
+
209
+ All three must succeed. If any fails, surface the workflow run URL rather
210
+ than guessing.
211
+
212
+ ## Failure modes this prevents
213
+
214
+ 1. **On PyPI but not on GitHub Releases.** The workflow's notes-file gate
215
+ runs before publish: no notes file at the tag → no PyPI upload.
216
+ 2. **Notes file is a raw commit dump.** Step 3's judgment guidelines.
217
+ 3. **Tagging a non-main commit.** The workflow's "Verify tag is on main".
218
+ 4. **Tag ≠ `pyproject.toml` version, or `__version__` drift.** Two
219
+ separate workflow steps; Step 4 keeps them moving together.
220
+ 5. **Forgetting to refresh `uv.lock`.** `uv sync --frozen` fails in the
221
+ build job; Step 4 bumps the lock to avoid the round-trip.
222
+
223
+ ## What this skill does NOT do
224
+
225
+ - **Doesn't publish to PyPI directly.** The workflow does, via Trusted
226
+ Publishing (OIDC). There is no PyPI token anywhere in this repo, and
227
+ none should be added.
228
+ - **Doesn't create the GitHub Release directly.** The workflow's
229
+ `release-github` job does, from the notes file.
230
+ - **Doesn't write a CHANGELOG.md.** The convention is per-release files
231
+ under `docs/releases/`.
232
+ - **Doesn't yank.** PyPI versions are immutable; a broken release is
233
+ fixed by shipping the next version, not by re-uploading.
@@ -0,0 +1,34 @@
1
+ {
2
+ "hooks": {
3
+ "PreCompact": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE COMPACTION ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
9
+ }
10
+ ]
11
+ }
12
+ ],
13
+ "SessionStart": [
14
+ {
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "if [ -f docs/adrs/INDEX.md ]; then printf '=== ARCHITECTURE DECISIONS (docs/adrs/) ===\\n\\nRead a full ADR before changing logic in the area it covers.\\n\\n'; cat docs/adrs/INDEX.md; fi"
19
+ }
20
+ ]
21
+ }
22
+ ],
23
+ "SessionEnd": [
24
+ {
25
+ "hooks": [
26
+ {
27
+ "type": "command",
28
+ "command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE SESSION END ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
29
+ }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+ }
@@ -111,7 +111,7 @@ jobs:
111
111
  # two string representations depending on which API you read.
112
112
  - name: Cap auto-address iterations (max 7)
113
113
  run: |
114
- MAX=7
114
+ MAX=15
115
115
  CLAUDE_RUNS=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
116
116
  --jq '[.comments[] | select(.author.login == "claude")] | length')
117
117
  if [ "$CLAUDE_RUNS" -ge "$MAX" ]; then
@@ -100,10 +100,16 @@ jobs:
100
100
  uses: anthropics/claude-code-action@v1
101
101
  with:
102
102
  # Subscription-tied OAuth path. Provision via `claude setup-token`
103
- # CLI and store as the CLAUDE_CODE_OAUTH_TOKEN repo secret. To use
104
- # the pay-per-use API-key path instead, replace this line with
105
- # `anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}` and create
106
- # a key at https://platform.claude.com/settings/workspaces/default/keys.
103
+ # CLI and store as the CLAUDE_CODE_OAUTH_TOKEN repo secret.
104
+ #
105
+ # This ran on the pay-per-use API key for one day (d2c62b0) to escape
106
+ # the throttling the subscription's shared session window imposes on
107
+ # long review runs, and the spend was not worth it: this workflow is
108
+ # fired by every review comment on an open PR, so an active
109
+ # review loop bills per round with no ceiling of its own. Throttling
110
+ # is the cheaper failure. To switch back, replace this line with
111
+ # `anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}` (the secret is
112
+ # still provisioned) and set a workspace spend limit first.
107
113
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
108
114
 
109
115
  # Lets Claude read CI results on PRs.
@@ -8,11 +8,21 @@ unrelated project). It is consumed by developers who `pip install stacktrace-cli
8
8
  and by CI pipelines that invoke `stacktrace`, and it builds on the open-source
9
9
  `openaca` Agent Composition Analysis toolkit.
10
10
 
11
+ It is also the **design home for Stacktrace Detect** — the AIDR product this CLI
12
+ grows into. The specs in `docs/specs/` and the decisions in `docs/adrs/` describe
13
+ the whole system: session input, correlation against the composition graph, the
14
+ three-stage detector, and the combined report. The code here is the placeholder that
15
+ precedes them.
16
+
11
17
  ## Common commands
12
18
 
13
19
  ```bash
14
20
  uv sync # install / update deps
15
21
  uv run stacktrace --version # run the CLI from the working tree
22
+ uv run stacktrace sessions # what the agents on this machine did
23
+ uv run stacktrace sessions --format json # the same, machine-readable
24
+ uv run stacktrace detect # what ran, correlated and judged
25
+ uv run stacktrace detect --bom b.json # against a supplied Agent BOM
16
26
  uv build # build sdist + wheel into dist/
17
27
  uvx --from 'twine>=7' twine check dist/* # validate artifacts (twine <7 rejects Metadata-Version 2.5)
18
28
  ```
@@ -48,6 +58,24 @@ Composition Analysis primitives this CLI is intended to drive.
48
58
  The package is currently pre-alpha: `main()` reports its version and points at
49
59
  the docs. Real functionality lands in later releases.
50
60
 
61
+ **The design that functionality implements is in this repo.** Read
62
+ `docs/specs/aidr.md` first — it is the umbrella: components, contracts, the two
63
+ modes, and what comes from where. Then the component specs:
64
+
65
+ | Spec | Covers |
66
+ |---|---|
67
+ | `docs/specs/aidr.md` | Umbrella — components, contracts, tenets, delivery |
68
+ | `docs/specs/session-input.md` | The seam over OpenAIDR, which collects sessions |
69
+ | `docs/specs/correlation.md` | The join: sessions against the composition graph |
70
+ | `docs/specs/detector.md` | Three-stage detection and the finding family |
71
+
72
+ Two dependencies, both Apache-2.0 and neither depending on this package:
73
+ `openaca` for composition, and `openaidr`
74
+ (https://github.com/open-agent-security/openaidr) for session collection, which
75
+ wraps Uber's third-party `adr-sensor`. Nothing proprietary flows into either
76
+ (ADR-0007); the one thing we need from OpenACA is graph caching, and it names
77
+ nothing about detection.
78
+
51
79
  ## Repo conventions
52
80
 
53
81
  - **Four names, deliberately not identical.** PyPI distribution
@@ -75,6 +103,23 @@ the docs. Real functionality lands in later releases.
75
103
  API token exists in this repo, and none should be added), then creates the
76
104
  GitHub Release from the notes file. The `release-stacktrace` skill drives
77
105
  the human side.
106
+ - **OpenACA carries nothing AIDR** (ADR-0006). No finding family, no output row
107
+ type, no scan flag, no rule namespace upstream. Detection rule identifiers are
108
+ `stacktrace-*`, never `openaca-*`, and the combined report is assembled here.
109
+ - **Session collection is not ours** (ADR-0002). It lives in `openaidr`, open
110
+ source. Its types stop at our adapter; parsing an agent format here would
111
+ duplicate the thing the split exists to prevent.
112
+ - **A class-shaped review finding is closed with an invariant, not a patch.**
113
+ Four classes of finding recurred across this branch's review rounds — a denied
114
+ call counted as evidence, untrusted text echoed into a finding, an
115
+ unvalidated field in an external document, a prompt field missing from the
116
+ cache key — because each was fixed at the site a reviewer happened to read
117
+ while the *rule* stayed in the reviewer's head. Each is now enforced by a test
118
+ that asks about every site or every field rather than the one that bit us:
119
+ `tests/test_call_status_conventions.py`, `test_untrusted_content_never_travels.py`,
120
+ `test_bom_shape.py`, `test_fingerprint_covers_what_the_analyzer_reads.py`. When
121
+ a review names a site, fix the invariant and extend the exhaustive test — a
122
+ second finding of the same class means the first fix was at the wrong level.
78
123
 
79
124
  ---
80
125
 
@@ -0,0 +1,228 @@
1
+ Metadata-Version: 2.5
2
+ Name: stacktrace-cli
3
+ Version: 0.1.0
4
+ Summary: Placeholder CLI for Stacktrace.ai — installs the `stacktrace` command.
5
+ Project-URL: Homepage, https://stacktrace.ai
6
+ Author-email: Prasanth G <prasanth@openaca.dev>
7
+ License-Expression: LicenseRef-Proprietary
8
+ Keywords: agent-security,ai-security,openaca,stacktrace
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: Other/Proprietary License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Security
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: click>=8.1
17
+ Requires-Dist: httpx<1.0.dev0,>=0.28.1
18
+ Requires-Dist: openaca==0.6.0
19
+ Requires-Dist: openaidr==0.1.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ # stacktrace-cli
23
+
24
+ Command-line interface for [Stacktrace.ai](https://stacktrace.ai).
25
+
26
+ The PyPI distribution is named `stacktrace-cli` Installing this package provides
27
+ the `stacktrace` executable.
28
+
29
+ ```bash
30
+ pip install stacktrace-cli
31
+ stacktrace --version
32
+ ```
33
+
34
+ This is an early placeholder release. It depends on
35
+ [`openaca`](https://pypi.org/project/openaca/), the open-source Agent
36
+ Composition Analysis toolkit that Stacktrace.ai builds on.
37
+
38
+ ## Design
39
+
40
+ This repository is the design home for Stacktrace Detect — the agentic AI
41
+ detection and response product this CLI grows into. OpenACA answers *what is
42
+ installed and what could it do*; Stacktrace Detect adds *what did it actually
43
+ do*, by joining agent session activity to the composition graph.
44
+
45
+ | Document | Covers |
46
+ |---|---|
47
+ | [docs/specs/aidr.md](docs/specs/aidr.md) | Umbrella — components, contracts, tenets, delivery |
48
+ | [docs/specs/session-input.md](docs/specs/session-input.md) | The seam over OpenAIDR, which collects sessions |
49
+ | [docs/specs/correlation.md](docs/specs/correlation.md) | The join: sessions against the composition graph |
50
+ | [docs/specs/detector.md](docs/specs/detector.md) | Three-stage detection and the finding family |
51
+
52
+ Decisions are in [docs/adrs/](docs/adrs/).
53
+
54
+ Built on two Apache-2.0 packages, neither of which depends on this one:
55
+ [`openaca`](https://pypi.org/project/openaca/) for composition analysis, and
56
+ [`openaidr`](https://github.com/open-agent-security/openaidr) for session
57
+ collection.
58
+
59
+ ## Status
60
+
61
+ `stacktrace` is one front door over two kinds of command:
62
+
63
+ ```
64
+ Analysis (OpenACA):
65
+ bom Generate an Agent BOM for a repository or endpoint.
66
+ policy Validate and compile restrictive endpoint policies.
67
+ scan Scan a repository or endpoint for agent-composition findings.
68
+
69
+ Stacktrace:
70
+ detect Find security and reliability findings in what agents did.
71
+ remote Configure remote endpoint services.
72
+ sessions Print what the agents on this machine actually did.
73
+ ```
74
+
75
+ `stacktrace` is the entry point for hosted-product users. OpenACA remains the
76
+ tool itself and stays independently installable — with it installed, both
77
+ `openaca scan` and `stacktrace scan` work and do exactly the same thing,
78
+ because they are the same Click command object running in one process.
79
+ `stacktrace --version` reports both versions, which turns "which did you run?"
80
+ into one answered question.
81
+
82
+ **Pre-alpha, and not installable from PyPI yet.** `stacktrace sessions` works —
83
+ it reads what the agents on this machine did, through
84
+ [OpenAIDR](https://github.com/open-agent-security/openaidr):
85
+
86
+ ```console
87
+ $ uv run stacktrace sessions --since 2d --include-content
88
+ claude-code:s1 [claude-code] 2026-08-27T09:00:00+00:00 2 turns 2 calls
89
+ assistant: Reading the changelog before drafting the release notes.
90
+ ok 28c Read
91
+ result: ## 0.4.0 - correlate, detect
92
+ assistant: Filing the release-notes follow-up.
93
+ - github/create_issue
94
+
95
+ Summary — 1 sessions, 2 turns, 2 tool calls
96
+
97
+ agent kinds
98
+ 1 claude-code
99
+
100
+ tools called (2 distinct)
101
+ 1 Read
102
+ 1 github/create_issue
103
+
104
+ MCP servers reached (1 distinct)
105
+ 1 github
106
+
107
+ 0 subagent turns · 0 results abridged upstream · 1 ok
108
+
109
+ 1 of 2 calls returned with no outcome the collector could establish; the agent's parser supplies no success signal.
110
+ ```
111
+
112
+ The blank status on the second row is the collector's `unknown`, not a pending
113
+ call: the client recorded no outcome it could establish, and the closing line
114
+ says how many of those there were rather than filling one in.
115
+
116
+ The next step is the join: resolving what ran to the components the agent is
117
+ built from. `detect` performs it — it correlates the collected sessions against
118
+ an Agent BOM before it judges anything — but there is no longer a command that
119
+ prints the correlated view on its own. `stacktrace correlate` was withdrawn; the
120
+ correlation code stays, as the stage `detect` is built on.
121
+
122
+ Both `openaca` and `openaidr` resolve from PyPI now, like any other
123
+ dependency — no sibling checkout, no `[tool.uv.sources]` override. What still
124
+ gates a release is narrower: both float on `>=` during normal development but
125
+ must be pinned exactly (`==`) before a version is published, so a built wheel
126
+ names precisely what it was tested against.
127
+ `uv run pytest tests/test_release_readiness.py -m release_gate` checks that —
128
+ the `release-stacktrace` skill runs it as part of cutting a release.
129
+
130
+ ## Adding a command
131
+
132
+ A command is a **pass-through** or it is **native**, never both — and a
133
+ pass-through never gains a flag of its own. See
134
+ [ADR-0021](docs/adrs/0021-two-command-kinds.md) for why.
135
+
136
+ - **Pass-through** — add one string to `PASSTHROUGH` in
137
+ `src/stacktrace_cli/cli.py`, having decided it belongs. The loop registers
138
+ OpenACA's own command object under that name; there is nothing else to
139
+ write and nothing to keep in step with OpenACA.
140
+ - **Native** — write a Click command or group and `add_command` it, the way
141
+ `remote` is. If it needs OpenACA it calls `openaca.core`
142
+ ([ADR-0020](docs/adrs/0020-openaca-consumption-boundary.md)), and whatever
143
+ it names there is added to the contract test in `tests/remote/`, because it
144
+ now holds duplicated knowledge of an interface.
145
+
146
+ The section a command lands under in `--help` follows from which kind it is;
147
+ there is no second list to update.
148
+
149
+
150
+ ## Development
151
+
152
+ `uv sync` and the four gates — `ruff check`, `ruff format --check`, `pyright`,
153
+ `pytest` — need nothing beside this repository. `openaca` resolves from PyPI at
154
+ the floor `pyproject.toml` declares. A sibling `../openaca` checkout is no
155
+ longer read: the interim `[tool.uv.sources]` path source retired at the 0.6.0
156
+ cutover.
157
+
158
+ ## Cutover from `openaca remote`
159
+
160
+ `stacktrace remote` is the hosted-service client OpenACA is removing. The
161
+ behaviour, the payload and the Cloud endpoints are unchanged; only the code
162
+ that produces them moved. Machines already running `openaca remote sync
163
+ endpoint` need four steps each.
164
+
165
+ 1. **Have the collector token to hand, or mint a new one.** A collector token
166
+ cannot be recovered from the Cloud — it is returned once at issuance and
167
+ only a hash and its last four characters are kept — so it has to come from
168
+ wherever it was stored (an MDM secret store, a password manager). If it was
169
+ not stored, mint a new one and deploy that; nothing goes down while you do,
170
+ because the existing token keeps working until it is revoked.
171
+ 2. **Configure this machine.** `stacktrace remote configure --token …`, or set
172
+ `STACKTRACE_REMOTE_TOKEN` and `STACKTRACE_REMOTE_API_URL` for a scripted
173
+ deployment.
174
+ 3. **Repoint whatever schedules the sync** at `stacktrace remote sync
175
+ endpoint`.
176
+ 4. **Delete `~/.config/openaca/remote.toml`.** Once OpenACA has no `remote`
177
+ command, that file is an unused plaintext credential sitting on disk.
178
+ Nothing breaks if it stays, which is exactly why it will be forgotten.
179
+
180
+ ### What takes care of itself
181
+
182
+ - **The asset converges.** Registration is idempotent on `(org, asset_type,
183
+ external_id)` with the host name as `external_id`, so registering from here
184
+ resolves to the same asset OpenACA registered — one extra round trip on the
185
+ first run, no duplicate machine in the console.
186
+ - **Both commands may coexist during the overlap**, producing two BOM rows per
187
+ sync. That is noise rather than corruption.
188
+
189
+ > These two statements, and the unrecoverability of a collector token in step
190
+ > 1, describe what the deployed Cloud does. They have **not** been confirmed
191
+ > against the running service for this release — they are read from the
192
+ > hosted side's design, which records what some revision implements rather
193
+ > than what production does today. Confirm them before an operator acts on
194
+ > step 1 in particular: discarding the only copy of a token on the strength of
195
+ > an unverified sentence leaves no recovery.
196
+
197
+ ### What does not
198
+
199
+ - **A pending OpenACA spool is orphaned.** Its files are in
200
+ `~/.local/state/openaca`, and this channel never reads them. Drain it by
201
+ running the old command once before cutting over, or accept losing what it
202
+ holds. Do not assume that is one sync's worth: the spool keeps one file per
203
+ failed agent and accumulates across offline runs, so count the files first.
204
+ - **The MDM deploy scripts and the scheduled agent** do not exist here yet.
205
+
206
+ ## Release obligations
207
+
208
+ Finishing the remote-sync work was **not** the same as being releasable, and
209
+ the gap was structural rather than a matter of polish: while `pyproject.toml`
210
+ carried a path source to a sibling checkout, `pip install stacktrace-cli` from
211
+ PyPI would have resolved an `openaca` without the consumption facade, and every
212
+ `remote` command would have failed at import.
213
+
214
+ All three are discharged, at `openaca` 0.6.0 — the first release carrying the
215
+ facade:
216
+
217
+ 1. `[tool.uv.sources]` is gone; `uv.lock` resolves `openaca` from PyPI.
218
+ 2. `.github/workflows/ci.yml` checks out this repository alone. The sibling
219
+ checkout and its pinned-revision assertion went with the path source.
220
+ 3. The floor is `openaca>=0.6.0`, which is where plan 005 Task 1's
221
+ required-import probe first reports no missing name. Re-run that probe
222
+ against any candidate release before moving the floor again; it passes only
223
+ when it reports nothing missing.
224
+
225
+ One proof retired with them. The payload-equivalence test imported openaca's
226
+ `tools.remote` — the module 0.6.0 removed — so it cannot run against any
227
+ release that carries the facade. What it established is a fact about the
228
+ migration, not a property that a later release could re-check.