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.
Files changed (146) hide show
  1. x_ipe/app.py +32 -1
  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 +289 -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 +10 -2
  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 +50 -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 +535 -2
  87. x_ipe/services/kb_service.py +378 -0
  88. x_ipe/services/proxy_service.py +37 -7
  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 +148 -1
  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 +23 -0
  103. x_ipe/static/css/tracing-dashboard.css +796 -0
  104. x_ipe/static/css/uiux-feedback.css +7 -1
  105. x_ipe/static/css/workplace.css +636 -0
  106. x_ipe/static/img/homepage-infinity-loop.png +0 -0
  107. x_ipe/static/js/features/confirm-dialog.js +169 -0
  108. x_ipe/static/js/features/folder-view.js +742 -0
  109. x_ipe/static/js/features/homepage-infinity.js +314 -0
  110. x_ipe/static/js/features/kb-core.js +371 -0
  111. x_ipe/static/js/features/quality-evaluation.js +387 -0
  112. x_ipe/static/js/features/sidebar.js +255 -12
  113. x_ipe/static/js/features/tracing-dashboard.js +855 -0
  114. x_ipe/static/js/features/tracing-graph.js +1031 -0
  115. x_ipe/static/js/features/tree-drag.js +227 -0
  116. x_ipe/static/js/features/tree-search.js +228 -0
  117. x_ipe/static/js/features/workplace.js +661 -33
  118. x_ipe/static/js/init.js +76 -0
  119. x_ipe/static/js/terminal-v2.js +45 -14
  120. x_ipe/static/js/terminal.js +50 -49
  121. x_ipe/static/js/uiux-feedback.js +75 -16
  122. x_ipe/templates/base.html +24 -0
  123. x_ipe/templates/index.html +10 -1
  124. x_ipe/templates/knowledge-base.html +110 -0
  125. x_ipe/templates/workplace.html +4 -0
  126. x_ipe/tracing/__init__.py +37 -0
  127. x_ipe/tracing/buffer.py +135 -0
  128. x_ipe/tracing/context.py +125 -0
  129. x_ipe/tracing/decorator.py +288 -0
  130. x_ipe/tracing/middleware.py +197 -0
  131. x_ipe/tracing/parser.py +235 -0
  132. x_ipe/tracing/redactor.py +111 -0
  133. x_ipe/tracing/writer.py +122 -0
  134. {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
  135. {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/RECORD +138 -65
  136. x_ipe/app.py.bak +0 -1333
  137. x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
  138. x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
  139. x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
  140. x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
  141. x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
  142. x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
  143. x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
  144. {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
  145. {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
  146. {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,288 @@
1
+ """
2
+ FEATURE-023: Application Action Tracing - Core
3
+
4
+ @x_ipe_tracing decorator for automatic function tracing.
5
+
6
+ Provides a decorator that automatically logs function entry, exit,
7
+ return values, and exceptions with execution timing.
8
+ """
9
+ import functools
10
+ import time
11
+ import asyncio
12
+ import inspect
13
+ from datetime import datetime, timezone
14
+ from typing import Callable, List, Optional, Any, Dict
15
+
16
+ from .context import TraceContext
17
+ from .buffer import TraceEntry
18
+ from .redactor import Redactor
19
+
20
+
21
+ def x_ipe_tracing(
22
+ level: str = "INFO",
23
+ redact: Optional[List[str]] = None
24
+ ) -> Callable:
25
+ """
26
+ Decorator for automatic function tracing.
27
+
28
+ Logs function entry with parameters, exit with return value,
29
+ and any exceptions that occur. Automatically redacts sensitive data.
30
+
31
+ Args:
32
+ level: Log level - "INFO", "DEBUG", or "SKIP"
33
+ redact: List of parameter names to redact (in addition to built-in patterns)
34
+
35
+ Usage:
36
+ @x_ipe_tracing(level="INFO", redact=["password"])
37
+ def create_user(email: str, password: str) -> dict:
38
+ ...
39
+
40
+ Returns:
41
+ Decorated function that traces execution
42
+ """
43
+ if level == "SKIP":
44
+ return lambda fn: fn # No-op decorator
45
+
46
+ redactor = Redactor(custom_fields=redact)
47
+
48
+ def decorator(func: Callable) -> Callable:
49
+ @functools.wraps(func)
50
+ def sync_wrapper(*args, **kwargs):
51
+ ctx = TraceContext.get_current()
52
+ if not ctx:
53
+ return func(*args, **kwargs) # No active trace
54
+
55
+ return _trace_call(ctx, func, args, kwargs, level, redactor)
56
+
57
+ @functools.wraps(func)
58
+ async def async_wrapper(*args, **kwargs):
59
+ ctx = TraceContext.get_current()
60
+ if not ctx:
61
+ return await func(*args, **kwargs) # No active trace
62
+
63
+ return await _trace_call_async(ctx, func, args, kwargs, level, redactor)
64
+
65
+ if asyncio.iscoroutinefunction(func):
66
+ return async_wrapper
67
+ return sync_wrapper
68
+
69
+ return decorator
70
+
71
+
72
+ def _extract_params(func: Callable, args: tuple, kwargs: dict) -> Dict[str, Any]:
73
+ """
74
+ Extract function parameters as a dictionary.
75
+
76
+ Uses function signature to map positional args to parameter names.
77
+
78
+ Args:
79
+ func: The function being called
80
+ args: Positional arguments
81
+ kwargs: Keyword arguments
82
+
83
+ Returns:
84
+ Dictionary of parameter names to values
85
+ """
86
+ params = {}
87
+
88
+ try:
89
+ sig = inspect.signature(func)
90
+ param_names = list(sig.parameters.keys())
91
+
92
+ # Map positional args
93
+ for i, arg in enumerate(args):
94
+ if i < len(param_names):
95
+ # Skip 'self' and 'cls' parameters
96
+ param_name = param_names[i]
97
+ if param_name not in ('self', 'cls'):
98
+ params[param_name] = arg
99
+ else:
100
+ params[f"arg_{i}"] = arg
101
+
102
+ # Add keyword args
103
+ params.update(kwargs)
104
+ except (ValueError, TypeError):
105
+ # Fallback if signature introspection fails
106
+ for i, arg in enumerate(args):
107
+ params[f"arg_{i}"] = arg
108
+ params.update(kwargs)
109
+
110
+ return params
111
+
112
+
113
+ def _safe_serialize(value: Any) -> Any:
114
+ """
115
+ Safely serialize a value for logging.
116
+
117
+ Handles circular references and non-serializable objects.
118
+
119
+ Args:
120
+ value: Value to serialize
121
+
122
+ Returns:
123
+ Serializable representation of the value
124
+ """
125
+ try:
126
+ # Try direct serialization
127
+ import json
128
+ json.dumps(value, default=str)
129
+ return value
130
+ except (TypeError, ValueError, RecursionError):
131
+ # Fallback for complex objects
132
+ return str(value)[:500]
133
+
134
+
135
+ def _trace_call(
136
+ ctx: TraceContext,
137
+ func: Callable,
138
+ args: tuple,
139
+ kwargs: dict,
140
+ level: str,
141
+ redactor: Redactor
142
+ ) -> Any:
143
+ """
144
+ Trace a synchronous function call.
145
+
146
+ Logs entry, executes the function, logs exit or exception.
147
+ """
148
+ func_name = func.__name__
149
+ depth = ctx.push_call(func_name)
150
+
151
+ # Extract and redact parameters
152
+ params = _extract_params(func, args, kwargs)
153
+ redacted_params = redactor.redact(params)
154
+
155
+ # Log entry
156
+ ctx.buffer.add(TraceEntry(
157
+ timestamp=datetime.now(timezone.utc),
158
+ trace_id=ctx.trace_id,
159
+ level=level,
160
+ direction="→",
161
+ event_type="start_function",
162
+ function_name=func_name,
163
+ data=_safe_serialize(redacted_params),
164
+ depth=depth
165
+ ))
166
+
167
+ start = time.perf_counter()
168
+ try:
169
+ result = func(*args, **kwargs)
170
+ duration = (time.perf_counter() - start) * 1000
171
+
172
+ # Redact and serialize return value
173
+ redacted_result = redactor.redact({"return": _safe_serialize(result)})
174
+
175
+ # Log success
176
+ ctx.buffer.add(TraceEntry(
177
+ timestamp=datetime.now(timezone.utc),
178
+ trace_id=ctx.trace_id,
179
+ level=level,
180
+ direction="←",
181
+ event_type="return_function",
182
+ function_name=func_name,
183
+ data=redacted_result,
184
+ duration_ms=duration,
185
+ depth=depth
186
+ ))
187
+ return result
188
+
189
+ except Exception as e:
190
+ duration = (time.perf_counter() - start) * 1000
191
+
192
+ # Log error
193
+ ctx.buffer.add(TraceEntry(
194
+ timestamp=datetime.now(timezone.utc),
195
+ trace_id=ctx.trace_id,
196
+ level="ERROR",
197
+ direction="←",
198
+ event_type="exception",
199
+ function_name=func_name,
200
+ data={
201
+ "error": type(e).__name__,
202
+ "message": str(e)
203
+ },
204
+ duration_ms=duration,
205
+ depth=depth
206
+ ))
207
+ raise
208
+
209
+ finally:
210
+ ctx.pop_call()
211
+
212
+
213
+ async def _trace_call_async(
214
+ ctx: TraceContext,
215
+ func: Callable,
216
+ args: tuple,
217
+ kwargs: dict,
218
+ level: str,
219
+ redactor: Redactor
220
+ ) -> Any:
221
+ """
222
+ Trace an asynchronous function call.
223
+
224
+ Logs entry, awaits the function, logs exit or exception.
225
+ """
226
+ func_name = func.__name__
227
+ depth = ctx.push_call(func_name)
228
+
229
+ # Extract and redact parameters
230
+ params = _extract_params(func, args, kwargs)
231
+ redacted_params = redactor.redact(params)
232
+
233
+ # Log entry
234
+ ctx.buffer.add(TraceEntry(
235
+ timestamp=datetime.now(timezone.utc),
236
+ trace_id=ctx.trace_id,
237
+ level=level,
238
+ direction="→",
239
+ event_type="start_function",
240
+ function_name=func_name,
241
+ data=_safe_serialize(redacted_params),
242
+ depth=depth
243
+ ))
244
+
245
+ start = time.perf_counter()
246
+ try:
247
+ result = await func(*args, **kwargs)
248
+ duration = (time.perf_counter() - start) * 1000
249
+
250
+ # Redact and serialize return value
251
+ redacted_result = redactor.redact({"return": _safe_serialize(result)})
252
+
253
+ # Log success
254
+ ctx.buffer.add(TraceEntry(
255
+ timestamp=datetime.now(timezone.utc),
256
+ trace_id=ctx.trace_id,
257
+ level=level,
258
+ direction="←",
259
+ event_type="return_function",
260
+ function_name=func_name,
261
+ data=redacted_result,
262
+ duration_ms=duration,
263
+ depth=depth
264
+ ))
265
+ return result
266
+
267
+ except Exception as e:
268
+ duration = (time.perf_counter() - start) * 1000
269
+
270
+ # Log error
271
+ ctx.buffer.add(TraceEntry(
272
+ timestamp=datetime.now(timezone.utc),
273
+ trace_id=ctx.trace_id,
274
+ level="ERROR",
275
+ direction="←",
276
+ event_type="exception",
277
+ function_name=func_name,
278
+ data={
279
+ "error": type(e).__name__,
280
+ "message": str(e)
281
+ },
282
+ duration_ms=duration,
283
+ depth=depth
284
+ ))
285
+ raise
286
+
287
+ finally:
288
+ ctx.pop_call()
@@ -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)