stacktrace-cli 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
@@ -1,3 +1,3 @@
1
1
  """The `stacktrace` command-line interface — detection and response for AI agents."""
2
2
 
3
- __version__ = "0.4.1"
3
+ __version__ = "0.5.0"
@@ -9,6 +9,7 @@ import sys
9
9
  import click
10
10
 
11
11
  from .paths import RuntimePaths
12
+ from .presentation import render_finding
12
13
  from .store import FindingStore, SessionKey
13
14
 
14
15
 
@@ -100,7 +101,4 @@ def findings(agent_kind: str, session_id: str, output_format: str) -> None:
100
101
  click.echo("No Stacktrace findings for this session.")
101
102
  return
102
103
  for finding in stored:
103
- click.echo(
104
- f"{str(finding.get('severity', 'unknown')).upper()} "
105
- f"{finding.get('rule_id', 'unknown-rule')}: {finding.get('title', 'Finding')}"
106
- )
104
+ click.echo(render_finding(finding))
@@ -0,0 +1,68 @@
1
+ """Explain durable findings without reintroducing discarded transcript prose."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from stacktrace_cli.detector.blocked import REASON_CODES, reason_for
8
+
9
+ # Notification prose is selected by rule ID only (ADR-0031). In particular,
10
+ # finding titles, component names and evidence never supply notification text.
11
+ _GUIDANCE = {
12
+ "stacktrace-credential-egress": (
13
+ "Credential-shaped material appeared in an outbound tool call",
14
+ (
15
+ "Treat the credential as potentially exposed: revoke or rotate it, "
16
+ "then inspect why the outbound tool received it."
17
+ ),
18
+ ),
19
+ "stacktrace-deceptive-completion": (
20
+ "A success claim conflicts with verification evidence",
21
+ "Inspect the cited verification results before relying on the completion claim.",
22
+ ),
23
+ "stacktrace-agent-blocked": (
24
+ "The agent encountered a block while working",
25
+ "Inspect the recorded block reason before retrying the task.",
26
+ ),
27
+ }
28
+
29
+
30
+ def rule_guidance(rule_id: str) -> tuple[str, str]:
31
+ return _GUIDANCE.get(
32
+ rule_id,
33
+ ("A finding was recorded", "Inspect the retained evidence in the local session."),
34
+ )
35
+
36
+
37
+ def render_finding(finding: dict[str, object]) -> str:
38
+ """Render the old, title-less ledger as well as newly recorded findings."""
39
+ rule_id = str(finding.get("rule_id", "unknown-rule"))
40
+ title, remedy = rule_guidance(rule_id)
41
+ evidence = finding.get("evidence", [])
42
+ items = (
43
+ [item for item in evidence if isinstance(item, dict)] if isinstance(evidence, list) else []
44
+ )
45
+ if rule_id == "stacktrace-agent-blocked" and items:
46
+ code = items[0].get("kind")
47
+ if isinstance(code, str) and code in REASON_CODES:
48
+ reason = reason_for(code)
49
+ title, remedy = reason.title, reason.remedy
50
+
51
+ lines = [
52
+ f"{str(finding.get('severity', 'unknown')).upper()} {rule_id}: {title}",
53
+ f" Confidence: {finding.get('confidence', 'unknown')}",
54
+ ]
55
+ for item in items:
56
+ # Quote stored descriptors as data, including escaped terminal controls.
57
+ kind = json.dumps(item.get("kind"), ensure_ascii=True)
58
+ detail = json.dumps(item.get("detail"), ensure_ascii=True)
59
+ span = json.dumps(item.get("span"), ensure_ascii=True)
60
+ lines.extend((f" Evidence ({kind}): {detail}", f" Location: {span}"))
61
+ if not items:
62
+ lines.append(" Evidence: no descriptors were retained.")
63
+ lines.append(f" Next step: {remedy}")
64
+ lines.append(
65
+ " Tool/component names and raw call content are not retained in this ledger; "
66
+ "inspect the cited locations in the local session for attribution."
67
+ )
68
+ return "\n".join(lines)
@@ -17,6 +17,8 @@ from stacktrace_cli.detector.rules import scope_of
17
17
  from stacktrace_cli.private_state import ensure_private_directory
18
18
  from stacktrace_cli.telemetry import emit_derived
19
19
 
20
+ from .presentation import rule_guidance
21
+
20
22
 
21
23
  @dataclass(frozen=True)
22
24
  class SessionKey:
@@ -266,10 +268,16 @@ class FindingStore:
266
268
  identity = f"{session.agent_kind}\0{session.session_id}\0{rule_id}\0{anchor}".encode()
267
269
  return f"stn_{hashlib.sha256(identity).hexdigest()[:24]}"
268
270
 
271
+ #: Severities that earn a notification. `Severity` is `low | medium | high`;
272
+ #: `critical` is carried because the gate has always tested for it, though
273
+ #: no rule emits it. `low` stays silent.
274
+ _NOTIFIABLE_SEVERITIES = frozenset({"medium", "high", "critical"})
275
+
269
276
  @staticmethod
270
277
  def _eligible(finding: dict[str, object]) -> bool:
271
278
  return (
272
- finding.get("severity") in {"high", "critical"} and finding.get("confidence") == "high"
279
+ finding.get("severity") in FindingStore._NOTIFIABLE_SEVERITIES
280
+ and finding.get("confidence") == "high"
273
281
  )
274
282
 
275
283
  @staticmethod
@@ -302,13 +310,17 @@ class FindingStore:
302
310
  severity = finding["severity"]
303
311
  assert isinstance(rule_id, str)
304
312
  assert isinstance(severity, str)
313
+ explanation, _ = rule_guidance(rule_id)
305
314
  return Notification(
306
315
  event_id=event_id,
307
316
  session=session,
308
317
  rule_id=rule_id,
309
318
  severity=severity,
310
- title=f"Stacktrace detected a {severity}-severity finding",
311
- body=f"{rule_id} in this agent session. Run /stacktrace:findings for details.",
319
+ title=f"Stacktrace: {explanation}",
320
+ body=(
321
+ f"Severity: {severity}. Rule: {rule_id}. "
322
+ "Run /stacktrace:findings for evidence and next steps."
323
+ ),
312
324
  )
313
325
 
314
326
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stacktrace-cli
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: CLI for Stacktrace — Detection and Response platform for AI Agents.
5
5
  Project-URL: Homepage, https://stacktrace.ai
6
6
  Author-email: "Stacktrace AI, Inc" <founders@stacktrace.ai>
@@ -51,7 +51,7 @@ Requires Python 3.11 or newer.
51
51
 
52
52
  ```console
53
53
  $ stacktrace --version
54
- stacktrace 0.4.1 (openaca 0.7.0)
54
+ stacktrace 0.5.0 (openaca 0.7.0)
55
55
  ```
56
56
 
57
57
  ## Quick start
@@ -1,4 +1,4 @@
1
- stacktrace_cli/__init__.py,sha256=bXjoLHPSd6YwfHKM4Sfm35_n4QAJu53np9qMAKHK4cQ,111
1
+ stacktrace_cli/__init__.py,sha256=zHZ09_BkTtuYNvOWuyPl2wfwi89SQMreN3vwkdmlloU,111
2
2
  stacktrace_cli/__main__.py,sha256=F_tqj3PRzeBtY-uvBs923H8oc-s9bebhIh6UnAUwNYc,448
3
3
  stacktrace_cli/analysis.py,sha256=cNsFO0Awr2gxzQWfEC1befhQ-6XOZDcv0VMmR5QYFug,23766
4
4
  stacktrace_cli/build.py,sha256=lXMXBMoQtXC5qVMAWBsJavcVfXPmJiAZgV3cRBqUvyk,4846
@@ -15,16 +15,17 @@ stacktrace_cli/correlate/project_map.py,sha256=R37SktEsBzVPufQZmQeYWm9PoO0YGg98Y
15
15
  stacktrace_cli/correlate/record.py,sha256=ggpEJaQNyuUqjNcE2FuskivTgNhIBmqnxWncpigRT50,12057
16
16
  stacktrace_cli/correlate/render.py,sha256=hJZhZcqE4bKAeXIegddY7uViCyp7NAPTQl1cj9YfM68,35387
17
17
  stacktrace_cli/daemon/__init__.py,sha256=YP0eKpNcRl1GzKO1GXh9gOoigViU2duV-PEOw0lLLwQ,59
18
- stacktrace_cli/daemon/cli.py,sha256=KY6q0YZxoIktDgCn9a3qroSxmVMtkyTzzticGk1MMuQ,3034
18
+ stacktrace_cli/daemon/cli.py,sha256=vDUF93touwMFKEH1lEv8J5cHCermBGu8hCmqJJqyJ58,2932
19
19
  stacktrace_cli/daemon/client.py,sha256=DRiyuhlPn4S4XFzzegCBZJmhkcMjL6N8xu1ETDPZq6c,4751
20
20
  stacktrace_cli/daemon/composition.py,sha256=3SiDqtfKpW5lhpN3H9SYVGQzhSotRvrt-o_3kd41D54,4286
21
21
  stacktrace_cli/daemon/observer.py,sha256=uc8AriC4xPvSENg4bdWSI7vtF1vjA4GmcWANZZvcziQ,6761
22
22
  stacktrace_cli/daemon/paths.py,sha256=fDmGavMvSQg_UbJTmamwmOUBwTm18NUeI_Lo2JFMtGI,1380
23
+ stacktrace_cli/daemon/presentation.py,sha256=LPm9ibm4Kw42RtB3H4PNT3iJgHUyWn-rlhgvymmlHrk,2751
23
24
  stacktrace_cli/daemon/protocol.py,sha256=s6VGko72dWOW0V2iDq0WTpYQmWV2l4Q-97bU_NR5Xo4,762
24
25
  stacktrace_cli/daemon/runtime.py,sha256=cJusGYyWKo-4hp1bKYgDPCgvBFKHptX6DEv2GqeNcXg,8115
25
26
  stacktrace_cli/daemon/server.py,sha256=pMNoJ8BOTNAhyJigM_WHwDOczjJtyN_YYaNUs5M6qic,5986
26
27
  stacktrace_cli/daemon/service.py,sha256=ULUWfS0hvjQ2j9Lk0u_tofpFbfLDMUcXrPYOircSld0,6128
27
- stacktrace_cli/daemon/store.py,sha256=FnpmkPP1uhWrsQ6BGNTmu4QSvYFbJvMkz_Rqd61LCq0,14346
28
+ stacktrace_cli/daemon/store.py,sha256=uS3QcLUN48RiVje3lnvHEs_zrtpRYg7ly1VQph8cr9M,14788
28
29
  stacktrace_cli/detector/__init__.py,sha256=uUwte-wGAjsXDlTgYClDtYtmZSj3-X52U_bRHIZzICw,69
29
30
  stacktrace_cli/detector/analyzer.py,sha256=HGBJmXI8_9Z8iDink1YXTMMuIoyUYWOSVc0qeNzWR00,8437
30
31
  stacktrace_cli/detector/blocked.py,sha256=Bd88-ADzTlWV-Bd5JH7DPRrlJHxh6KwoAe2tlDd-drA,24377
@@ -84,7 +85,7 @@ stacktrace_cli/telemetry/cli.py,sha256=YgNFWxa24BFuq20uW7VdNRBaSJO7fKsvmMQYR8yo9
84
85
  stacktrace_cli/telemetry/events.py,sha256=VF_My0Nu9wLT9qk2FQfMgPjJob9uVbDtIHjBJ4ju5pw,10690
85
86
  stacktrace_cli/telemetry/posthog.py,sha256=8Bu5AwhVUcvV-4pqTdybWUfOGqPR-7W7apmSjkX3_94,4705
86
87
  stacktrace_cli/telemetry/state.py,sha256=-7UmK8bBFj3Zh3bzX0kJwxn7-CvreMar_0x1NiZghkE,7242
87
- stacktrace_cli-0.4.1.dist-info/METADATA,sha256=KQUHzPqcvqETFW66LsToGUc77HL08B33wjt-P9ypvZ0,7763
88
- stacktrace_cli-0.4.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
89
- stacktrace_cli-0.4.1.dist-info/entry_points.txt,sha256=OYDmb2CtEjd8TV78zxiGB1XVrLrC6vvayAPXa79_tJ0,60
90
- stacktrace_cli-0.4.1.dist-info/RECORD,,
88
+ stacktrace_cli-0.5.0.dist-info/METADATA,sha256=YO4ib9vOki1qJ2mTuUkhjYv15tORfuIGX3whJy9R0YY,7763
89
+ stacktrace_cli-0.5.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
90
+ stacktrace_cli-0.5.0.dist-info/entry_points.txt,sha256=OYDmb2CtEjd8TV78zxiGB1XVrLrC6vvayAPXa79_tJ0,60
91
+ stacktrace_cli-0.5.0.dist-info/RECORD,,