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.
Files changed (42) hide show
  1. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/PKG-INFO +5 -5
  2. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/pyproject.toml +11 -5
  3. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/__init__.py +4 -4
  4. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/cli.py +509 -6
  5. pisama_claude_code-0.4.3/src/pisama_claude_code/install.py +342 -0
  6. pisama_claude_code-0.4.3/src/pisama_claude_code/sample_traces/__init__.py +5 -0
  7. pisama_claude_code-0.4.3/src/pisama_claude_code/sample_traces/demo_loop.jsonl +5 -0
  8. pisama_claude_code-0.4.2/.github/ISSUE_TEMPLATE/bug_report.yml +0 -117
  9. pisama_claude_code-0.4.2/.github/ISSUE_TEMPLATE/config.yml +0 -8
  10. pisama_claude_code-0.4.2/.github/ISSUE_TEMPLATE/feature_request.yml +0 -93
  11. pisama_claude_code-0.4.2/.github/PULL_REQUEST_TEMPLATE.md +0 -61
  12. pisama_claude_code-0.4.2/.github/workflows/ci.yml +0 -80
  13. pisama_claude_code-0.4.2/.github/workflows/publish.yml +0 -68
  14. pisama_claude_code-0.4.2/CHANGELOG.md +0 -115
  15. pisama_claude_code-0.4.2/CODE_OF_CONDUCT.md +0 -133
  16. pisama_claude_code-0.4.2/CONTRIBUTING.md +0 -168
  17. pisama_claude_code-0.4.2/SECURITY.md +0 -97
  18. pisama_claude_code-0.4.2/assets/demo.gif +0 -0
  19. pisama_claude_code-0.4.2/demo/README.md +0 -78
  20. pisama_claude_code-0.4.2/demo/demo-simulated.sh +0 -131
  21. pisama_claude_code-0.4.2/demo/demo.cast +0 -153
  22. pisama_claude_code-0.4.2/demo/record-demo.sh +0 -82
  23. pisama_claude_code-0.4.2/src/pisama_claude_code/install.py +0 -181
  24. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/.gitignore +0 -0
  25. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/LICENSE +0 -0
  26. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/README.md +0 -0
  27. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/adapter.py +0 -0
  28. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/guardian.py +0 -0
  29. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/hooks/__init__.py +0 -0
  30. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/hooks/capture_hook.py +0 -0
  31. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/hooks/guardian_hook.py +0 -0
  32. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/otel_export.py +0 -0
  33. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/py.typed +0 -0
  34. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/storage.py +0 -0
  35. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/trace_converter.py +0 -0
  36. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/src/pisama_claude_code/trace_types.py +0 -0
  37. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/__init__.py +0 -0
  38. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/conftest.py +0 -0
  39. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_adapter.py +0 -0
  40. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_cli.py +0 -0
  41. {pisama_claude_code-0.4.2 → pisama_claude_code-0.4.3}/tests/test_guardian.py +0 -0
  42. {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.2
4
- Summary: Trace capture for Claude Code sessions - sync to PISAMA platform for analysis
5
- Project-URL: Homepage, https://pisama.dev
6
- Project-URL: Documentation, https://pisama.dev/docs/claude-code
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: PISAMA Team <team@pisama.dev>
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.2"
8
- description = "Trace capture for Claude Code sessions - sync to PISAMA platform for analysis"
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 = "PISAMA Team", email = "team@pisama.dev" }
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.dev"
84
- Documentation = "https://pisama.dev/docs/claude-code"
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
- """PISAMA Claude Code Integration - Trace capture, failure detection, and self-healing."""
1
+ """Pisama Claude Code Integration - Trace capture, failure detection, and self-healing."""
2
2
 
3
- __version__ = "0.4.0"
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 PISAMA hooks to ~/.claude/hooks/."""
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 PISAMA hooks from ~/.claude/hooks/."""
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.0")
73
+ @click.version_option(version="0.4.3")
62
74
  def main():
63
- """PISAMA Claude Code - Trace capture and sync."""
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
- def install(force: bool):
73
- """Install PISAMA hooks to ~/.claude/hooks/."""
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."""