traceforge-toolkit 0.1.0__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 (205) hide show
  1. traceforge/__init__.py +72 -0
  2. traceforge/__main__.py +5 -0
  3. traceforge/_generated.py +254 -0
  4. traceforge/adapters/__init__.py +12 -0
  5. traceforge/adapters/base.py +82 -0
  6. traceforge/adapters/genai_otel.py +164 -0
  7. traceforge/adapters/mapped_json.py +297 -0
  8. traceforge/adapters/otel.py +220 -0
  9. traceforge/boundary/__init__.py +46 -0
  10. traceforge/boundary/data/boundary-model.joblib +0 -0
  11. traceforge/boundary/decode.py +87 -0
  12. traceforge/boundary/features.py +137 -0
  13. traceforge/boundary/inference.py +267 -0
  14. traceforge/boundary/inferencer.py +146 -0
  15. traceforge/classify/__init__.py +95 -0
  16. traceforge/classify/cmd.py +109 -0
  17. traceforge/classify/coding.py +213 -0
  18. traceforge/classify/config.py +554 -0
  19. traceforge/classify/core.py +266 -0
  20. traceforge/classify/data/binary_info.yaml +358 -0
  21. traceforge/classify/data/canonical_tools.yaml +251 -0
  22. traceforge/classify/data/effect_overrides.yaml +480 -0
  23. traceforge/classify/data/mcp_profiles.yaml +711 -0
  24. traceforge/classify/data/recommendation_rules.yaml +111 -0
  25. traceforge/classify/data/risk.yaml +534 -0
  26. traceforge/classify/data/shell_defaults.yaml +95 -0
  27. traceforge/classify/data/shell_rules.yaml +1192 -0
  28. traceforge/classify/data/tool_classifications.yaml +215 -0
  29. traceforge/classify/data/verb_inference.yaml +218 -0
  30. traceforge/classify/mcp.py +181 -0
  31. traceforge/classify/phases.py +75 -0
  32. traceforge/classify/powershell.py +93 -0
  33. traceforge/classify/registry.py +138 -0
  34. traceforge/classify/risk.py +553 -0
  35. traceforge/classify/rules.py +215 -0
  36. traceforge/classify/schema.yaml +282 -0
  37. traceforge/classify/shell.py +503 -0
  38. traceforge/classify/tools.py +91 -0
  39. traceforge/classify/workflow.py +32 -0
  40. traceforge/cli/__init__.py +42 -0
  41. traceforge/cli/config_cmd.py +130 -0
  42. traceforge/cli/detect.py +46 -0
  43. traceforge/cli/download_cmd.py +74 -0
  44. traceforge/cli/factory.py +60 -0
  45. traceforge/cli/gate_cmd.py +30 -0
  46. traceforge/cli/init_cmd.py +86 -0
  47. traceforge/cli/replay.py +130 -0
  48. traceforge/cli/runner.py +181 -0
  49. traceforge/cli/score.py +217 -0
  50. traceforge/cli/status.py +65 -0
  51. traceforge/cli/watch.py +297 -0
  52. traceforge/config/__init__.py +80 -0
  53. traceforge/config/defaults.py +149 -0
  54. traceforge/config/loader.py +239 -0
  55. traceforge/config/mappings.py +104 -0
  56. traceforge/config/models.py +579 -0
  57. traceforge/enricher.py +576 -0
  58. traceforge/formatting/__init__.py +12 -0
  59. traceforge/formatting/budget.py +92 -0
  60. traceforge/formatting/density.py +154 -0
  61. traceforge/gate/__init__.py +17 -0
  62. traceforge/gate/client.py +145 -0
  63. traceforge/gate/external.py +305 -0
  64. traceforge/gate/registry.py +108 -0
  65. traceforge/gate/server.py +174 -0
  66. traceforge/gates/__init__.py +8 -0
  67. traceforge/gates/pii.py +352 -0
  68. traceforge/gates/pii_patterns.yaml +228 -0
  69. traceforge/governance/__init__.py +121 -0
  70. traceforge/governance/assessor.py +441 -0
  71. traceforge/governance/budget.py +59 -0
  72. traceforge/governance/canonical.py +56 -0
  73. traceforge/governance/codec.py +414 -0
  74. traceforge/governance/context.py +249 -0
  75. traceforge/governance/drift.py +196 -0
  76. traceforge/governance/emitter.py +234 -0
  77. traceforge/governance/envelope.py +138 -0
  78. traceforge/governance/ifc.py +364 -0
  79. traceforge/governance/integrity.py +162 -0
  80. traceforge/governance/labeler.py +250 -0
  81. traceforge/governance/mcp_drift.py +328 -0
  82. traceforge/governance/monitor.py +539 -0
  83. traceforge/governance/observer.py +276 -0
  84. traceforge/governance/persistence.py +401 -0
  85. traceforge/governance/phase1.py +77 -0
  86. traceforge/governance/pii.py +276 -0
  87. traceforge/governance/pipeline.py +840 -0
  88. traceforge/governance/registry.py +110 -0
  89. traceforge/governance/results.py +183 -0
  90. traceforge/governance/risk_wrapper.py +113 -0
  91. traceforge/governance/rules.py +368 -0
  92. traceforge/governance/scorer.py +262 -0
  93. traceforge/governance/shield.py +318 -0
  94. traceforge/governance/state.py +466 -0
  95. traceforge/governance/types.py +176 -0
  96. traceforge/mappings/__init__.py +1 -0
  97. traceforge/mappings/aider.yaml +216 -0
  98. traceforge/mappings/aider_markdown.yaml +113 -0
  99. traceforge/mappings/amazonq.yaml +56 -0
  100. traceforge/mappings/antigravity.yaml +88 -0
  101. traceforge/mappings/claude.yaml +93 -0
  102. traceforge/mappings/cline.yaml +286 -0
  103. traceforge/mappings/codex.yaml +158 -0
  104. traceforge/mappings/continue_dev.yaml +57 -0
  105. traceforge/mappings/copilot.yaml +266 -0
  106. traceforge/mappings/copilot_markdown.yaml +72 -0
  107. traceforge/mappings/copilot_vscode.yaml +181 -0
  108. traceforge/mappings/crewai.yaml +592 -0
  109. traceforge/mappings/goose.yaml +128 -0
  110. traceforge/mappings/langgraph.yaml +165 -0
  111. traceforge/mappings/maf.yaml +90 -0
  112. traceforge/mappings/maf_transcript.yaml +99 -0
  113. traceforge/mappings/openai_agents.yaml +144 -0
  114. traceforge/mappings/opencode.yaml +473 -0
  115. traceforge/mappings/openhands.yaml +320 -0
  116. traceforge/mappings/pydantic_ai.yaml +183 -0
  117. traceforge/mappings/smolagents.yaml +89 -0
  118. traceforge/mappings/sweagent.yaml +82 -0
  119. traceforge/migrations/__init__.py +1 -0
  120. traceforge/migrations/env.py +60 -0
  121. traceforge/migrations/models.py +145 -0
  122. traceforge/migrations/runner.py +44 -0
  123. traceforge/migrations/script.py.mako +25 -0
  124. traceforge/migrations/versions/0001_initial.py +176 -0
  125. traceforge/migrations/versions/__init__.py +4 -0
  126. traceforge/models.py +29 -0
  127. traceforge/parsers/__init__.py +21 -0
  128. traceforge/parsers/aider.py +416 -0
  129. traceforge/parsers/base.py +226 -0
  130. traceforge/parsers/copilot.py +314 -0
  131. traceforge/phase/__init__.py +50 -0
  132. traceforge/phase/data/phase-model.joblib +0 -0
  133. traceforge/phase/data/potion-base-8M/README.md +99 -0
  134. traceforge/phase/data/potion-base-8M/config.json +13 -0
  135. traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
  136. traceforge/phase/data/potion-base-8M/modules.json +14 -0
  137. traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
  138. traceforge/phase/event_rows.py +107 -0
  139. traceforge/phase/features.py +468 -0
  140. traceforge/phase/inference.py +279 -0
  141. traceforge/phase/inferencer.py +171 -0
  142. traceforge/phase/segmentation.py +258 -0
  143. traceforge/pipeline.py +891 -0
  144. traceforge/preprocessors/__init__.py +34 -0
  145. traceforge/preprocessors/amazonq.py +224 -0
  146. traceforge/preprocessors/antigravity.py +116 -0
  147. traceforge/preprocessors/claude.py +95 -0
  148. traceforge/preprocessors/cline.py +36 -0
  149. traceforge/preprocessors/codex.py +311 -0
  150. traceforge/preprocessors/continue_dev.py +119 -0
  151. traceforge/preprocessors/copilot_vscode.py +171 -0
  152. traceforge/preprocessors/goose.py +156 -0
  153. traceforge/preprocessors/maf_transcript.py +84 -0
  154. traceforge/preprocessors/openai_agents.py +86 -0
  155. traceforge/preprocessors/opencode.py +85 -0
  156. traceforge/preprocessors/openhands.py +36 -0
  157. traceforge/preprocessors/pydantic_ai.py +62 -0
  158. traceforge/preprocessors/registry.py +24 -0
  159. traceforge/preprocessors/smolagents.py +90 -0
  160. traceforge/py.typed +0 -0
  161. traceforge/sdk/__init__.py +59 -0
  162. traceforge/sdk/gate_policy.py +63 -0
  163. traceforge/sdk/gate_types.py +140 -0
  164. traceforge/sdk/pipeline.py +265 -0
  165. traceforge/sdk/verdict.py +81 -0
  166. traceforge/sinks/__init__.py +23 -0
  167. traceforge/sinks/base.py +95 -0
  168. traceforge/sinks/callback.py +132 -0
  169. traceforge/sinks/console.py +112 -0
  170. traceforge/sinks/factory.py +94 -0
  171. traceforge/sinks/jsonl.py +125 -0
  172. traceforge/sinks/otel_exporter.py +212 -0
  173. traceforge/sinks/parquet.py +260 -0
  174. traceforge/sinks/s3.py +206 -0
  175. traceforge/sinks/sqlite_output.py +234 -0
  176. traceforge/sinks/webhook.py +136 -0
  177. traceforge/sources/__init__.py +18 -0
  178. traceforge/sources/auto_detect.py +173 -0
  179. traceforge/sources/base.py +45 -0
  180. traceforge/sources/file_poll.py +136 -0
  181. traceforge/sources/file_watch.py +221 -0
  182. traceforge/sources/http_poll.py +141 -0
  183. traceforge/sources/replay.py +63 -0
  184. traceforge/sources/sqlite.py +187 -0
  185. traceforge/sources/sse.py +198 -0
  186. traceforge/telemetry/__init__.py +192 -0
  187. traceforge/title/__init__.py +23 -0
  188. traceforge/title/_resolve.py +79 -0
  189. traceforge/title/context.py +295 -0
  190. traceforge/title/data/boilerplate_files.json +14 -0
  191. traceforge/title/heuristics.py +429 -0
  192. traceforge/title/hygiene.py +90 -0
  193. traceforge/title/inference.py +314 -0
  194. traceforge/title/inferencer.py +477 -0
  195. traceforge/title/naming.py +398 -0
  196. traceforge/trace.py +291 -0
  197. traceforge/tracking/__init__.py +30 -0
  198. traceforge/tracking/models.py +115 -0
  199. traceforge/tracking/phase_tracker.py +288 -0
  200. traceforge/types.py +315 -0
  201. traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
  202. traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
  203. traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
  204. traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
  205. traceforge_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,215 @@
1
+ # Native tool classifications — maps canonical tool names to Classification objects.
2
+ # Mechanism reflects the RESOURCE DOMAIN the tool accesses (not implementation).
3
+
4
+ tool_classifications:
5
+ view:
6
+ mechanism: filesystem
7
+ effect: read_only
8
+ scope: [artifact.source_code]
9
+ role: [retriever.file_browser]
10
+ action: [retrieve.read]
11
+ capability: [filesystem_read]
12
+
13
+ edit:
14
+ mechanism: filesystem
15
+ effect: mutating
16
+ scope: [artifact.source_code]
17
+ role: [modifier.file_editor]
18
+ action: [modify.edit]
19
+ capability: [filesystem_write]
20
+
21
+ create:
22
+ mechanism: filesystem
23
+ effect: mutating
24
+ scope: [artifact.source_code]
25
+ role: [modifier.file_editor]
26
+ action: [persist.write]
27
+ capability: [filesystem_write]
28
+
29
+ grep:
30
+ mechanism: filesystem
31
+ effect: read_only
32
+ scope: [artifact.source_code]
33
+ role: [retriever.search_index]
34
+ action: [retrieve.search]
35
+ capability: [filesystem_read]
36
+
37
+ glob:
38
+ mechanism: filesystem
39
+ effect: read_only
40
+ scope: [artifact.source_code]
41
+ role: [retriever.file_browser]
42
+ action: [retrieve.browse]
43
+ capability: [filesystem_read]
44
+
45
+ git_commit:
46
+ mechanism: process.shell
47
+ effect: mutating
48
+ scope: [artifact.repository]
49
+ role: [persistence.version_control]
50
+ action: [persist.commit]
51
+ capability: [filesystem_write]
52
+
53
+ git_push:
54
+ mechanism: process.shell
55
+ effect: mutating
56
+ scope: [artifact.repository]
57
+ role: [persistence.version_control]
58
+ action: [deliver.push]
59
+ capability: [filesystem_write, network_outbound]
60
+
61
+ git_diff:
62
+ mechanism: process.shell
63
+ effect: read_only
64
+ scope: [artifact.repository]
65
+ role: [persistence.version_control]
66
+ action: [retrieve.diff]
67
+ capability: [filesystem_read]
68
+
69
+ git_status:
70
+ mechanism: process.shell
71
+ effect: read_only
72
+ scope: [artifact.repository]
73
+ role: [persistence.version_control]
74
+ action: [retrieve.browse]
75
+ capability: [filesystem_read]
76
+
77
+ git_log:
78
+ mechanism: process.shell
79
+ effect: read_only
80
+ scope: [artifact.repository]
81
+ role: [persistence.version_control]
82
+ action: [retrieve.browse]
83
+ capability: [filesystem_read]
84
+
85
+ git_add:
86
+ mechanism: process.shell
87
+ effect: mutating
88
+ scope: [artifact.repository]
89
+ role: [persistence.version_control]
90
+ action: [persist.stage]
91
+ capability: [filesystem_write]
92
+
93
+ git_pull:
94
+ mechanism: process.shell
95
+ effect: mutating
96
+ scope: [artifact.repository]
97
+ role: [persistence.version_control]
98
+ action: [retrieve]
99
+ capability: [filesystem_write, network_outbound]
100
+
101
+ git_merge:
102
+ mechanism: process.shell
103
+ effect: mutating
104
+ scope: [artifact.repository]
105
+ role: [persistence.version_control]
106
+ action: [modify.merge]
107
+ capability: [filesystem_write]
108
+
109
+ git_rebase:
110
+ mechanism: process.shell
111
+ effect: mutating
112
+ scope: [artifact.repository]
113
+ role: [persistence.version_control]
114
+ action: [modify.rebase]
115
+ capability: [filesystem_write]
116
+
117
+ git_checkout:
118
+ mechanism: process.shell
119
+ effect: mutating
120
+ scope: [artifact.repository]
121
+ role: [persistence.version_control]
122
+ action: [retrieve.browse]
123
+ capability: [filesystem_write]
124
+
125
+ git_branch:
126
+ mechanism: process.shell
127
+ effect: read_only
128
+ scope: [artifact.repository]
129
+ role: [persistence.version_control]
130
+ action: [retrieve.browse]
131
+ capability: [filesystem_read]
132
+
133
+ report_intent:
134
+ mechanism: communication.system
135
+ effect: read_only
136
+ role: [communicator.system_reporter]
137
+ action: []
138
+ capability: []
139
+
140
+ ask_user:
141
+ mechanism: communication.user
142
+ effect: read_only
143
+ role: [communicator.user_prompt]
144
+ action: []
145
+ capability: [human_interaction]
146
+
147
+ web_fetch:
148
+ mechanism: network.http
149
+ effect: read_only
150
+ role: [retriever.web_scraper]
151
+ action: [retrieve]
152
+ capability: [network_outbound]
153
+
154
+ web_search:
155
+ mechanism: network.http
156
+ effect: read_only
157
+ role: [retriever.search_index]
158
+ action: [retrieve.search]
159
+ capability: [network_outbound]
160
+
161
+ task:
162
+ mechanism: delegation.agent
163
+ role: []
164
+ action: []
165
+ capability: [subprocess]
166
+
167
+ shell:
168
+ mechanism: process.shell
169
+ role: [executor.script_runner]
170
+ action: [execute.run_script]
171
+ capability: [subprocess]
172
+
173
+ think:
174
+ mechanism: communication.system
175
+ effect: read_only
176
+ role: [communicator.system_reporter]
177
+ action: [analyze]
178
+ capability: []
179
+
180
+ state_write:
181
+ mechanism: communication.system
182
+ effect: mutating
183
+ role: [communicator.system_reporter]
184
+ action: [persist]
185
+ capability: []
186
+
187
+ state_read:
188
+ mechanism: communication.system
189
+ effect: read_only
190
+ role: [communicator.system_reporter]
191
+ action: [retrieve]
192
+ capability: []
193
+
194
+ web_browse:
195
+ mechanism: network.http
196
+ effect: read_only
197
+ role: [retriever.web_scraper]
198
+ action: [retrieve.browse]
199
+ capability: [network_outbound, human_interaction]
200
+
201
+ codebase_search:
202
+ mechanism: filesystem
203
+ effect: read_only
204
+ scope: [artifact.source_code]
205
+ role: [retriever.search_index]
206
+ action: [retrieve.search]
207
+ capability: [filesystem_read]
208
+
209
+ delete_file:
210
+ mechanism: filesystem
211
+ effect: destructive
212
+ scope: [artifact.source_code]
213
+ role: [modifier.file_editor]
214
+ action: [remove]
215
+ capability: [filesystem_write]
@@ -0,0 +1,218 @@
1
+ # Verb prefix → (effect, action) inference for MCP tool names.
2
+ # When a tool name contains a recognized verb prefix, these defaults apply.
3
+ # Used by mcp.py for tools not matched by any server profile.
4
+
5
+ verb_inference:
6
+ # ─── Read-only verbs ─────────────────────────────────────────────────────────
7
+ get:
8
+ effect: read_only
9
+ action: retrieve
10
+ list:
11
+ effect: read_only
12
+ action: retrieve
13
+ read:
14
+ effect: read_only
15
+ action: retrieve
16
+ search:
17
+ effect: read_only
18
+ action: retrieve
19
+ query:
20
+ effect: read_only
21
+ action: retrieve
22
+ describe:
23
+ effect: read_only
24
+ action: analyze
25
+ fetch:
26
+ effect: read_only
27
+ action: retrieve
28
+ browse:
29
+ effect: read_only
30
+ action: retrieve
31
+ find:
32
+ effect: read_only
33
+ action: retrieve
34
+ show:
35
+ effect: read_only
36
+ action: retrieve
37
+ inspect:
38
+ effect: read_only
39
+ action: analyze
40
+ check:
41
+ effect: read_only
42
+ action: validate
43
+ scan:
44
+ effect: read_only
45
+ action: validate
46
+ view:
47
+ effect: read_only
48
+ action: retrieve
49
+
50
+ # ─── Mutating verbs (create/modify) ─────────────────────────────────────────
51
+ create:
52
+ effect: mutating
53
+ action: generate
54
+ update:
55
+ effect: mutating
56
+ action: modify
57
+ write:
58
+ effect: mutating
59
+ action: persist
60
+ set:
61
+ effect: mutating
62
+ action: persist
63
+ add:
64
+ effect: mutating
65
+ action: persist
66
+ append:
67
+ effect: mutating
68
+ action: persist
69
+ apply:
70
+ effect: mutating
71
+ action: execute
72
+ run:
73
+ effect: mutating
74
+ action: execute
75
+ execute:
76
+ effect: mutating
77
+ action: execute
78
+ start:
79
+ effect: mutating
80
+ action: execute
81
+ trigger:
82
+ effect: mutating
83
+ action: execute
84
+ deploy:
85
+ effect: mutating
86
+ action: deliver
87
+ publish:
88
+ effect: mutating
89
+ action: deliver
90
+ push:
91
+ effect: mutating
92
+ action: deliver
93
+ send:
94
+ effect: mutating
95
+ action: deliver
96
+ post:
97
+ effect: mutating
98
+ action: deliver
99
+ submit:
100
+ effect: mutating
101
+ action: deliver
102
+ upload:
103
+ effect: mutating
104
+ action: deliver
105
+ fork:
106
+ effect: mutating
107
+ action: generate
108
+ star:
109
+ effect: mutating
110
+ action: persist
111
+ assign:
112
+ effect: mutating
113
+ action: modify
114
+ merge:
115
+ effect: mutating
116
+ action: modify
117
+ approve:
118
+ effect: mutating
119
+ action: modify
120
+ dismiss:
121
+ effect: mutating
122
+ action: modify
123
+ resolve:
124
+ effect: mutating
125
+ action: modify
126
+ configure:
127
+ effect: mutating
128
+ action: configure
129
+ install:
130
+ effect: mutating
131
+ action: configure
132
+ scale:
133
+ effect: mutating
134
+ action: configure
135
+ build:
136
+ effect: mutating
137
+ action: transform
138
+ pull:
139
+ effect: mutating
140
+ action: retrieve
141
+ edit:
142
+ effect: mutating
143
+ action: modify
144
+ replace:
145
+ effect: mutating
146
+ action: modify
147
+ rename:
148
+ effect: mutating
149
+ action: modify
150
+ restore:
151
+ effect: mutating
152
+ action: modify
153
+ archive:
154
+ effect: mutating
155
+ action: modify
156
+ manage:
157
+ effect: mutating
158
+ action: modify
159
+ move:
160
+ effect: mutating
161
+ action: modify
162
+
163
+ # ─── Mutating verbs (browser/UI interaction) ────────────────────────────────
164
+ navigate:
165
+ effect: mutating
166
+ action: execute
167
+ click:
168
+ effect: mutating
169
+ action: execute
170
+ type:
171
+ effect: mutating
172
+ action: execute
173
+ fill:
174
+ effect: mutating
175
+ action: execute
176
+ select:
177
+ effect: mutating
178
+ action: execute
179
+ hover:
180
+ effect: mutating
181
+ action: execute
182
+ press:
183
+ effect: mutating
184
+ action: execute
185
+ drag:
186
+ effect: mutating
187
+ action: execute
188
+
189
+ # ─── Destructive verbs ──────────────────────────────────────────────────────
190
+ delete:
191
+ effect: destructive
192
+ action: remove
193
+ destroy:
194
+ effect: destructive
195
+ action: remove
196
+ drop:
197
+ effect: destructive
198
+ action: remove
199
+ remove:
200
+ effect: destructive
201
+ action: remove
202
+ clear:
203
+ effect: destructive
204
+ action: remove
205
+ terminate:
206
+ effect: destructive
207
+ action: remove
208
+
209
+ # ─── Lifecycle/cancel ───────────────────────────────────────────────────────
210
+ unstar:
211
+ effect: mutating
212
+ action: modify
213
+ stop:
214
+ effect: mutating
215
+ action: execute
216
+ cancel:
217
+ effect: mutating
218
+ action: execute
@@ -0,0 +1,181 @@
1
+ """MCP (Model Context Protocol) tool classification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING
7
+
8
+ from traceforge.classify.core import Classification
9
+ from traceforge.classify.phases import with_phase_map
10
+
11
+ if TYPE_CHECKING:
12
+ from traceforge.classify.config import ClassificationEngine
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class McpToolOverride:
17
+ """Per-tool classification override within an MCP server profile."""
18
+
19
+ effect: str | None = None
20
+ mechanism: str | None = None
21
+ role: frozenset[str] | None = None
22
+ action: frozenset[str] | None = None
23
+ scope: frozenset[str] | None = None
24
+ capability: frozenset[str] | None = None
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class McpServerProfile:
29
+ """Classification profile for a known MCP server."""
30
+
31
+ namespace_aliases: tuple[str, ...]
32
+ mechanism: str
33
+ role: frozenset[str] = frozenset()
34
+ default_effect: str | None = None
35
+ scope: frozenset[str] = frozenset()
36
+ action: frozenset[str] = frozenset()
37
+ capability: frozenset[str] = frozenset()
38
+ tool_overrides: dict[str, McpToolOverride] = field(default_factory=dict)
39
+
40
+
41
+ def extract_mcp_namespace(raw_name: str) -> str:
42
+ """Extract the MCP server namespace from a raw tool name."""
43
+ name = raw_name.strip()
44
+ if name.startswith("mcp__"):
45
+ parts = name.split("__", 2)
46
+ if len(parts) == 3:
47
+ return parts[1].lower()
48
+ return ""
49
+
50
+
51
+ def _normalize_mcp_suffix(raw_name: str) -> str:
52
+ """Extract and normalize the tool suffix from an MCP tool name."""
53
+ name = raw_name.strip()
54
+ if name.startswith("mcp__"):
55
+ parts = name.split("__", 2)
56
+ if len(parts) == 3:
57
+ return parts[2].lower().replace("-", "_")
58
+ return name.lower().replace("-", "_")
59
+
60
+
61
+ def _infer_from_verb(
62
+ tool_suffix: str,
63
+ *,
64
+ engine: ClassificationEngine,
65
+ ) -> tuple[str | None, str | None]:
66
+ """Infer effect and action from tool name verb prefix."""
67
+ verb_map = engine.verb_inference
68
+ lower = tool_suffix.lower()
69
+ for verb, (effect, action) in verb_map.items():
70
+ if lower.startswith(verb + "_") or lower == verb:
71
+ return effect, action
72
+ return None, None
73
+
74
+
75
+ _EFFECT_SEVERITY: dict[str | None, int] = {
76
+ None: 0,
77
+ "null": 0,
78
+ "read_only": 1,
79
+ "mutating": 2,
80
+ "destructive": 3,
81
+ }
82
+
83
+ _WRITE_EFFECTS = frozenset({"mutating", "destructive"})
84
+ _FS_MECHANISMS = frozenset({"filesystem", "filesystem.local", "filesystem.remote"})
85
+
86
+
87
+ def _build_classification(
88
+ profile: McpServerProfile,
89
+ override: McpToolOverride | None,
90
+ tool_suffix: str,
91
+ *,
92
+ engine: ClassificationEngine,
93
+ ) -> Classification:
94
+ """Build a Classification from profile defaults plus any tool override."""
95
+ mechanism = profile.mechanism
96
+ role = profile.role
97
+ scope = profile.scope
98
+ action = profile.action
99
+ capability = profile.capability
100
+ effect = profile.default_effect
101
+
102
+ if override is not None:
103
+ if override.mechanism is not None:
104
+ mechanism = override.mechanism
105
+ if override.role is not None:
106
+ role = override.role
107
+ if override.scope is not None:
108
+ scope = override.scope
109
+ if override.action is not None:
110
+ action = override.action
111
+ if override.capability is not None:
112
+ capability = override.capability
113
+ if override.effect is not None:
114
+ effect = override.effect
115
+
116
+ verb_effect, verb_action = _infer_from_verb(tool_suffix, engine=engine)
117
+ if verb_effect is None:
118
+ for alias in profile.namespace_aliases:
119
+ prefix = alias + "_"
120
+ if tool_suffix.startswith(prefix):
121
+ verb_effect, verb_action = _infer_from_verb(
122
+ tool_suffix[len(prefix) :],
123
+ engine=engine,
124
+ )
125
+ if verb_effect is not None:
126
+ break
127
+ # Verb inference: upgrade effect when inferred verb is more dangerous than
128
+ # the profile default (destructive > mutating > read_only > null).
129
+ # Explicit per-tool overrides take priority over verb inference.
130
+ has_explicit_override = override is not None and override.effect is not None
131
+ if not has_explicit_override and verb_effect is not None:
132
+ if _EFFECT_SEVERITY.get(verb_effect, 0) > _EFFECT_SEVERITY.get(effect, 0):
133
+ effect = verb_effect
134
+ elif effect is None and verb_effect is not None:
135
+ effect = verb_effect
136
+
137
+ has_action_override = override is not None and override.action is not None
138
+ if not has_action_override and verb_action is not None:
139
+ action = frozenset({verb_action}) | action
140
+
141
+ # When verb inference upgraded the effect, ensure capabilities are consistent.
142
+ if (
143
+ effect in _WRITE_EFFECTS
144
+ and mechanism in _FS_MECHANISMS
145
+ and "filesystem_write" not in capability
146
+ ):
147
+ capability = capability | frozenset({"filesystem_write"})
148
+ # Upgrade role from retriever → modifier.file_editor for filesystem write ops
149
+ if any(r.startswith("retriever.") for r in role):
150
+ upgraded = frozenset(
151
+ "modifier.file_editor" if r.startswith("retriever.") else r for r in role
152
+ )
153
+ role = upgraded
154
+
155
+ return Classification(
156
+ mechanism=mechanism,
157
+ effect=effect,
158
+ scope=scope,
159
+ role=role,
160
+ action=action,
161
+ capability=capability,
162
+ )
163
+
164
+
165
+ def classify_mcp_tool(
166
+ raw_name: str,
167
+ *,
168
+ engine: ClassificationEngine,
169
+ ) -> Classification | None:
170
+ """Classify a tool using engine-provided MCP server profiles."""
171
+ namespace = extract_mcp_namespace(raw_name)
172
+ tool_suffix = _normalize_mcp_suffix(raw_name)
173
+
174
+ alias_index = engine.mcp_alias_index
175
+ profile = alias_index.get(namespace) if namespace else None
176
+ if profile is None:
177
+ return None
178
+
179
+ override = profile.tool_overrides.get(tool_suffix)
180
+ cls = _build_classification(profile, override, tool_suffix, engine=engine)
181
+ return with_phase_map(cls)
@@ -0,0 +1,75 @@
1
+ """Phase derivation logic — shared across tool, MCP, and shell classifiers.
2
+
3
+ Extracted to its own module to avoid circular imports between tools.py and mcp.py.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from traceforge.classify.core import Classification, PhaseSegment
9
+ from traceforge.classify.workflow import Phase
10
+
11
+ # Rule table: (predicate, phase) — evaluated in order, first match wins for single-phase
12
+ _DERIVE_RULES: list[tuple[str, str, str]] = [
13
+ # (kind, value, phase)
14
+ ("action", "validate", Phase.VERIFICATION),
15
+ ("action", "deliver", Phase.REVIEW),
16
+ ("action", "retrieve", Phase.EXPLORATION),
17
+ ("action", "analyze", Phase.EXPLORATION),
18
+ ("action", "configure", Phase.IMPLEMENTATION),
19
+ ("action", "execute", Phase.IMPLEMENTATION),
20
+ ("action", "modify", Phase.IMPLEMENTATION),
21
+ ("action", "persist", Phase.IMPLEMENTATION),
22
+ ]
23
+
24
+
25
+ def derive_phase(cls: Classification) -> str:
26
+ """Derive a single phase for a tool classification.
27
+
28
+ Uses a rule table consistent with _phases_from_classification in the enricher.
29
+ Returns the first matching phase; VCS persist/deliver is special-cased to REVIEW.
30
+ """
31
+ # Special case: VCS persist/deliver → review
32
+ if cls.has_role("persistence.version_control") and (
33
+ cls.has_action("persist") or cls.has_action("deliver")
34
+ ):
35
+ return Phase.REVIEW
36
+
37
+ for kind, value, phase in _DERIVE_RULES:
38
+ if kind == "action" and cls.has_action(value):
39
+ return phase
40
+
41
+ # Mechanism-based fallbacks
42
+ if cls.mechanism.startswith("communication"):
43
+ return Phase.PLANNING
44
+ if cls.mechanism.startswith("delegation"):
45
+ return Phase.IMPLEMENTATION
46
+ if cls.mechanism == "filesystem" and cls.effect == "read_only":
47
+ return Phase.EXPLORATION
48
+ return Phase.IMPLEMENTATION
49
+
50
+
51
+ def with_phase_map(cls: Classification) -> Classification:
52
+ """Add a single-segment phase_map to a Classification.
53
+
54
+ Reuses derive_phase() to keep phase assignment consistent across all
55
+ tool types (native, MCP, shell single-command).
56
+ """
57
+ phase = derive_phase(cls)
58
+ seg = PhaseSegment(
59
+ phase=phase,
60
+ actions=cls.action,
61
+ scopes=cls.scope,
62
+ roles=cls.role,
63
+ )
64
+ return Classification(
65
+ mechanism=cls.mechanism,
66
+ effect=cls.effect,
67
+ scope=cls.scope,
68
+ role=cls.role,
69
+ action=cls.action,
70
+ capability=cls.capability,
71
+ structure=cls.structure,
72
+ shell_dialect=cls.shell_dialect,
73
+ binaries=cls.binaries,
74
+ phase_map=(seg,),
75
+ )