x-ipe 1.0.23__py3-none-any.whl → 1.0.25__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.
- x_ipe/app.py +32 -1
- x_ipe/handlers/terminal_handlers.py +6 -0
- x_ipe/handlers/voice_handlers.py +5 -0
- x_ipe/resources/copilot-instructions.md +19 -6
- x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
- x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
- x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
- x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
- x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
- x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
- x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
- x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
- x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
- x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
- x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
- x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
- x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
- x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
- x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
- x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
- x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
- x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
- x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
- x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
- x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
- x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
- x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
- x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
- x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
- x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
- x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
- x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
- x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
- x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
- x_ipe/routes/__init__.py +2 -0
- x_ipe/routes/ideas_routes.py +289 -0
- x_ipe/routes/kb_routes.py +80 -0
- x_ipe/routes/main_routes.py +18 -0
- x_ipe/routes/project_routes.py +7 -0
- x_ipe/routes/proxy_routes.py +10 -2
- x_ipe/routes/quality_evaluation_routes.py +193 -0
- x_ipe/routes/settings_routes.py +6 -0
- x_ipe/routes/tools_routes.py +6 -0
- x_ipe/routes/tracing_routes.py +232 -0
- x_ipe/routes/uiux_feedback_routes.py +50 -0
- x_ipe/services/__init__.py +5 -0
- x_ipe/services/config_service.py +6 -0
- x_ipe/services/file_service.py +20 -0
- x_ipe/services/homepage_service.py +160 -0
- x_ipe/services/ideas_service.py +535 -2
- x_ipe/services/kb_service.py +378 -0
- x_ipe/services/proxy_service.py +37 -7
- x_ipe/services/settings_service.py +13 -0
- x_ipe/services/skills_service.py +4 -0
- x_ipe/services/terminal_service.py +24 -0
- x_ipe/services/themes_service.py +4 -0
- x_ipe/services/tools_config_service.py +4 -0
- x_ipe/services/tracing_service.py +333 -0
- x_ipe/services/uiux_feedback_service.py +148 -1
- x_ipe/services/voice_input_service_v2.py +11 -0
- x_ipe/static/css/base.css +7 -0
- x_ipe/static/css/homepage-infinity.css +330 -0
- x_ipe/static/css/kb-core.css +301 -0
- x_ipe/static/css/quality-evaluation.css +345 -0
- x_ipe/static/css/sidebar.css +14 -4
- x_ipe/static/css/terminal.css +23 -0
- x_ipe/static/css/tracing-dashboard.css +796 -0
- x_ipe/static/css/uiux-feedback.css +7 -1
- x_ipe/static/css/workplace.css +636 -0
- x_ipe/static/img/homepage-infinity-loop.png +0 -0
- x_ipe/static/js/features/confirm-dialog.js +169 -0
- x_ipe/static/js/features/folder-view.js +742 -0
- x_ipe/static/js/features/homepage-infinity.js +314 -0
- x_ipe/static/js/features/kb-core.js +371 -0
- x_ipe/static/js/features/quality-evaluation.js +387 -0
- x_ipe/static/js/features/sidebar.js +255 -12
- x_ipe/static/js/features/tracing-dashboard.js +855 -0
- x_ipe/static/js/features/tracing-graph.js +1031 -0
- x_ipe/static/js/features/tree-drag.js +227 -0
- x_ipe/static/js/features/tree-search.js +228 -0
- x_ipe/static/js/features/workplace.js +661 -33
- x_ipe/static/js/init.js +76 -0
- x_ipe/static/js/terminal-v2.js +45 -14
- x_ipe/static/js/terminal.js +50 -49
- x_ipe/static/js/uiux-feedback.js +75 -16
- x_ipe/templates/base.html +24 -0
- x_ipe/templates/index.html +10 -1
- x_ipe/templates/knowledge-base.html +110 -0
- x_ipe/templates/workplace.html +4 -0
- x_ipe/tracing/__init__.py +37 -0
- x_ipe/tracing/buffer.py +135 -0
- x_ipe/tracing/context.py +125 -0
- x_ipe/tracing/decorator.py +288 -0
- x_ipe/tracing/middleware.py +197 -0
- x_ipe/tracing/parser.py +235 -0
- x_ipe/tracing/redactor.py +111 -0
- x_ipe/tracing/writer.py +122 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/RECORD +138 -65
- x_ipe/app.py.bak +0 -1333
- x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
- x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
- x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
- x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
- x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
- x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
- x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
x_ipe/tracing/parser.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FEATURE-023-C: Trace Viewer & DAG Visualization
|
|
3
|
+
|
|
4
|
+
TraceLogParser for parsing trace log files into visualization-ready graph structures.
|
|
5
|
+
|
|
6
|
+
Handles the log format:
|
|
7
|
+
[TRACE-START] trace_id | API | timestamp
|
|
8
|
+
[INFO] → start_function: name | input_json
|
|
9
|
+
[INFO] ← return_function: name | output_json | duration
|
|
10
|
+
[ERROR] ← exception: name | error | duration
|
|
11
|
+
[TRACE-END] trace_id | total_duration | status
|
|
12
|
+
"""
|
|
13
|
+
import re
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Any, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TraceLogParser:
|
|
19
|
+
"""
|
|
20
|
+
Parse trace log files into graph structure for visualization.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
parser = TraceLogParser()
|
|
24
|
+
result = parser.parse(Path("trace.log"))
|
|
25
|
+
# result = {"trace_id": "...", "nodes": [...], "edges": [...]}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Regex patterns for parsing log lines
|
|
29
|
+
TRACE_START_PATTERN = re.compile(
|
|
30
|
+
r'\[TRACE-START\]\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|\s*(.+)'
|
|
31
|
+
)
|
|
32
|
+
TRACE_END_PATTERN = re.compile(
|
|
33
|
+
r'\[TRACE-END\]\s*([^\|]+)\s*\|\s*(\d+)ms\s*\|\s*(\w+)'
|
|
34
|
+
)
|
|
35
|
+
FUNCTION_START_PATTERN = re.compile(
|
|
36
|
+
r'\[(INFO|DEBUG)\]\s*→\s*start_function:\s*([^\|]+)\s*\|\s*(.+)'
|
|
37
|
+
)
|
|
38
|
+
FUNCTION_RETURN_PATTERN = re.compile(
|
|
39
|
+
r'\[(INFO|DEBUG)\]\s*←\s*return_function:\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|\s*(\d+)ms'
|
|
40
|
+
)
|
|
41
|
+
EXCEPTION_PATTERN = re.compile(
|
|
42
|
+
r'\[(ERROR|INFO|DEBUG)\]\s*←\s*exception:\s*([^\|]+)\s*\|\s*([^|]+):\s*([^\|]+)\s*\|\s*(\d+)ms'
|
|
43
|
+
)
|
|
44
|
+
STACK_LINE_PATTERN = re.compile(
|
|
45
|
+
r'\s+at\s+(\w+)\s+\(([^:]+):?(\d+)?\)'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def parse(self, filepath: Path) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Parse trace log file into visualization-ready structure.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
filepath: Path to the trace log file
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dictionary with trace data:
|
|
57
|
+
{
|
|
58
|
+
"trace_id": str,
|
|
59
|
+
"api": str,
|
|
60
|
+
"timestamp": str,
|
|
61
|
+
"total_time_ms": int,
|
|
62
|
+
"status": str ("success" or "error"),
|
|
63
|
+
"nodes": [
|
|
64
|
+
{
|
|
65
|
+
"id": str,
|
|
66
|
+
"label": str,
|
|
67
|
+
"timing": str,
|
|
68
|
+
"status": str,
|
|
69
|
+
"level": str,
|
|
70
|
+
"input": str,
|
|
71
|
+
"output": str,
|
|
72
|
+
"error": dict or None
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"edges": [
|
|
76
|
+
{"source": str, "target": str}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
"""
|
|
80
|
+
result = {
|
|
81
|
+
"trace_id": "",
|
|
82
|
+
"api": "",
|
|
83
|
+
"timestamp": "",
|
|
84
|
+
"total_time_ms": 0,
|
|
85
|
+
"status": "success",
|
|
86
|
+
"nodes": [],
|
|
87
|
+
"edges": []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if not filepath.exists():
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
content = filepath.read_text()
|
|
94
|
+
if not content.strip():
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
lines = content.splitlines()
|
|
98
|
+
nodes = []
|
|
99
|
+
edges = []
|
|
100
|
+
call_stack = [] # Stack of node indices for tracking parent-child
|
|
101
|
+
node_counter = 0
|
|
102
|
+
current_error_node = None
|
|
103
|
+
stack_lines = []
|
|
104
|
+
|
|
105
|
+
for raw_line in lines:
|
|
106
|
+
# Keep original line for stack trace matching
|
|
107
|
+
line = raw_line.strip()
|
|
108
|
+
|
|
109
|
+
# Check for stack trace lines (indented with 'at')
|
|
110
|
+
stack_match = self.STACK_LINE_PATTERN.match(raw_line)
|
|
111
|
+
if stack_match and current_error_node is not None:
|
|
112
|
+
func, file_path, line_num = stack_match.groups()
|
|
113
|
+
stack_lines.append({
|
|
114
|
+
"func": func,
|
|
115
|
+
"file": file_path,
|
|
116
|
+
"line": int(line_num) if line_num else None
|
|
117
|
+
})
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# If we were collecting stack and hit non-stack line, finalize
|
|
121
|
+
if stack_lines and current_error_node is not None:
|
|
122
|
+
nodes[current_error_node]["error"]["stack"] = stack_lines
|
|
123
|
+
stack_lines = []
|
|
124
|
+
current_error_node = None
|
|
125
|
+
|
|
126
|
+
# TRACE-START
|
|
127
|
+
start_match = self.TRACE_START_PATTERN.match(line)
|
|
128
|
+
if start_match:
|
|
129
|
+
trace_id, api, timestamp = start_match.groups()
|
|
130
|
+
result["trace_id"] = trace_id.strip()
|
|
131
|
+
result["api"] = api.strip()
|
|
132
|
+
result["timestamp"] = timestamp.strip()
|
|
133
|
+
|
|
134
|
+
# Create root API node
|
|
135
|
+
nodes.append({
|
|
136
|
+
"id": f"node-{node_counter}",
|
|
137
|
+
"label": api.strip(),
|
|
138
|
+
"timing": "",
|
|
139
|
+
"status": "success",
|
|
140
|
+
"level": "API",
|
|
141
|
+
"input": "{}",
|
|
142
|
+
"output": "{}",
|
|
143
|
+
"error": None
|
|
144
|
+
})
|
|
145
|
+
call_stack.append(node_counter)
|
|
146
|
+
node_counter += 1
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# TRACE-END
|
|
150
|
+
end_match = self.TRACE_END_PATTERN.match(line)
|
|
151
|
+
if end_match:
|
|
152
|
+
_, total_ms, status = end_match.groups()
|
|
153
|
+
result["total_time_ms"] = int(total_ms)
|
|
154
|
+
result["status"] = status.lower()
|
|
155
|
+
|
|
156
|
+
# Update root node timing
|
|
157
|
+
if nodes:
|
|
158
|
+
nodes[0]["timing"] = f"{total_ms}ms"
|
|
159
|
+
if status.upper() == "ERROR":
|
|
160
|
+
nodes[0]["status"] = "error"
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Function start
|
|
164
|
+
func_start_match = self.FUNCTION_START_PATTERN.match(line)
|
|
165
|
+
if func_start_match:
|
|
166
|
+
level, func_name, input_json = func_start_match.groups()
|
|
167
|
+
func_name = func_name.strip()
|
|
168
|
+
|
|
169
|
+
# Create function node
|
|
170
|
+
new_node = {
|
|
171
|
+
"id": f"node-{node_counter}",
|
|
172
|
+
"label": func_name,
|
|
173
|
+
"timing": "",
|
|
174
|
+
"status": "success",
|
|
175
|
+
"level": level,
|
|
176
|
+
"input": input_json.strip(),
|
|
177
|
+
"output": "{}",
|
|
178
|
+
"error": None
|
|
179
|
+
}
|
|
180
|
+
nodes.append(new_node)
|
|
181
|
+
|
|
182
|
+
# Create edge from parent
|
|
183
|
+
if call_stack:
|
|
184
|
+
parent_id = call_stack[-1]
|
|
185
|
+
edges.append({
|
|
186
|
+
"source": f"node-{parent_id}",
|
|
187
|
+
"target": f"node-{node_counter}"
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
call_stack.append(node_counter)
|
|
191
|
+
node_counter += 1
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
# Function return
|
|
195
|
+
func_return_match = self.FUNCTION_RETURN_PATTERN.match(line)
|
|
196
|
+
if func_return_match:
|
|
197
|
+
level, func_name, output_json, duration = func_return_match.groups()
|
|
198
|
+
func_name = func_name.strip()
|
|
199
|
+
|
|
200
|
+
# Pop from stack and update node
|
|
201
|
+
if call_stack:
|
|
202
|
+
current_id = call_stack.pop()
|
|
203
|
+
if current_id < len(nodes):
|
|
204
|
+
nodes[current_id]["output"] = output_json.strip()
|
|
205
|
+
nodes[current_id]["timing"] = f"{duration}ms"
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
# Exception
|
|
209
|
+
exception_match = self.EXCEPTION_PATTERN.match(line)
|
|
210
|
+
if exception_match:
|
|
211
|
+
level, func_name, error_type, error_msg, duration = exception_match.groups()
|
|
212
|
+
func_name = func_name.strip()
|
|
213
|
+
|
|
214
|
+
# Pop from stack and update node with error
|
|
215
|
+
if call_stack:
|
|
216
|
+
current_id = call_stack.pop()
|
|
217
|
+
if current_id < len(nodes):
|
|
218
|
+
nodes[current_id]["status"] = "error"
|
|
219
|
+
nodes[current_id]["timing"] = f"{duration}ms"
|
|
220
|
+
nodes[current_id]["error"] = {
|
|
221
|
+
"type": error_type.strip(),
|
|
222
|
+
"message": error_msg.strip(),
|
|
223
|
+
"stack": []
|
|
224
|
+
}
|
|
225
|
+
current_error_node = current_id
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# Finalize any remaining stack lines
|
|
229
|
+
if stack_lines and current_error_node is not None:
|
|
230
|
+
nodes[current_error_node]["error"]["stack"] = stack_lines
|
|
231
|
+
|
|
232
|
+
result["nodes"] = nodes
|
|
233
|
+
result["edges"] = edges
|
|
234
|
+
|
|
235
|
+
return result
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FEATURE-023: Application Action Tracing - Core
|
|
3
|
+
|
|
4
|
+
Redactor module for sensitive data redaction in trace logs.
|
|
5
|
+
|
|
6
|
+
This module provides automatic redaction of sensitive data before logging,
|
|
7
|
+
including passwords, tokens, API keys, and credit card numbers.
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
from typing import Any, Dict, List, Set, Optional
|
|
11
|
+
|
|
12
|
+
REDACTED = "[REDACTED]"
|
|
13
|
+
|
|
14
|
+
# Sensitive key patterns (case-insensitive matching)
|
|
15
|
+
SENSITIVE_KEY_PATTERNS = {
|
|
16
|
+
"password",
|
|
17
|
+
"secret",
|
|
18
|
+
"token",
|
|
19
|
+
"api_key",
|
|
20
|
+
"apikey",
|
|
21
|
+
"authorization",
|
|
22
|
+
"auth",
|
|
23
|
+
"credential",
|
|
24
|
+
"private_key",
|
|
25
|
+
"privatekey",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Value patterns
|
|
29
|
+
CREDIT_CARD_PATTERN = re.compile(r"^\d{16}$")
|
|
30
|
+
JWT_PREFIX = "eyJ"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Redactor:
|
|
34
|
+
"""
|
|
35
|
+
Sensitive data redactor for trace logs.
|
|
36
|
+
|
|
37
|
+
Automatically redacts:
|
|
38
|
+
- Fields containing sensitive key patterns (password, secret, token, etc.)
|
|
39
|
+
- Credit card numbers (16-digit patterns)
|
|
40
|
+
- JWT tokens (values starting with 'eyJ')
|
|
41
|
+
- Custom fields specified via constructor
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
redactor = Redactor(custom_fields=["ssn", "dob"])
|
|
45
|
+
safe_data = redactor.redact({"password": "secret", "name": "John"})
|
|
46
|
+
# Result: {"password": "[REDACTED]", "name": "John"}
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, custom_fields: Optional[List[str]] = None):
|
|
50
|
+
"""
|
|
51
|
+
Initialize Redactor with optional custom fields.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
custom_fields: Additional field names to redact (case-insensitive)
|
|
55
|
+
"""
|
|
56
|
+
self.custom_fields: Set[str] = set(
|
|
57
|
+
f.lower() for f in (custom_fields or [])
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def redact(self, data: Any) -> Any:
|
|
61
|
+
"""
|
|
62
|
+
Recursively redact sensitive data.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
data: Data to redact (dict, list, or primitive)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Data with sensitive values replaced with '[REDACTED]'
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(data, dict):
|
|
71
|
+
return {k: self._redact_value(k, v) for k, v in data.items()}
|
|
72
|
+
elif isinstance(data, list):
|
|
73
|
+
return [self.redact(item) for item in data]
|
|
74
|
+
elif isinstance(data, tuple):
|
|
75
|
+
return tuple(self.redact(item) for item in data)
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
def _redact_value(self, key: str, value: Any) -> Any:
|
|
79
|
+
"""
|
|
80
|
+
Redact a single value based on key name and value patterns.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
key: Field name
|
|
84
|
+
value: Field value
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Redacted value or original value if not sensitive
|
|
88
|
+
"""
|
|
89
|
+
key_lower = key.lower()
|
|
90
|
+
|
|
91
|
+
# Check field name against sensitive patterns
|
|
92
|
+
for pattern in SENSITIVE_KEY_PATTERNS:
|
|
93
|
+
if pattern in key_lower:
|
|
94
|
+
return REDACTED
|
|
95
|
+
|
|
96
|
+
# Check custom fields
|
|
97
|
+
if key_lower in self.custom_fields:
|
|
98
|
+
return REDACTED
|
|
99
|
+
|
|
100
|
+
# Check value patterns for strings
|
|
101
|
+
if isinstance(value, str):
|
|
102
|
+
# Credit card pattern (16 digits)
|
|
103
|
+
if CREDIT_CARD_PATTERN.match(value):
|
|
104
|
+
return REDACTED
|
|
105
|
+
|
|
106
|
+
# JWT pattern (starts with eyJ)
|
|
107
|
+
if value.startswith(JWT_PREFIX):
|
|
108
|
+
return REDACTED
|
|
109
|
+
|
|
110
|
+
# Recurse for nested structures
|
|
111
|
+
return self.redact(value)
|
x_ipe/tracing/writer.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FEATURE-023: Application Action Tracing - Core
|
|
3
|
+
|
|
4
|
+
TraceLogWriter for writing trace buffers to log files.
|
|
5
|
+
|
|
6
|
+
Handles file naming, directory creation, permission setting,
|
|
7
|
+
and cleanup of old log files.
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from .buffer import TraceBuffer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TraceLogWriter:
|
|
19
|
+
"""
|
|
20
|
+
Writer for trace log files.
|
|
21
|
+
|
|
22
|
+
Writes trace buffers to structured log files with proper naming,
|
|
23
|
+
permissions, and cleanup of old files.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
writer = TraceLogWriter("instance/traces/")
|
|
27
|
+
filepath = writer.write(buffer, "SUCCESS")
|
|
28
|
+
deleted = writer.cleanup(retention_hours=24)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, log_path: str = "instance/traces/"):
|
|
32
|
+
"""
|
|
33
|
+
Initialize TraceLogWriter.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
log_path: Directory path for log files
|
|
37
|
+
"""
|
|
38
|
+
self.log_path = Path(log_path)
|
|
39
|
+
|
|
40
|
+
def write(self, buffer: TraceBuffer, status: str = "SUCCESS") -> Optional[str]:
|
|
41
|
+
"""
|
|
42
|
+
Write trace buffer to log file.
|
|
43
|
+
|
|
44
|
+
Creates the log directory if it doesn't exist.
|
|
45
|
+
Sets file permissions to 600 (owner read/write only).
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
buffer: TraceBuffer to write
|
|
49
|
+
status: Final status (SUCCESS, ERROR)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Path to created log file, or None if write failed
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
# Ensure directory exists
|
|
56
|
+
self.log_path.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
# Calculate total duration
|
|
59
|
+
total_ms = (datetime.now(timezone.utc) - buffer.started_at).total_seconds() * 1000
|
|
60
|
+
content = buffer.to_log_string(status, total_ms)
|
|
61
|
+
|
|
62
|
+
# Generate filename
|
|
63
|
+
timestamp = buffer.started_at.strftime("%Y%m%d-%H%M%S")
|
|
64
|
+
api_name = self._sanitize_api_name(buffer.root_api)
|
|
65
|
+
filename = f"{timestamp}-{api_name}-{buffer.trace_id}.log"
|
|
66
|
+
filepath = self.log_path / filename
|
|
67
|
+
|
|
68
|
+
# Write file
|
|
69
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
70
|
+
f.write(content)
|
|
71
|
+
|
|
72
|
+
# Set permissions (owner read/write only)
|
|
73
|
+
os.chmod(filepath, 0o600)
|
|
74
|
+
|
|
75
|
+
return str(filepath)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"[TRACING] Failed to write log: {e}")
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def cleanup(self, retention_hours: int = 24) -> int:
|
|
82
|
+
"""
|
|
83
|
+
Delete log files older than retention period.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
retention_hours: Hours to retain log files
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Number of files deleted
|
|
90
|
+
"""
|
|
91
|
+
if not self.log_path.exists():
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
deleted = 0
|
|
95
|
+
cutoff = datetime.now(timezone.utc).timestamp() - (retention_hours * 3600)
|
|
96
|
+
|
|
97
|
+
for filepath in self.log_path.glob("*.log"):
|
|
98
|
+
try:
|
|
99
|
+
if filepath.stat().st_mtime < cutoff:
|
|
100
|
+
filepath.unlink()
|
|
101
|
+
deleted += 1
|
|
102
|
+
except OSError:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
return deleted
|
|
106
|
+
|
|
107
|
+
def _sanitize_api_name(self, api: str) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Convert API string to safe filename component.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
api: API string (e.g., "POST /api/orders")
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Sanitized string (e.g., "post--api-orders")
|
|
116
|
+
"""
|
|
117
|
+
# Replace spaces and slashes with hyphens
|
|
118
|
+
sanitized = api.lower().replace(" ", "-").replace("/", "-")
|
|
119
|
+
# Remove multiple consecutive hyphens
|
|
120
|
+
sanitized = re.sub(r'-+', '-', sanitized)
|
|
121
|
+
# Strip leading/trailing hyphens
|
|
122
|
+
return sanitized.strip("-")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: x-ipe
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.0.25
|
|
4
|
+
Summary: An AI native integrated project environment for end to end business value delivery
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
7
|
Requires-Dist: beautifulsoup4>=4.14.3
|