@musashishao/agent-kit 1.6.1 → 1.7.0
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.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/adr/ADR-TEMPLATE.md +57 -0
- package/.agent/adr/README.md +30 -0
- package/.agent/agents/backend-specialist.md +1 -1
- package/.agent/agents/devops-engineer.md +1 -1
- package/.agent/agents/performance-optimizer.md +1 -1
- package/.agent/agents/security-auditor.md +1 -1
- package/.agent/dashboard/index.html +169 -0
- package/.agent/rules/REFERENCE.md +14 -0
- package/.agent/skills/ai-incident-management/SKILL.md +517 -0
- package/.agent/skills/ai-security-guardrails/SKILL.md +405 -0
- package/.agent/skills/ai-security-guardrails/owasp-llm-top10.md +160 -0
- package/.agent/skills/ai-security-guardrails/scripts/prompt_injection_scanner.py +230 -0
- package/.agent/skills/compliance-for-ai/SKILL.md +411 -0
- package/.agent/skills/observability-patterns/SKILL.md +484 -0
- package/.agent/skills/observability-patterns/scripts/otel_validator.py +330 -0
- package/.agent/skills/opentelemetry-expert/SKILL.md +738 -0
- package/.agent/skills/opentelemetry-expert/scripts/trace_analyzer.py +351 -0
- package/.agent/skills/privacy-preserving-dev/SKILL.md +442 -0
- package/.agent/skills/privacy-preserving-dev/scripts/pii_scanner.py +285 -0
- package/.agent/workflows/autofix.md +4 -1
- package/.agent/workflows/brainstorm.md +1 -1
- package/.agent/workflows/context.md +3 -1
- package/.agent/workflows/create.md +1 -1
- package/.agent/workflows/dashboard.md +4 -1
- package/.agent/workflows/debug.md +1 -1
- package/.agent/workflows/deploy.md +1 -1
- package/.agent/workflows/enhance.md +1 -1
- package/.agent/workflows/next.md +4 -1
- package/.agent/workflows/orchestrate.md +1 -1
- package/.agent/workflows/plan.md +1 -1
- package/.agent/workflows/preview.md +1 -1
- package/.agent/workflows/quality.md +1 -1
- package/.agent/workflows/spec.md +1 -1
- package/.agent/workflows/status.md +1 -1
- package/.agent/workflows/test.md +1 -1
- package/.agent/workflows/ui-ux-pro-max.md +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill: observability-patterns
|
|
4
|
+
Script: otel_validator.py
|
|
5
|
+
Purpose: Validate OpenTelemetry instrumentation in AI applications
|
|
6
|
+
Usage: python otel_validator.py <project_path> [--output json|summary]
|
|
7
|
+
Output: JSON with instrumentation findings
|
|
8
|
+
|
|
9
|
+
This script checks for:
|
|
10
|
+
1. OpenTelemetry SDK setup
|
|
11
|
+
2. LLM call instrumentation
|
|
12
|
+
3. Proper span attributes for AI
|
|
13
|
+
4. Context propagation patterns
|
|
14
|
+
"""
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import re
|
|
18
|
+
import json
|
|
19
|
+
import argparse
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Dict, List, Any
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
|
|
24
|
+
# Fix console encoding
|
|
25
|
+
try:
|
|
26
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
27
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
28
|
+
except AttributeError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# ============================================================================
|
|
32
|
+
# CONFIGURATION
|
|
33
|
+
# ============================================================================
|
|
34
|
+
|
|
35
|
+
SKIP_DIRS = {'node_modules', '.git', 'dist', 'build', '__pycache__', '.venv', 'venv', '.next'}
|
|
36
|
+
CODE_EXTENSIONS = {'.js', '.ts', '.jsx', '.tsx', '.py', '.go'}
|
|
37
|
+
CONFIG_EXTENSIONS = {'.json', '.yaml', '.yml', '.toml'}
|
|
38
|
+
|
|
39
|
+
# Patterns to check for good observability practices
|
|
40
|
+
GOOD_PATTERNS = [
|
|
41
|
+
# OpenTelemetry setup
|
|
42
|
+
(r"@opentelemetry|opentelemetry-sdk|from opentelemetry", "otel_sdk", "OpenTelemetry SDK imported"),
|
|
43
|
+
(r"getTracer|trace\.get_tracer", "tracer_init", "Tracer initialized"),
|
|
44
|
+
(r"getMeter|metrics\.get_meter", "meter_init", "Meter initialized"),
|
|
45
|
+
|
|
46
|
+
# Span creation
|
|
47
|
+
(r"startActiveSpan|start_as_current_span|start_span", "span_creation", "Spans being created"),
|
|
48
|
+
(r"span\.setAttributes?|span\.set_attribute", "span_attributes", "Span attributes set"),
|
|
49
|
+
(r"span\.recordException|record_exception", "error_recording", "Errors recorded in spans"),
|
|
50
|
+
|
|
51
|
+
# AI-specific attributes
|
|
52
|
+
(r"llm\.|model|tokens|completion", "ai_attributes", "AI-related attributes found"),
|
|
53
|
+
(r"agent\.step|agent\.tool", "agent_tracing", "Agent tracing attributes"),
|
|
54
|
+
|
|
55
|
+
# Metrics
|
|
56
|
+
(r"createCounter|create_counter|Counter\(", "counter_metric", "Counter metrics defined"),
|
|
57
|
+
(r"createHistogram|create_histogram|Histogram\(", "histogram_metric", "Histogram metrics defined"),
|
|
58
|
+
|
|
59
|
+
# Context propagation
|
|
60
|
+
(r"propagation\.inject|inject_context", "context_inject", "Context injection found"),
|
|
61
|
+
(r"propagation\.extract|extract_context", "context_extract", "Context extraction found"),
|
|
62
|
+
|
|
63
|
+
# Logging with trace context
|
|
64
|
+
(r"trace_id|span_id|traceId|spanId", "log_correlation", "Log-trace correlation"),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# Patterns that indicate missing observability
|
|
68
|
+
MISSING_PATTERNS = [
|
|
69
|
+
# LLM calls without tracing
|
|
70
|
+
(r"openai\.|anthropic\.|llm\.|completion", "llm_call", "LLM call found"),
|
|
71
|
+
(r"fetch|axios|requests\.", "http_call", "HTTP call found"),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# Check if LLM/HTTP calls are within spans
|
|
75
|
+
SPAN_CONTEXT_PATTERN = r"(startActiveSpan|start_as_current_span|with_span).*?(openai|anthropic|llm|fetch|axios|requests)"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ============================================================================
|
|
79
|
+
# SCANNING FUNCTIONS
|
|
80
|
+
# ============================================================================
|
|
81
|
+
|
|
82
|
+
def check_package_deps(project_path: Path) -> Dict[str, Any]:
|
|
83
|
+
"""Check package.json or requirements.txt for OTel dependencies."""
|
|
84
|
+
result = {
|
|
85
|
+
"has_otel": False,
|
|
86
|
+
"packages": [],
|
|
87
|
+
"missing": []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Check package.json
|
|
91
|
+
pkg_json = project_path / "package.json"
|
|
92
|
+
if pkg_json.exists():
|
|
93
|
+
try:
|
|
94
|
+
with open(pkg_json) as f:
|
|
95
|
+
pkg = json.load(f)
|
|
96
|
+
all_deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
97
|
+
|
|
98
|
+
otel_packages = [k for k in all_deps if "opentelemetry" in k.lower()]
|
|
99
|
+
if otel_packages:
|
|
100
|
+
result["has_otel"] = True
|
|
101
|
+
result["packages"] = otel_packages
|
|
102
|
+
|
|
103
|
+
# Check for recommended packages
|
|
104
|
+
recommended = ["@opentelemetry/sdk-node", "@opentelemetry/api"]
|
|
105
|
+
result["missing"] = [r for r in recommended if r not in all_deps]
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Check requirements.txt
|
|
110
|
+
req_txt = project_path / "requirements.txt"
|
|
111
|
+
if req_txt.exists():
|
|
112
|
+
try:
|
|
113
|
+
with open(req_txt) as f:
|
|
114
|
+
content = f.read().lower()
|
|
115
|
+
if "opentelemetry" in content:
|
|
116
|
+
result["has_otel"] = True
|
|
117
|
+
result["packages"].append("opentelemetry (Python)")
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def scan_file_for_patterns(filepath: Path, patterns: List[tuple]) -> Dict[str, List[Dict]]:
|
|
125
|
+
"""Scan file for specific patterns."""
|
|
126
|
+
findings = {}
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
|
130
|
+
content = f.read()
|
|
131
|
+
lines = content.split('\n')
|
|
132
|
+
|
|
133
|
+
for pattern, key, description in patterns:
|
|
134
|
+
matches = list(re.finditer(pattern, content, re.IGNORECASE | re.DOTALL))
|
|
135
|
+
if matches:
|
|
136
|
+
if key not in findings:
|
|
137
|
+
findings[key] = []
|
|
138
|
+
|
|
139
|
+
for match in matches[:3]: # Limit to 3 per pattern
|
|
140
|
+
# Find line number
|
|
141
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
142
|
+
findings[key].append({
|
|
143
|
+
"line": line_num,
|
|
144
|
+
"snippet": lines[line_num - 1].strip()[:80] if line_num <= len(lines) else "",
|
|
145
|
+
"description": description
|
|
146
|
+
})
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
return findings
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def analyze_instrumentation(project_path: str) -> Dict[str, Any]:
|
|
154
|
+
"""Analyze project for observability instrumentation."""
|
|
155
|
+
project = Path(project_path)
|
|
156
|
+
|
|
157
|
+
results = {
|
|
158
|
+
"project": project_path,
|
|
159
|
+
"timestamp": datetime.now().isoformat(),
|
|
160
|
+
"dependencies": check_package_deps(project),
|
|
161
|
+
"instrumentation": {
|
|
162
|
+
"good_practices": {},
|
|
163
|
+
"potential_gaps": [],
|
|
164
|
+
"files_analyzed": 0
|
|
165
|
+
},
|
|
166
|
+
"recommendations": [],
|
|
167
|
+
"score": 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
all_good_findings = {}
|
|
171
|
+
llm_calls_without_traces = []
|
|
172
|
+
|
|
173
|
+
for root, dirs, files in os.walk(project):
|
|
174
|
+
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
|
|
175
|
+
|
|
176
|
+
for file in files:
|
|
177
|
+
ext = Path(file).suffix.lower()
|
|
178
|
+
if ext not in CODE_EXTENSIONS:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
filepath = Path(root) / file
|
|
182
|
+
results["instrumentation"]["files_analyzed"] += 1
|
|
183
|
+
|
|
184
|
+
# Check for good patterns
|
|
185
|
+
file_findings = scan_file_for_patterns(filepath, GOOD_PATTERNS)
|
|
186
|
+
for key, findings in file_findings.items():
|
|
187
|
+
if key not in all_good_findings:
|
|
188
|
+
all_good_findings[key] = []
|
|
189
|
+
for f in findings:
|
|
190
|
+
f["file"] = str(filepath.relative_to(project))
|
|
191
|
+
all_good_findings[key].append(f)
|
|
192
|
+
|
|
193
|
+
# Check for potential gaps (LLM calls without spans)
|
|
194
|
+
try:
|
|
195
|
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
|
196
|
+
content = f.read()
|
|
197
|
+
lines = content.split('\n')
|
|
198
|
+
|
|
199
|
+
# Find LLM/API calls
|
|
200
|
+
for pattern, key, desc in MISSING_PATTERNS:
|
|
201
|
+
for match in re.finditer(pattern, content, re.IGNORECASE):
|
|
202
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
203
|
+
|
|
204
|
+
# Check if there's a span context nearby (within 10 lines)
|
|
205
|
+
context_start = max(0, line_num - 10)
|
|
206
|
+
context_end = min(len(lines), line_num + 5)
|
|
207
|
+
context = '\n'.join(lines[context_start:context_end])
|
|
208
|
+
|
|
209
|
+
has_span = bool(re.search(r"startActiveSpan|start_as_current_span|with_span|tracer\.", context, re.IGNORECASE))
|
|
210
|
+
|
|
211
|
+
if not has_span and key == "llm_call":
|
|
212
|
+
llm_calls_without_traces.append({
|
|
213
|
+
"file": str(filepath.relative_to(project)),
|
|
214
|
+
"line": line_num,
|
|
215
|
+
"snippet": lines[line_num - 1].strip()[:80]
|
|
216
|
+
})
|
|
217
|
+
except Exception:
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
results["instrumentation"]["good_practices"] = all_good_findings
|
|
221
|
+
results["instrumentation"]["potential_gaps"] = llm_calls_without_traces[:10] # Limit output
|
|
222
|
+
|
|
223
|
+
# Calculate score
|
|
224
|
+
score = 0
|
|
225
|
+
max_score = 100
|
|
226
|
+
|
|
227
|
+
if results["dependencies"]["has_otel"]:
|
|
228
|
+
score += 20
|
|
229
|
+
if all_good_findings.get("tracer_init"):
|
|
230
|
+
score += 15
|
|
231
|
+
if all_good_findings.get("span_creation"):
|
|
232
|
+
score += 15
|
|
233
|
+
if all_good_findings.get("span_attributes"):
|
|
234
|
+
score += 10
|
|
235
|
+
if all_good_findings.get("ai_attributes"):
|
|
236
|
+
score += 10
|
|
237
|
+
if all_good_findings.get("counter_metric") or all_good_findings.get("histogram_metric"):
|
|
238
|
+
score += 10
|
|
239
|
+
if all_good_findings.get("error_recording"):
|
|
240
|
+
score += 10
|
|
241
|
+
if all_good_findings.get("log_correlation"):
|
|
242
|
+
score += 5
|
|
243
|
+
if all_good_findings.get("context_inject") or all_good_findings.get("context_extract"):
|
|
244
|
+
score += 5
|
|
245
|
+
|
|
246
|
+
# Deduct for gaps
|
|
247
|
+
if len(llm_calls_without_traces) > 0:
|
|
248
|
+
score -= min(20, len(llm_calls_without_traces) * 5)
|
|
249
|
+
|
|
250
|
+
results["score"] = max(0, score)
|
|
251
|
+
|
|
252
|
+
# Generate recommendations
|
|
253
|
+
if not results["dependencies"]["has_otel"]:
|
|
254
|
+
results["recommendations"].append("Install OpenTelemetry SDK: npm install @opentelemetry/sdk-node")
|
|
255
|
+
if not all_good_findings.get("tracer_init"):
|
|
256
|
+
results["recommendations"].append("Initialize a tracer: const tracer = trace.getTracer('service-name')")
|
|
257
|
+
if not all_good_findings.get("span_creation"):
|
|
258
|
+
results["recommendations"].append("Add spans to track operations: tracer.startActiveSpan('operation', ...)")
|
|
259
|
+
if not all_good_findings.get("ai_attributes"):
|
|
260
|
+
results["recommendations"].append("Add AI-specific attributes: llm.model, llm.tokens, agent.step")
|
|
261
|
+
if llm_calls_without_traces:
|
|
262
|
+
results["recommendations"].append(f"Wrap {len(llm_calls_without_traces)} LLM calls in spans for tracing")
|
|
263
|
+
if not all_good_findings.get("error_recording"):
|
|
264
|
+
results["recommendations"].append("Record exceptions in spans: span.recordException(error)")
|
|
265
|
+
|
|
266
|
+
# Determine status
|
|
267
|
+
if score >= 80:
|
|
268
|
+
results["status"] = "[OK] Good observability instrumentation"
|
|
269
|
+
elif score >= 50:
|
|
270
|
+
results["status"] = "[?] Partial instrumentation - improvements recommended"
|
|
271
|
+
elif score >= 20:
|
|
272
|
+
results["status"] = "[!] Basic instrumentation - significant improvements needed"
|
|
273
|
+
else:
|
|
274
|
+
results["status"] = "[!!] Missing observability - add OpenTelemetry instrumentation"
|
|
275
|
+
|
|
276
|
+
return results
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ============================================================================
|
|
280
|
+
# MAIN
|
|
281
|
+
# ============================================================================
|
|
282
|
+
|
|
283
|
+
def main():
|
|
284
|
+
parser = argparse.ArgumentParser(
|
|
285
|
+
description="Validate OpenTelemetry instrumentation in AI applications"
|
|
286
|
+
)
|
|
287
|
+
parser.add_argument("project_path", nargs="?", default=".", help="Project directory to scan")
|
|
288
|
+
parser.add_argument("--output", choices=["json", "summary"], default="json",
|
|
289
|
+
help="Output format")
|
|
290
|
+
|
|
291
|
+
args = parser.parse_args()
|
|
292
|
+
|
|
293
|
+
if not os.path.isdir(args.project_path):
|
|
294
|
+
print(json.dumps({"error": f"Directory not found: {args.project_path}"}))
|
|
295
|
+
sys.exit(1)
|
|
296
|
+
|
|
297
|
+
results = analyze_instrumentation(args.project_path)
|
|
298
|
+
|
|
299
|
+
if args.output == "summary":
|
|
300
|
+
print(f"\n{'='*60}")
|
|
301
|
+
print(f"Observability Validator: {results['project']}")
|
|
302
|
+
print(f"{'='*60}")
|
|
303
|
+
print(f"Status: {results['status']}")
|
|
304
|
+
print(f"Score: {results['score']}/100")
|
|
305
|
+
print(f"\nDependencies:")
|
|
306
|
+
print(f" OpenTelemetry installed: {'Yes' if results['dependencies']['has_otel'] else 'No'}")
|
|
307
|
+
if results['dependencies']['packages']:
|
|
308
|
+
print(f" Packages: {', '.join(results['dependencies']['packages'][:5])}")
|
|
309
|
+
|
|
310
|
+
print(f"\nInstrumentation Found:")
|
|
311
|
+
for key, findings in results['instrumentation']['good_practices'].items():
|
|
312
|
+
print(f" ✓ {key}: {len(findings)} occurrences")
|
|
313
|
+
|
|
314
|
+
if results['instrumentation']['potential_gaps']:
|
|
315
|
+
print(f"\nPotential Gaps:")
|
|
316
|
+
for gap in results['instrumentation']['potential_gaps'][:5]:
|
|
317
|
+
print(f" ⚠ {gap['file']}:{gap['line']} - LLM call without span")
|
|
318
|
+
|
|
319
|
+
if results['recommendations']:
|
|
320
|
+
print(f"\nRecommendations:")
|
|
321
|
+
for rec in results['recommendations']:
|
|
322
|
+
print(f" → {rec}")
|
|
323
|
+
|
|
324
|
+
print(f"{'='*60}\n")
|
|
325
|
+
else:
|
|
326
|
+
print(json.dumps(results, indent=2))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__":
|
|
330
|
+
main()
|