x-ipe 1.0.24__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.
Files changed (139) hide show
  1. x_ipe/app.py +25 -3
  2. x_ipe/handlers/terminal_handlers.py +6 -0
  3. x_ipe/handlers/voice_handlers.py +5 -0
  4. x_ipe/resources/copilot-instructions.md +19 -6
  5. x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
  6. x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
  7. x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
  8. x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
  9. x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
  10. x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
  11. x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
  12. x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
  13. x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
  14. x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
  15. x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
  16. x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
  17. x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
  18. x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
  19. x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
  20. x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
  21. x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
  22. x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
  23. x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
  24. x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
  25. x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
  26. x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
  27. x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
  28. x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
  29. x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
  30. x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
  31. x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
  32. x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
  33. x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
  34. x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
  35. x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
  36. x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
  37. x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
  38. x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
  39. x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
  40. x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
  41. x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
  42. x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
  43. x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
  44. x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
  45. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
  46. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
  47. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
  48. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
  49. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
  50. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
  51. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
  52. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
  53. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
  54. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
  55. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
  56. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
  57. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
  58. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
  59. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
  60. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
  61. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
  62. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
  63. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
  64. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
  65. x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
  66. x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
  67. x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
  68. x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
  69. x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
  70. x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
  71. x_ipe/routes/__init__.py +2 -0
  72. x_ipe/routes/ideas_routes.py +17 -0
  73. x_ipe/routes/kb_routes.py +80 -0
  74. x_ipe/routes/main_routes.py +18 -0
  75. x_ipe/routes/project_routes.py +7 -0
  76. x_ipe/routes/proxy_routes.py +2 -0
  77. x_ipe/routes/quality_evaluation_routes.py +193 -0
  78. x_ipe/routes/settings_routes.py +6 -0
  79. x_ipe/routes/tools_routes.py +6 -0
  80. x_ipe/routes/tracing_routes.py +232 -0
  81. x_ipe/routes/uiux_feedback_routes.py +30 -0
  82. x_ipe/services/__init__.py +5 -0
  83. x_ipe/services/config_service.py +6 -0
  84. x_ipe/services/file_service.py +20 -0
  85. x_ipe/services/homepage_service.py +160 -0
  86. x_ipe/services/ideas_service.py +19 -0
  87. x_ipe/services/kb_service.py +378 -0
  88. x_ipe/services/proxy_service.py +4 -0
  89. x_ipe/services/settings_service.py +13 -0
  90. x_ipe/services/skills_service.py +4 -0
  91. x_ipe/services/terminal_service.py +24 -0
  92. x_ipe/services/themes_service.py +4 -0
  93. x_ipe/services/tools_config_service.py +4 -0
  94. x_ipe/services/tracing_service.py +333 -0
  95. x_ipe/services/uiux_feedback_service.py +32 -0
  96. x_ipe/services/voice_input_service_v2.py +11 -0
  97. x_ipe/static/css/base.css +7 -0
  98. x_ipe/static/css/homepage-infinity.css +330 -0
  99. x_ipe/static/css/kb-core.css +301 -0
  100. x_ipe/static/css/quality-evaluation.css +345 -0
  101. x_ipe/static/css/sidebar.css +14 -4
  102. x_ipe/static/css/terminal.css +1 -0
  103. x_ipe/static/css/tracing-dashboard.css +796 -0
  104. x_ipe/static/css/workplace.css +20 -0
  105. x_ipe/static/img/homepage-infinity-loop.png +0 -0
  106. x_ipe/static/js/features/homepage-infinity.js +314 -0
  107. x_ipe/static/js/features/kb-core.js +371 -0
  108. x_ipe/static/js/features/quality-evaluation.js +387 -0
  109. x_ipe/static/js/features/sidebar.js +255 -12
  110. x_ipe/static/js/features/tracing-dashboard.js +855 -0
  111. x_ipe/static/js/features/tracing-graph.js +1031 -0
  112. x_ipe/static/js/features/tree-search.js +6 -2
  113. x_ipe/static/js/features/workplace.js +200 -6
  114. x_ipe/static/js/init.js +76 -0
  115. x_ipe/static/js/uiux-feedback.js +18 -2
  116. x_ipe/templates/base.html +19 -0
  117. x_ipe/templates/index.html +7 -1
  118. x_ipe/templates/knowledge-base.html +110 -0
  119. x_ipe/templates/workplace.html +4 -0
  120. x_ipe/tracing/__init__.py +37 -0
  121. x_ipe/tracing/buffer.py +135 -0
  122. x_ipe/tracing/context.py +125 -0
  123. x_ipe/tracing/decorator.py +288 -0
  124. x_ipe/tracing/middleware.py +197 -0
  125. x_ipe/tracing/parser.py +235 -0
  126. x_ipe/tracing/redactor.py +111 -0
  127. x_ipe/tracing/writer.py +122 -0
  128. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
  129. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/RECORD +132 -62
  130. x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
  131. x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
  132. x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
  133. x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
  134. x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
  135. x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
  136. x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
  137. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
  138. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
  139. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,197 @@
1
+ """
2
+ FEATURE-023: Application Action Tracing - Middleware
3
+
4
+ Flask middleware for automatic request tracing.
5
+
6
+ Creates TraceContext for each request when tracing is active,
7
+ then writes the trace log file after the request completes.
8
+ """
9
+ import time
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from flask import Flask, request, g
13
+ from typing import List
14
+
15
+ from .context import TraceContext
16
+ from .writer import TraceLogWriter
17
+
18
+
19
+ # APIs to ignore (to avoid infinite loops and noise)
20
+ IGNORED_API_PREFIXES = (
21
+ '/api/tracing/', # Tracing APIs themselves
22
+ '/static/', # Static files
23
+ '/socket.io/', # WebSocket
24
+ )
25
+
26
+
27
+ def init_tracing_middleware(app: Flask) -> None:
28
+ """
29
+ Initialize tracing middleware for a Flask app.
30
+
31
+ Registers before_request and after_request hooks that:
32
+ - Check if tracing is active
33
+ - Start TraceContext before each request
34
+ - End TraceContext and write log file after each request
35
+
36
+ Args:
37
+ app: Flask application instance
38
+ """
39
+
40
+ @app.before_request
41
+ def start_trace():
42
+ """Start trace context if tracing is active."""
43
+ # Skip hardcoded ignored paths (system paths)
44
+ if request.path.startswith(IGNORED_API_PREFIXES):
45
+ return
46
+
47
+ # Check if tracing is active and get config
48
+ project_root = app.config.get('PROJECT_ROOT', '.')
49
+ active, ignored_apis = _get_tracing_config(project_root)
50
+
51
+ if not active:
52
+ return
53
+
54
+ # Skip user-configured ignored APIs
55
+ if _is_path_ignored(request.path, ignored_apis):
56
+ return
57
+
58
+ # Start trace context
59
+ api_name = f"{request.method} {request.path}"
60
+ TraceContext.start_trace(api_name)
61
+ g.trace_start_time = time.perf_counter()
62
+
63
+ @app.after_request
64
+ def end_trace(response):
65
+ """End trace context and write log file."""
66
+ # Check if we started a trace
67
+ if not hasattr(g, 'trace_start_time'):
68
+ return response
69
+
70
+ # End the trace
71
+ buffer = TraceContext.end_trace()
72
+ if buffer is None:
73
+ return response
74
+
75
+ # Determine status from response
76
+ status = "SUCCESS" if response.status_code < 400 else "ERROR"
77
+
78
+ # Get log path from config
79
+ project_root = app.config.get('PROJECT_ROOT', '.')
80
+ log_path = _get_trace_log_path(project_root)
81
+
82
+ # Write trace to file
83
+ writer = TraceLogWriter(log_path)
84
+ writer.write(buffer, status)
85
+
86
+ return response
87
+
88
+ @app.teardown_request
89
+ def cleanup_trace(exception=None):
90
+ """Clean up trace context on error."""
91
+ if exception is not None:
92
+ buffer = TraceContext.end_trace()
93
+ if buffer is not None:
94
+ project_root = app.config.get('PROJECT_ROOT', '.')
95
+ log_path = _get_trace_log_path(project_root)
96
+ writer = TraceLogWriter(log_path)
97
+ writer.write(buffer, "ERROR")
98
+
99
+
100
+ def _get_tracing_config(project_root: str) -> tuple:
101
+ """
102
+ Get tracing configuration including active status and ignored APIs.
103
+
104
+ Reads from tools.json to check tracing_enabled, tracing_stop_at,
105
+ and tracing_ignored_apis.
106
+
107
+ Args:
108
+ project_root: Path to project root
109
+
110
+ Returns:
111
+ Tuple of (is_active: bool, ignored_apis: List[str])
112
+ """
113
+ import json
114
+
115
+ tools_path = Path(project_root) / "x-ipe-docs" / "config" / "tools.json"
116
+ if not tools_path.exists():
117
+ return False, []
118
+
119
+ try:
120
+ with open(tools_path) as f:
121
+ config = json.load(f)
122
+ except (json.JSONDecodeError, IOError):
123
+ return False, []
124
+
125
+ ignored_apis = config.get("tracing_ignored_apis", [])
126
+
127
+ # Check if explicitly enabled
128
+ if config.get("tracing_enabled", False):
129
+ return True, ignored_apis
130
+
131
+ # Check if stop_at is in the future
132
+ stop_at = config.get("tracing_stop_at")
133
+ if stop_at:
134
+ try:
135
+ # Keep timezone-aware for proper comparison
136
+ stop_time = datetime.fromisoformat(
137
+ stop_at.replace("Z", "+00:00")
138
+ )
139
+ if datetime.now(timezone.utc) < stop_time:
140
+ return True, ignored_apis
141
+ except (ValueError, AttributeError):
142
+ pass
143
+
144
+ return False, ignored_apis
145
+
146
+
147
+ def _is_path_ignored(path: str, ignored_apis: List[str]) -> bool:
148
+ """
149
+ Check if a request path matches any user-configured ignored API patterns.
150
+
151
+ Supports exact matches and prefix matches (patterns ending with *).
152
+
153
+ Args:
154
+ path: Request path (e.g., "/api/project/structure")
155
+ ignored_apis: List of API patterns to ignore
156
+
157
+ Returns:
158
+ True if path should be ignored
159
+ """
160
+ for pattern in ignored_apis:
161
+ if pattern.endswith('*'):
162
+ # Prefix match
163
+ if path.startswith(pattern[:-1]):
164
+ return True
165
+ else:
166
+ # Exact match
167
+ if path == pattern:
168
+ return True
169
+ return False
170
+
171
+
172
+ def _get_trace_log_path(project_root: str) -> str:
173
+ """
174
+ Get the trace log directory path.
175
+
176
+ Reads from tools.json or uses default.
177
+
178
+ Args:
179
+ project_root: Path to project root
180
+
181
+ Returns:
182
+ Absolute path to trace log directory
183
+ """
184
+ import json
185
+
186
+ tools_path = Path(project_root) / "x-ipe-docs" / "config" / "tools.json"
187
+ log_path = "instance/traces/"
188
+
189
+ if tools_path.exists():
190
+ try:
191
+ with open(tools_path) as f:
192
+ config = json.load(f)
193
+ log_path = config.get("tracing_log_path", log_path)
194
+ except (json.JSONDecodeError, IOError):
195
+ pass
196
+
197
+ return str(Path(project_root) / log_path)
@@ -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)
@@ -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.24
4
- Summary: X-IPE: AI-powered development framework
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