pisama-claude-code 0.4.2__tar.gz → 0.4.3__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.
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/PKG-INFO +5 -5
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/pyproject.toml +11 -5
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/__init__.py +4 -4
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/cli.py +509 -6
- pisama_claude_code-0.4.3/src/pisama_claude_code/install.py +342 -0
- pisama_claude_code-0.4.3/src/pisama_claude_code/sample_traces/__init__.py +5 -0
- pisama_claude_code-0.4.3/src/pisama_claude_code/sample_traces/demo_loop.jsonl +5 -0
- pisama_claude_code-0.4.2/.github/ISSUE_TEMPLATE/bug_report.yml +0 -117
- pisama_claude_code-0.4.2/.github/ISSUE_TEMPLATE/config.yml +0 -8
- pisama_claude_code-0.4.2/.github/ISSUE_TEMPLATE/feature_request.yml +0 -93
- pisama_claude_code-0.4.2/.github/PULL_REQUEST_TEMPLATE.md +0 -61
- pisama_claude_code-0.4.2/.github/workflows/ci.yml +0 -80
- pisama_claude_code-0.4.2/.github/workflows/publish.yml +0 -68
- pisama_claude_code-0.4.2/CHANGELOG.md +0 -115
- pisama_claude_code-0.4.2/CODE_OF_CONDUCT.md +0 -133
- pisama_claude_code-0.4.2/CONTRIBUTING.md +0 -168
- pisama_claude_code-0.4.2/SECURITY.md +0 -97
- pisama_claude_code-0.4.2/assets/demo.gif +0 -0
- pisama_claude_code-0.4.2/demo/README.md +0 -78
- pisama_claude_code-0.4.2/demo/demo-simulated.sh +0 -131
- pisama_claude_code-0.4.2/demo/demo.cast +0 -153
- pisama_claude_code-0.4.2/demo/record-demo.sh +0 -82
- pisama_claude_code-0.4.2/src/pisama_claude_code/install.py +0 -181
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/.gitignore +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/LICENSE +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/README.md +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/adapter.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/guardian.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/hooks/__init__.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/hooks/capture_hook.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/hooks/guardian_hook.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/otel_export.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/py.typed +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/storage.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/trace_converter.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/trace_types.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/__init__.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/conftest.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_adapter.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_cli.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_guardian.py +0 -0
- {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_storage.py +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pisama-claude-code
|
|
3
|
-
Version: 0.4.
|
|
4
|
-
Summary: Trace capture for Claude Code sessions - sync to
|
|
5
|
-
Project-URL: Homepage, https://pisama.
|
|
6
|
-
Project-URL: Documentation, https://pisama.
|
|
3
|
+
Version: 0.4.3
|
|
4
|
+
Summary: Trace capture for Claude Code sessions - sync to Pisama platform for analysis
|
|
5
|
+
Project-URL: Homepage, https://pisama.ai
|
|
6
|
+
Project-URL: Documentation, https://pisama.ai/docs/claude-code
|
|
7
7
|
Project-URL: Repository, https://github.com/tn-pisama/pisama-claude-code
|
|
8
8
|
Project-URL: Issues, https://github.com/tn-pisama/pisama-claude-code/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/tn-pisama/pisama-claude-code/blob/main/CHANGELOG.md
|
|
10
10
|
Project-URL: Discussions, https://github.com/tn-pisama/pisama-claude-code/discussions
|
|
11
11
|
Author: Tuomo Nikulainen
|
|
12
|
-
Maintainer-email:
|
|
12
|
+
Maintainer-email: Pisama Team <team@pisama.ai>
|
|
13
13
|
License-Expression: MIT
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Keywords: agent,ai-agent,anthropic,claude,claude-code,debugging,developer-tools,forensics,llm,monitoring,observability,pisama,self-healing,tracing
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pisama-claude-code"
|
|
7
|
-
version = "0.4.
|
|
8
|
-
description = "Trace capture for Claude Code sessions - sync to
|
|
7
|
+
version = "0.4.3"
|
|
8
|
+
description = "Trace capture for Claude Code sessions - sync to Pisama platform for analysis"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -13,7 +13,7 @@ authors = [
|
|
|
13
13
|
{ name = "Tuomo Nikulainen" }
|
|
14
14
|
]
|
|
15
15
|
maintainers = [
|
|
16
|
-
{ name = "
|
|
16
|
+
{ name = "Pisama Team", email = "team@pisama.ai" }
|
|
17
17
|
]
|
|
18
18
|
keywords = [
|
|
19
19
|
"claude",
|
|
@@ -80,8 +80,8 @@ all = [
|
|
|
80
80
|
pisama-cc = "pisama_claude_code.cli:main"
|
|
81
81
|
|
|
82
82
|
[project.urls]
|
|
83
|
-
Homepage = "https://pisama.
|
|
84
|
-
Documentation = "https://pisama.
|
|
83
|
+
Homepage = "https://pisama.ai"
|
|
84
|
+
Documentation = "https://pisama.ai/docs/claude-code"
|
|
85
85
|
Repository = "https://github.com/tn-pisama/pisama-claude-code"
|
|
86
86
|
Issues = "https://github.com/tn-pisama/pisama-claude-code/issues"
|
|
87
87
|
Changelog = "https://github.com/tn-pisama/pisama-claude-code/blob/main/CHANGELOG.md"
|
|
@@ -90,6 +90,12 @@ Discussions = "https://github.com/tn-pisama/pisama-claude-code/discussions"
|
|
|
90
90
|
[tool.hatch.build.targets.wheel]
|
|
91
91
|
packages = ["src/pisama_claude_code"]
|
|
92
92
|
|
|
93
|
+
[tool.hatch.build.targets.sdist]
|
|
94
|
+
include = [
|
|
95
|
+
"/src",
|
|
96
|
+
"/tests",
|
|
97
|
+
]
|
|
98
|
+
|
|
93
99
|
[tool.pytest.ini_options]
|
|
94
100
|
asyncio_mode = "auto"
|
|
95
101
|
testpaths = ["tests"]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Pisama Claude Code Integration - Trace capture, failure detection, and self-healing."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.4.
|
|
3
|
+
__version__ = "0.4.3"
|
|
4
4
|
|
|
5
5
|
# Lazy imports to avoid loading everything at startup
|
|
6
6
|
def install(force: bool = False):
|
|
7
|
-
"""Install
|
|
7
|
+
"""Install Pisama hooks to ~/.claude/hooks/."""
|
|
8
8
|
from .install import install as _install
|
|
9
9
|
return _install(force=force)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def uninstall():
|
|
13
|
-
"""Remove
|
|
13
|
+
"""Remove Pisama hooks from ~/.claude/hooks/."""
|
|
14
14
|
from .install import uninstall as _uninstall
|
|
15
15
|
return _uninstall()
|
|
16
16
|
|
|
@@ -3,18 +3,30 @@
|
|
|
3
3
|
Command-line interface for capturing Claude Code traces and syncing
|
|
4
4
|
to the PISAMA platform for analysis and self-healing.
|
|
5
5
|
|
|
6
|
+
Quick Start (3 lines!):
|
|
7
|
+
pip install pisama-claude-code # Install package
|
|
8
|
+
pisama-cc install # Install hooks + auto-config
|
|
9
|
+
pisama-cc verify # Confirm everything works
|
|
10
|
+
|
|
6
11
|
Usage:
|
|
7
|
-
pisama-cc install Install hooks to ~/.claude/
|
|
12
|
+
pisama-cc install Install hooks to ~/.claude/ (auto-updates settings)
|
|
13
|
+
pisama-cc verify Verify installation is working
|
|
8
14
|
pisama-cc uninstall Remove hooks
|
|
9
15
|
pisama-cc status Show current status (incl. token/cost totals)
|
|
10
16
|
pisama-cc traces View recent traces (-v for token usage)
|
|
11
17
|
pisama-cc usage Show token usage and cost breakdown
|
|
18
|
+
pisama-cc demo Run demo detection on sample traces
|
|
12
19
|
pisama-cc export Export traces to file (JSONL or OTEL format)
|
|
13
20
|
pisama-cc export-otel Export traces to OpenTelemetry collector
|
|
14
21
|
pisama-cc connect Connect to PISAMA platform
|
|
15
22
|
pisama-cc sync Sync traces to platform
|
|
16
23
|
pisama-cc analyze Analyze traces (requires platform)
|
|
17
24
|
|
|
25
|
+
Self-Healing:
|
|
26
|
+
pisama-cc fix list List available fixes
|
|
27
|
+
pisama-cc fix show FIX_ID Show fix details
|
|
28
|
+
pisama-cc fix apply FIX_ID Apply a suggested fix
|
|
29
|
+
|
|
18
30
|
OTEL Export:
|
|
19
31
|
pisama-cc export --format otel -o traces.json
|
|
20
32
|
pisama-cc export-otel -e http://localhost:4318/v1/traces
|
|
@@ -58,9 +70,9 @@ def save_config(config: dict):
|
|
|
58
70
|
|
|
59
71
|
|
|
60
72
|
@click.group()
|
|
61
|
-
@click.version_option(version="0.4.
|
|
73
|
+
@click.version_option(version="0.4.3")
|
|
62
74
|
def main():
|
|
63
|
-
"""
|
|
75
|
+
"""Pisama Claude Code - Trace capture and sync."""
|
|
64
76
|
pass
|
|
65
77
|
|
|
66
78
|
|
|
@@ -69,10 +81,32 @@ GITHUB_URL = "https://github.com/tn-pisama/pisama-claude-code"
|
|
|
69
81
|
|
|
70
82
|
@main.command()
|
|
71
83
|
@click.option("--force", "-f", is_flag=True, help="Overwrite existing hooks")
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
@click.option("--no-auto-config", is_flag=True, help="Don't auto-update settings.local.json")
|
|
85
|
+
def install(force: bool, no_auto_config: bool):
|
|
86
|
+
"""Install PISAMA hooks to ~/.claude/hooks/.
|
|
87
|
+
|
|
88
|
+
By default, automatically updates settings.local.json to enable hooks.
|
|
89
|
+
Use --no-auto-config to skip automatic configuration.
|
|
90
|
+
"""
|
|
74
91
|
from pisama_claude_code.install import install as do_install
|
|
75
|
-
do_install(force=force)
|
|
92
|
+
do_install(force=force, auto_config=not no_auto_config)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@main.command()
|
|
96
|
+
def verify():
|
|
97
|
+
"""Verify PISAMA installation is working correctly.
|
|
98
|
+
|
|
99
|
+
Checks:
|
|
100
|
+
- Hook files exist and are executable
|
|
101
|
+
- settings.local.json has PISAMA hooks configured
|
|
102
|
+
- All required directories exist
|
|
103
|
+
|
|
104
|
+
Exit code 0 if all checks pass, 1 otherwise.
|
|
105
|
+
"""
|
|
106
|
+
from pisama_claude_code.install import verify as do_verify
|
|
107
|
+
import sys
|
|
108
|
+
success = do_verify()
|
|
109
|
+
sys.exit(0 if success else 1)
|
|
76
110
|
|
|
77
111
|
|
|
78
112
|
@main.command()
|
|
@@ -82,6 +116,208 @@ def uninstall():
|
|
|
82
116
|
do_uninstall()
|
|
83
117
|
|
|
84
118
|
|
|
119
|
+
@main.command()
|
|
120
|
+
@click.option("--verbose", "-v", is_flag=True, help="Show detailed detection info")
|
|
121
|
+
def demo(verbose: bool):
|
|
122
|
+
"""Run a demo detection on sample traces.
|
|
123
|
+
|
|
124
|
+
This demonstrates PISAMA's failure detection capabilities without
|
|
125
|
+
requiring platform connection. Uses bundled sample traces that
|
|
126
|
+
showcase common agent failures.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
pisama-cc demo # Quick demo
|
|
130
|
+
pisama-cc demo -v # Verbose with details
|
|
131
|
+
"""
|
|
132
|
+
import importlib.resources
|
|
133
|
+
|
|
134
|
+
click.echo("")
|
|
135
|
+
click.echo("🚀 PISAMA Demo - Instant Failure Detection")
|
|
136
|
+
click.echo("=" * 55)
|
|
137
|
+
click.echo("")
|
|
138
|
+
|
|
139
|
+
# Load sample trace from package
|
|
140
|
+
try:
|
|
141
|
+
# Python 3.9+ compatible way to get package resources
|
|
142
|
+
try:
|
|
143
|
+
from importlib.resources import files
|
|
144
|
+
sample_path = files("pisama_claude_code.sample_traces").joinpath("demo_loop.jsonl")
|
|
145
|
+
sample_content = sample_path.read_text()
|
|
146
|
+
except (ImportError, TypeError):
|
|
147
|
+
# Fallback for older Python
|
|
148
|
+
import pkg_resources
|
|
149
|
+
sample_content = pkg_resources.resource_string(
|
|
150
|
+
"pisama_claude_code", "sample_traces/demo_loop.jsonl"
|
|
151
|
+
).decode("utf-8")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
click.echo(f"❌ Could not load sample traces: {e}")
|
|
154
|
+
click.echo(" Try reinstalling: pip install --upgrade pisama-claude-code")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
# Parse traces
|
|
158
|
+
traces = []
|
|
159
|
+
for line in sample_content.strip().split("\n"):
|
|
160
|
+
if line.strip():
|
|
161
|
+
try:
|
|
162
|
+
traces.append(json.loads(line))
|
|
163
|
+
except json.JSONDecodeError:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
if not traces:
|
|
167
|
+
click.echo("❌ No valid traces in sample file")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
click.echo(f"📥 Loaded {len(traces)} sample tool calls")
|
|
171
|
+
click.echo("")
|
|
172
|
+
|
|
173
|
+
# Run local loop detection
|
|
174
|
+
click.echo("🔍 Analyzing for failures...")
|
|
175
|
+
click.echo("")
|
|
176
|
+
|
|
177
|
+
detections = _run_local_detection(traces)
|
|
178
|
+
|
|
179
|
+
if not detections:
|
|
180
|
+
click.echo("✅ No failures detected in sample trace")
|
|
181
|
+
click.echo("")
|
|
182
|
+
click.echo("Try running on your own traces:")
|
|
183
|
+
click.echo(" pisama-cc traces # View captured traces")
|
|
184
|
+
click.echo(" pisama-cc analyze # Analyze with platform (requires connection)")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
# Display detections with explanations
|
|
188
|
+
for i, detection in enumerate(detections, 1):
|
|
189
|
+
severity = detection.get("severity", 50)
|
|
190
|
+
if severity >= 70:
|
|
191
|
+
icon = "🔴"
|
|
192
|
+
elif severity >= 40:
|
|
193
|
+
icon = "🟡"
|
|
194
|
+
else:
|
|
195
|
+
icon = "🟢"
|
|
196
|
+
|
|
197
|
+
click.echo(f"{icon} Detection #{i}: {detection.get('type', 'Unknown Failure')}")
|
|
198
|
+
click.echo(f" Confidence: {detection.get('confidence', 0)}%")
|
|
199
|
+
click.echo("")
|
|
200
|
+
|
|
201
|
+
# Plain English explanation
|
|
202
|
+
explanation = detection.get("explanation", "")
|
|
203
|
+
if explanation:
|
|
204
|
+
click.echo(f" 📝 {explanation}")
|
|
205
|
+
click.echo("")
|
|
206
|
+
|
|
207
|
+
# Business impact
|
|
208
|
+
impact = detection.get("business_impact", "")
|
|
209
|
+
if impact:
|
|
210
|
+
click.echo(f" ⚠️ Impact: {impact}")
|
|
211
|
+
|
|
212
|
+
# Suggested action
|
|
213
|
+
action = detection.get("suggested_action", "")
|
|
214
|
+
if action:
|
|
215
|
+
click.echo(f" 💡 Fix: {action}")
|
|
216
|
+
|
|
217
|
+
click.echo("")
|
|
218
|
+
|
|
219
|
+
# Verbose details
|
|
220
|
+
if verbose and detection.get("details"):
|
|
221
|
+
click.echo(" Technical Details:")
|
|
222
|
+
for key, value in detection.get("details", {}).items():
|
|
223
|
+
click.echo(f" {key}: {value}")
|
|
224
|
+
click.echo("")
|
|
225
|
+
|
|
226
|
+
# Celebration and next steps
|
|
227
|
+
click.echo("=" * 55)
|
|
228
|
+
click.echo("🎉 First detection complete!")
|
|
229
|
+
click.echo("")
|
|
230
|
+
click.echo("📋 Next Steps:")
|
|
231
|
+
click.echo(" 1. Install hooks to capture your traces:")
|
|
232
|
+
click.echo(" pisama-cc install")
|
|
233
|
+
click.echo("")
|
|
234
|
+
click.echo(" 2. Use Claude Code normally - traces are captured automatically")
|
|
235
|
+
click.echo("")
|
|
236
|
+
click.echo(" 3. View your traces:")
|
|
237
|
+
click.echo(" pisama-cc traces")
|
|
238
|
+
click.echo("")
|
|
239
|
+
click.echo(" 4. Connect to platform for advanced analysis:")
|
|
240
|
+
click.echo(" pisama-cc connect --api-key <your-key>")
|
|
241
|
+
click.echo("")
|
|
242
|
+
click.echo(f"Star us on GitHub: {GITHUB_URL}")
|
|
243
|
+
click.echo("")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _run_local_detection(traces: list) -> list:
|
|
247
|
+
"""Run basic local detection without platform connection.
|
|
248
|
+
|
|
249
|
+
Currently supports:
|
|
250
|
+
- Loop detection (exact message repetition)
|
|
251
|
+
- Tool loop detection (same tool repeated)
|
|
252
|
+
"""
|
|
253
|
+
detections = []
|
|
254
|
+
|
|
255
|
+
# Check for loop detection
|
|
256
|
+
if len(traces) >= 3:
|
|
257
|
+
# Check for exact message loops
|
|
258
|
+
tool_calls = [(t.get("tool_name", ""), json.dumps(t.get("tool_input", {}), sort_keys=True)) for t in traces]
|
|
259
|
+
|
|
260
|
+
# Find repeated sequences
|
|
261
|
+
consecutive_same = 1
|
|
262
|
+
max_consecutive = 1
|
|
263
|
+
repeated_tool = None
|
|
264
|
+
repeated_input = None
|
|
265
|
+
|
|
266
|
+
for i in range(1, len(tool_calls)):
|
|
267
|
+
if tool_calls[i] == tool_calls[i - 1]:
|
|
268
|
+
consecutive_same += 1
|
|
269
|
+
if consecutive_same > max_consecutive:
|
|
270
|
+
max_consecutive = consecutive_same
|
|
271
|
+
repeated_tool = tool_calls[i][0]
|
|
272
|
+
repeated_input = tool_calls[i][1]
|
|
273
|
+
else:
|
|
274
|
+
consecutive_same = 1
|
|
275
|
+
|
|
276
|
+
# Report loop if 3+ consecutive identical calls
|
|
277
|
+
if max_consecutive >= 3:
|
|
278
|
+
confidence = min(95, 70 + (max_consecutive - 3) * 5)
|
|
279
|
+
severity = min(80, 40 + max_consecutive * 5)
|
|
280
|
+
|
|
281
|
+
detections.append({
|
|
282
|
+
"type": "Tool Loop Detected",
|
|
283
|
+
"confidence": confidence,
|
|
284
|
+
"severity": severity,
|
|
285
|
+
"explanation": f"Your agent called '{repeated_tool}' {max_consecutive} times in a row with identical parameters. This indicates the agent is stuck repeating the same action without making progress.",
|
|
286
|
+
"business_impact": "This wastes compute resources and API credits. The agent will likely time out without completing the task.",
|
|
287
|
+
"suggested_action": f"Add a retry limit (recommend max 3 attempts) for '{repeated_tool}'. Consider caching results to avoid duplicate calls.",
|
|
288
|
+
"details": {
|
|
289
|
+
"tool_name": repeated_tool,
|
|
290
|
+
"repetition_count": max_consecutive,
|
|
291
|
+
"detection_method": "exact_hash_match",
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
# Check for semantic loops (same tool, different params but same pattern)
|
|
296
|
+
tool_sequence = [t.get("tool_name", "") for t in traces]
|
|
297
|
+
tool_counts = {}
|
|
298
|
+
for tool in tool_sequence:
|
|
299
|
+
tool_counts[tool] = tool_counts.get(tool, 0) + 1
|
|
300
|
+
|
|
301
|
+
for tool, count in tool_counts.items():
|
|
302
|
+
if count >= 4 and count / len(traces) > 0.6:
|
|
303
|
+
# High concentration of one tool
|
|
304
|
+
detections.append({
|
|
305
|
+
"type": "Tool Overuse Pattern",
|
|
306
|
+
"confidence": 75,
|
|
307
|
+
"severity": 45,
|
|
308
|
+
"explanation": f"'{tool}' was called {count} times ({int(count/len(traces)*100)}% of all calls). This suggests the agent may be over-relying on one approach.",
|
|
309
|
+
"business_impact": "The agent might be missing more effective strategies or tools for the task.",
|
|
310
|
+
"suggested_action": f"Review why '{tool}' is being called so frequently. Consider adding alternative approaches or result caching.",
|
|
311
|
+
"details": {
|
|
312
|
+
"tool_name": tool,
|
|
313
|
+
"call_count": count,
|
|
314
|
+
"percentage": f"{int(count/len(traces)*100)}%",
|
|
315
|
+
},
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
return detections
|
|
319
|
+
|
|
320
|
+
|
|
85
321
|
@main.command()
|
|
86
322
|
@click.option("--last", default=20, help="Number of traces to show")
|
|
87
323
|
@click.option("--tool", help="Filter by tool name")
|
|
@@ -344,6 +580,273 @@ def display_analysis_results(results: dict):
|
|
|
344
580
|
click.echo(f"View details at: {results.get('dashboard_url', 'https://app.maotesting.com')}")
|
|
345
581
|
|
|
346
582
|
|
|
583
|
+
# =============================================================================
|
|
584
|
+
# FIX COMMANDS - Self-Healing Fix Application
|
|
585
|
+
# =============================================================================
|
|
586
|
+
|
|
587
|
+
@main.group()
|
|
588
|
+
def fix():
|
|
589
|
+
"""Self-healing fix commands."""
|
|
590
|
+
pass
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
@fix.command("list")
|
|
594
|
+
@click.option("--detection-id", help="Show fixes for specific detection")
|
|
595
|
+
def fix_list(detection_id: Optional[str]):
|
|
596
|
+
"""List available fixes for detected issues."""
|
|
597
|
+
if httpx is None:
|
|
598
|
+
click.echo("❌ httpx required. Run: pip install httpx")
|
|
599
|
+
return
|
|
600
|
+
|
|
601
|
+
config = get_config()
|
|
602
|
+
|
|
603
|
+
if not config.get("api_key"):
|
|
604
|
+
click.echo("❌ Fix listing requires platform connection")
|
|
605
|
+
click.echo(" Run: pisama-cc connect --api-key <your-key>")
|
|
606
|
+
return
|
|
607
|
+
|
|
608
|
+
click.echo("🔧 Available Fixes")
|
|
609
|
+
click.echo("=" * 60)
|
|
610
|
+
|
|
611
|
+
try:
|
|
612
|
+
# Get recent detections with fixes
|
|
613
|
+
endpoint = f"{config['api_url']}/v1/detections"
|
|
614
|
+
if detection_id:
|
|
615
|
+
endpoint = f"{config['api_url']}/v1/detections/{detection_id}/fixes"
|
|
616
|
+
|
|
617
|
+
response = httpx.get(
|
|
618
|
+
endpoint,
|
|
619
|
+
headers={"Authorization": f"Bearer {config['api_key']}"},
|
|
620
|
+
timeout=30,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
if response.status_code == 200:
|
|
624
|
+
data = response.json()
|
|
625
|
+
if detection_id:
|
|
626
|
+
# Single detection fixes
|
|
627
|
+
suggestions = data.get("suggestions", [])
|
|
628
|
+
if suggestions:
|
|
629
|
+
for i, fix_data in enumerate(suggestions, 1):
|
|
630
|
+
click.echo(f"\n{i}. {fix_data.get('title', 'Unnamed Fix')}")
|
|
631
|
+
click.echo(f" ID: {fix_data.get('id', 'unknown')}")
|
|
632
|
+
click.echo(f" Type: {fix_data.get('fix_type', 'unknown')}")
|
|
633
|
+
click.echo(f" Confidence: {fix_data.get('confidence', 'unknown')}")
|
|
634
|
+
click.echo(f" {fix_data.get('description', '')[:100]}")
|
|
635
|
+
else:
|
|
636
|
+
click.echo("No fixes available for this detection")
|
|
637
|
+
else:
|
|
638
|
+
# List detections
|
|
639
|
+
detections = data if isinstance(data, list) else data.get("detections", [])
|
|
640
|
+
for d in detections[:10]:
|
|
641
|
+
click.echo(f"\n• {d.get('detection_type', 'unknown')} [{d.get('id', '')[:8]}]")
|
|
642
|
+
click.echo(f" Confidence: {d.get('confidence', 0)}%")
|
|
643
|
+
click.echo(f" Run: pisama-cc fix list --detection-id {d.get('id', '')}")
|
|
644
|
+
else:
|
|
645
|
+
click.echo(f"❌ Failed to fetch fixes: {response.status_code}")
|
|
646
|
+
except httpx.ConnectError:
|
|
647
|
+
click.echo(f"❌ Could not connect to {config['api_url']}")
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
@fix.command("show")
|
|
651
|
+
@click.argument("fix_id")
|
|
652
|
+
@click.option("--detection-id", required=True, help="Detection ID for the fix")
|
|
653
|
+
def fix_show(fix_id: str, detection_id: str):
|
|
654
|
+
"""Show detailed fix information with code changes."""
|
|
655
|
+
if httpx is None:
|
|
656
|
+
click.echo("❌ httpx required. Run: pip install httpx")
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
config = get_config()
|
|
660
|
+
|
|
661
|
+
if not config.get("api_key"):
|
|
662
|
+
click.echo("❌ Fix viewing requires platform connection")
|
|
663
|
+
click.echo(" Run: pisama-cc connect --api-key <your-key>")
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
try:
|
|
667
|
+
response = httpx.get(
|
|
668
|
+
f"{config['api_url']}/v1/detections/{detection_id}/fixes",
|
|
669
|
+
headers={"Authorization": f"Bearer {config['api_key']}"},
|
|
670
|
+
timeout=30,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
if response.status_code == 200:
|
|
674
|
+
data = response.json()
|
|
675
|
+
suggestions = data.get("suggestions", [])
|
|
676
|
+
|
|
677
|
+
# Find the specific fix
|
|
678
|
+
fix_data = None
|
|
679
|
+
for s in suggestions:
|
|
680
|
+
if s.get("id") == fix_id:
|
|
681
|
+
fix_data = s
|
|
682
|
+
break
|
|
683
|
+
|
|
684
|
+
if not fix_data:
|
|
685
|
+
click.echo(f"❌ Fix {fix_id} not found")
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
click.echo("🔧 Fix Details")
|
|
689
|
+
click.echo("=" * 60)
|
|
690
|
+
click.echo(f"\nTitle: {fix_data.get('title', 'Unnamed')}")
|
|
691
|
+
click.echo(f"Type: {fix_data.get('fix_type', 'unknown')}")
|
|
692
|
+
click.echo(f"Confidence: {fix_data.get('confidence', 'unknown')}")
|
|
693
|
+
|
|
694
|
+
if fix_data.get("breaking_changes"):
|
|
695
|
+
click.echo("\n⚠️ WARNING: This fix may introduce breaking changes")
|
|
696
|
+
|
|
697
|
+
if fix_data.get("requires_testing"):
|
|
698
|
+
click.echo("ℹ️ This fix requires testing after application")
|
|
699
|
+
|
|
700
|
+
click.echo(f"\nDescription:\n{fix_data.get('description', 'No description')}")
|
|
701
|
+
click.echo(f"\nRationale:\n{fix_data.get('rationale', 'No rationale')}")
|
|
702
|
+
|
|
703
|
+
# Show code changes
|
|
704
|
+
code_changes = fix_data.get("code_changes", [])
|
|
705
|
+
if code_changes:
|
|
706
|
+
click.echo("\n" + "─" * 60)
|
|
707
|
+
click.echo("Code Changes:")
|
|
708
|
+
for change in code_changes:
|
|
709
|
+
click.echo(f"\n📄 {change.get('file_path', 'unknown')} ({change.get('language', '')})")
|
|
710
|
+
if change.get("description"):
|
|
711
|
+
click.echo(f" {change['description']}")
|
|
712
|
+
if change.get("diff"):
|
|
713
|
+
click.echo("\n" + change["diff"])
|
|
714
|
+
elif change.get("suggested_code"):
|
|
715
|
+
click.echo("\nSuggested code:")
|
|
716
|
+
click.echo(change["suggested_code"])
|
|
717
|
+
|
|
718
|
+
click.echo("\n" + "─" * 60)
|
|
719
|
+
click.echo(f"To apply: pisama-cc fix apply {fix_id} --detection-id {detection_id}")
|
|
720
|
+
else:
|
|
721
|
+
click.echo(f"❌ Failed to fetch fix details: {response.status_code}")
|
|
722
|
+
except httpx.ConnectError:
|
|
723
|
+
click.echo(f"❌ Could not connect to {config['api_url']}")
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@fix.command("apply")
|
|
727
|
+
@click.argument("fix_id")
|
|
728
|
+
@click.option("--detection-id", required=True, help="Detection ID for the fix")
|
|
729
|
+
@click.option("--dry-run", is_flag=True, help="Show what would be applied without making changes")
|
|
730
|
+
@click.option("--force", "-f", is_flag=True, help="Apply without confirmation")
|
|
731
|
+
def fix_apply(fix_id: str, detection_id: str, dry_run: bool, force: bool):
|
|
732
|
+
"""Apply a suggested fix for a detected issue.
|
|
733
|
+
|
|
734
|
+
This records the fix as applied and shows the code changes to implement.
|
|
735
|
+
The actual code changes need to be applied manually or via your IDE.
|
|
736
|
+
|
|
737
|
+
Examples:
|
|
738
|
+
|
|
739
|
+
# View a fix before applying
|
|
740
|
+
pisama-cc fix show FIX_ID --detection-id DETECTION_ID
|
|
741
|
+
|
|
742
|
+
# Apply a fix
|
|
743
|
+
pisama-cc fix apply FIX_ID --detection-id DETECTION_ID
|
|
744
|
+
|
|
745
|
+
# Dry run to see what would happen
|
|
746
|
+
pisama-cc fix apply FIX_ID --detection-id DETECTION_ID --dry-run
|
|
747
|
+
"""
|
|
748
|
+
if httpx is None:
|
|
749
|
+
click.echo("❌ httpx required. Run: pip install httpx")
|
|
750
|
+
return
|
|
751
|
+
|
|
752
|
+
config = get_config()
|
|
753
|
+
|
|
754
|
+
if not config.get("api_key"):
|
|
755
|
+
click.echo("❌ Fix application requires platform connection")
|
|
756
|
+
click.echo(" Run: pisama-cc connect --api-key <your-key>")
|
|
757
|
+
return
|
|
758
|
+
|
|
759
|
+
try:
|
|
760
|
+
# First, get the fix details
|
|
761
|
+
response = httpx.get(
|
|
762
|
+
f"{config['api_url']}/v1/detections/{detection_id}/fixes",
|
|
763
|
+
headers={"Authorization": f"Bearer {config['api_key']}"},
|
|
764
|
+
timeout=30,
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
if response.status_code != 200:
|
|
768
|
+
click.echo(f"❌ Failed to fetch fix: {response.status_code}")
|
|
769
|
+
return
|
|
770
|
+
|
|
771
|
+
data = response.json()
|
|
772
|
+
suggestions = data.get("suggestions", [])
|
|
773
|
+
|
|
774
|
+
# Find the specific fix
|
|
775
|
+
fix_data = None
|
|
776
|
+
for s in suggestions:
|
|
777
|
+
if s.get("id") == fix_id:
|
|
778
|
+
fix_data = s
|
|
779
|
+
break
|
|
780
|
+
|
|
781
|
+
if not fix_data:
|
|
782
|
+
click.echo(f"❌ Fix {fix_id} not found")
|
|
783
|
+
return
|
|
784
|
+
|
|
785
|
+
# Display fix summary
|
|
786
|
+
click.echo("🔧 Applying Fix")
|
|
787
|
+
click.echo("=" * 60)
|
|
788
|
+
click.echo(f"\nTitle: {fix_data.get('title', 'Unnamed')}")
|
|
789
|
+
click.echo(f"Type: {fix_data.get('fix_type', 'unknown')}")
|
|
790
|
+
click.echo(f"Detection: {detection_id[:8]}...")
|
|
791
|
+
|
|
792
|
+
if fix_data.get("breaking_changes"):
|
|
793
|
+
click.echo("\n⚠️ WARNING: This fix may introduce breaking changes!")
|
|
794
|
+
|
|
795
|
+
# Show code changes
|
|
796
|
+
code_changes = fix_data.get("code_changes", [])
|
|
797
|
+
if code_changes:
|
|
798
|
+
click.echo("\nCode changes to apply:")
|
|
799
|
+
for change in code_changes:
|
|
800
|
+
click.echo(f"\n📄 {change.get('file_path', 'unknown')}")
|
|
801
|
+
if change.get("diff"):
|
|
802
|
+
click.echo(change["diff"][:500])
|
|
803
|
+
if len(change.get("diff", "")) > 500:
|
|
804
|
+
click.echo("... (truncated, run 'fix show' to see full diff)")
|
|
805
|
+
|
|
806
|
+
if dry_run:
|
|
807
|
+
click.echo("\n" + "─" * 60)
|
|
808
|
+
click.echo("🔍 DRY RUN - No changes made")
|
|
809
|
+
click.echo("Remove --dry-run to apply this fix")
|
|
810
|
+
return
|
|
811
|
+
|
|
812
|
+
# Confirm application
|
|
813
|
+
if not force:
|
|
814
|
+
if not click.confirm("\nApply this fix?"):
|
|
815
|
+
click.echo("Cancelled")
|
|
816
|
+
return
|
|
817
|
+
|
|
818
|
+
# Apply the fix via API
|
|
819
|
+
apply_response = httpx.post(
|
|
820
|
+
f"{config['api_url']}/v1/detections/{detection_id}/fixes/{fix_id}/apply",
|
|
821
|
+
headers={"Authorization": f"Bearer {config['api_key']}"},
|
|
822
|
+
timeout=30,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
if apply_response.status_code in (200, 201):
|
|
826
|
+
result = apply_response.json()
|
|
827
|
+
click.echo("\n" + "=" * 60)
|
|
828
|
+
click.echo("✅ Fix recorded as applied!")
|
|
829
|
+
click.echo(f"\n{result.get('message', 'Fix applied successfully.')}")
|
|
830
|
+
|
|
831
|
+
if result.get("rollback_available"):
|
|
832
|
+
click.echo("\nRollback is available if needed.")
|
|
833
|
+
|
|
834
|
+
# Show next steps
|
|
835
|
+
click.echo("\n📋 Next Steps:")
|
|
836
|
+
click.echo("1. Review the code changes above")
|
|
837
|
+
click.echo("2. Apply the changes to your codebase")
|
|
838
|
+
click.echo("3. Test the affected code paths")
|
|
839
|
+
click.echo("4. Commit the changes")
|
|
840
|
+
else:
|
|
841
|
+
click.echo(f"\n❌ Failed to apply fix: {apply_response.status_code}")
|
|
842
|
+
click.echo(f" {apply_response.text[:200]}")
|
|
843
|
+
|
|
844
|
+
except httpx.ConnectError:
|
|
845
|
+
click.echo(f"❌ Could not connect to {config['api_url']}")
|
|
846
|
+
except Exception as e:
|
|
847
|
+
click.echo(f"❌ Error: {e}")
|
|
848
|
+
|
|
849
|
+
|
|
347
850
|
@main.command()
|
|
348
851
|
def status():
|
|
349
852
|
"""Show PISAMA installation and connection status."""
|