aptitude-resolver 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 (93) hide show
  1. aptitude_resolver/__init__.py +1 -0
  2. aptitude_resolver/application/__init__.py +1 -0
  3. aptitude_resolver/application/composition.py +331 -0
  4. aptitude_resolver/application/dto/__init__.py +59 -0
  5. aptitude_resolver/application/dto/install_dto.py +88 -0
  6. aptitude_resolver/application/dto/resolve_request_dto.py +20 -0
  7. aptitude_resolver/application/dto/resolve_result_dto.py +263 -0
  8. aptitude_resolver/application/queries/__init__.py +13 -0
  9. aptitude_resolver/application/queries/plan_skill_resolution.py +370 -0
  10. aptitude_resolver/application/use_cases/__init__.py +13 -0
  11. aptitude_resolver/application/use_cases/install_skill.py +139 -0
  12. aptitude_resolver/application/use_cases/resolution_mapping.py +259 -0
  13. aptitude_resolver/application/use_cases/resolve_skill_query.py +86 -0
  14. aptitude_resolver/application/use_cases/sync_from_lock.py +100 -0
  15. aptitude_resolver/cache/__init__.py +20 -0
  16. aptitude_resolver/cache/keys.py +34 -0
  17. aptitude_resolver/cache/store.py +61 -0
  18. aptitude_resolver/discovery/__init__.py +13 -0
  19. aptitude_resolver/discovery/candidate_discovery.py +155 -0
  20. aptitude_resolver/discovery/intent/__init__.py +8 -0
  21. aptitude_resolver/discovery/intent/parsing.py +63 -0
  22. aptitude_resolver/discovery/query_builder/__init__.py +5 -0
  23. aptitude_resolver/discovery/query_builder/build_query.py +17 -0
  24. aptitude_resolver/discovery/reranking/__init__.py +5 -0
  25. aptitude_resolver/discovery/reranking/candidate_reranker.py +584 -0
  26. aptitude_resolver/domain/__init__.py +1 -0
  27. aptitude_resolver/domain/errors/__init__.py +41 -0
  28. aptitude_resolver/domain/errors/resolver_errors.py +188 -0
  29. aptitude_resolver/domain/models/__init__.py +33 -0
  30. aptitude_resolver/domain/models/dependency_spec.py +16 -0
  31. aptitude_resolver/domain/models/discovered_skill.py +15 -0
  32. aptitude_resolver/domain/models/discovery_candidate.py +26 -0
  33. aptitude_resolver/domain/models/discovery_query.py +16 -0
  34. aptitude_resolver/domain/models/resolution_graph.py +59 -0
  35. aptitude_resolver/domain/models/search_intent.py +18 -0
  36. aptitude_resolver/domain/models/skill_coordinate.py +13 -0
  37. aptitude_resolver/domain/models/skill_identity.py +21 -0
  38. aptitude_resolver/domain/models/skill_metadata.py +31 -0
  39. aptitude_resolver/domain/models/version_summary.py +29 -0
  40. aptitude_resolver/domain/policy/__init__.py +26 -0
  41. aptitude_resolver/domain/policy/models.py +97 -0
  42. aptitude_resolver/domain/policy/ranking.py +33 -0
  43. aptitude_resolver/domain/policy/selection.py +40 -0
  44. aptitude_resolver/domain/tracing/__init__.py +5 -0
  45. aptitude_resolver/domain/tracing/models.py +15 -0
  46. aptitude_resolver/execution/__init__.py +29 -0
  47. aptitude_resolver/execution/debug_artifacts.py +107 -0
  48. aptitude_resolver/execution/materialize.py +174 -0
  49. aptitude_resolver/execution/plan.py +67 -0
  50. aptitude_resolver/governance/__init__.py +8 -0
  51. aptitude_resolver/governance/evaluator.py +304 -0
  52. aptitude_resolver/interfaces/__init__.py +1 -0
  53. aptitude_resolver/interfaces/cli/__init__.py +5 -0
  54. aptitude_resolver/interfaces/cli/app.py +586 -0
  55. aptitude_resolver/interfaces/cli/main.py +28 -0
  56. aptitude_resolver/interfaces/shared/__init__.py +9 -0
  57. aptitude_resolver/interfaces/shared/install_workflow.py +233 -0
  58. aptitude_resolver/interfaces/tui/__init__.py +5 -0
  59. aptitude_resolver/interfaces/tui/app.py +671 -0
  60. aptitude_resolver/lockfile/__init__.py +35 -0
  61. aptitude_resolver/lockfile/model.py +97 -0
  62. aptitude_resolver/lockfile/parser.py +247 -0
  63. aptitude_resolver/lockfile/replay.py +91 -0
  64. aptitude_resolver/lockfile/serializer.py +247 -0
  65. aptitude_resolver/registry/__init__.py +5 -0
  66. aptitude_resolver/registry/client.py +344 -0
  67. aptitude_resolver/registry/mappers.py +108 -0
  68. aptitude_resolver/registry/transport_models.py +125 -0
  69. aptitude_resolver/resolver/__init__.py +1 -0
  70. aptitude_resolver/resolver/conflict/__init__.py +7 -0
  71. aptitude_resolver/resolver/conflict/conflict_rules.py +20 -0
  72. aptitude_resolver/resolver/graph/__init__.py +7 -0
  73. aptitude_resolver/resolver/graph/recursive_graph_resolver.py +246 -0
  74. aptitude_resolver/resolver/normalizer/__init__.py +7 -0
  75. aptitude_resolver/resolver/normalizer/dependency_normalizer.py +22 -0
  76. aptitude_resolver/resolver/solver/__init__.py +19 -0
  77. aptitude_resolver/resolver/solver/candidate_selection.py +92 -0
  78. aptitude_resolver/resolver/solver/candidate_version_resolution.py +228 -0
  79. aptitude_resolver/resolver/solver/version_selection.py +41 -0
  80. aptitude_resolver/resolver/validation/__init__.py +7 -0
  81. aptitude_resolver/resolver/validation/graph_validator.py +18 -0
  82. aptitude_resolver/shared/__init__.py +1 -0
  83. aptitude_resolver/shared/config/__init__.py +27 -0
  84. aptitude_resolver/shared/config/aptitude_config.py +133 -0
  85. aptitude_resolver/shared/config/settings.py +26 -0
  86. aptitude_resolver/shared/logging/__init__.py +5 -0
  87. aptitude_resolver/shared/logging/configure.py +27 -0
  88. aptitude_resolver/telemetry/__init__.py +9 -0
  89. aptitude_resolver/telemetry/metrics.py +61 -0
  90. aptitude_resolver-0.1.0.dist-info/METADATA +333 -0
  91. aptitude_resolver-0.1.0.dist-info/RECORD +93 -0
  92. aptitude_resolver-0.1.0.dist-info/WHEEL +4 -0
  93. aptitude_resolver-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1 @@
1
+ """Aptitude Resolver package."""
@@ -0,0 +1 @@
1
+ """Application layer package."""
@@ -0,0 +1,331 @@
1
+ """Application-owned wiring helpers for configured use cases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+
8
+ from aptitude_resolver.application.use_cases import (
9
+ InstallSkillUseCase,
10
+ ResolveSkillQueryUseCase,
11
+ SyncFromLockUseCase,
12
+ )
13
+ from aptitude_resolver.domain.errors import InvalidResolverConfigurationError
14
+ from aptitude_resolver.domain.policy import PolicyContext, SelectionPreferences
15
+ from aptitude_resolver.registry import RegistryClient
16
+ from aptitude_resolver.shared.config import (
17
+ AptitudeConfig,
18
+ PolicyConfig,
19
+ SelectionConfig,
20
+ Settings,
21
+ load_user_aptitude_config,
22
+ load_workspace_aptitude_config,
23
+ read_env_selection_overrides,
24
+ )
25
+
26
+
27
+ def build_registry_client() -> tuple[RegistryClient, Callable[[], None]]:
28
+ """Create a registry client and its cleanup hook."""
29
+
30
+ registry_client = RegistryClient(Settings())
31
+ return registry_client, registry_client.close
32
+
33
+
34
+ def _effective_selection_preferences(
35
+ *,
36
+ selection_profile_override: str | None = None,
37
+ interaction_mode_override: str | None = None,
38
+ cwd: Path | None = None,
39
+ ) -> SelectionPreferences:
40
+ """Build one effective selection-preference object from all current sources."""
41
+
42
+ default_preferences = SelectionPreferences()
43
+ sources = [
44
+ (
45
+ "user_config",
46
+ "user config",
47
+ _selection_config(load_user_aptitude_config, "user config"),
48
+ ),
49
+ (
50
+ "workspace_config",
51
+ "workspace config",
52
+ _selection_config(
53
+ lambda: load_workspace_aptitude_config(cwd),
54
+ "workspace config",
55
+ ),
56
+ ),
57
+ ("environment", "environment", read_env_selection_overrides()),
58
+ (
59
+ "cli_override",
60
+ "CLI override",
61
+ SelectionConfig(
62
+ profile=selection_profile_override,
63
+ interaction_mode=interaction_mode_override,
64
+ )
65
+ if selection_profile_override is not None
66
+ or interaction_mode_override is not None
67
+ else None,
68
+ ),
69
+ ]
70
+
71
+ effective_profile = default_preferences.profile
72
+ effective_profile_source = "default"
73
+ effective_profile_error_source = "default"
74
+ effective_interaction_mode = default_preferences.interaction_mode
75
+ effective_interaction_source = "default"
76
+ effective_interaction_error_source = "default"
77
+
78
+ for source_id, _source_name, selection_config in sources:
79
+ if selection_config is None:
80
+ continue
81
+ if selection_config.profile is not None:
82
+ effective_profile = selection_config.profile
83
+ effective_profile_source = source_id
84
+ effective_profile_error_source = _source_name
85
+ if selection_config.interaction_mode is not None:
86
+ effective_interaction_mode = selection_config.interaction_mode
87
+ effective_interaction_source = source_id
88
+ effective_interaction_error_source = _source_name
89
+
90
+ try:
91
+ return SelectionPreferences(
92
+ profile=effective_profile,
93
+ interaction_mode=effective_interaction_mode,
94
+ profile_source=effective_profile_source,
95
+ interaction_mode_source=effective_interaction_source,
96
+ )
97
+ except ValueError as exc:
98
+ source = (
99
+ effective_profile_error_source
100
+ if "profile" in str(exc).lower()
101
+ else effective_interaction_error_source
102
+ )
103
+ raise InvalidResolverConfigurationError(source, str(exc)) from exc
104
+
105
+
106
+ def _effective_policy_context(
107
+ *,
108
+ allowed_trust_tiers_override: list[str] | None = None,
109
+ allowed_lifecycle_statuses_override: list[str] | None = None,
110
+ max_token_estimate_override: int | None = None,
111
+ max_content_size_bytes_override: int | None = None,
112
+ cwd: Path | None = None,
113
+ ) -> PolicyContext:
114
+ """Build one effective policy object from defaults, workspace policy, and CLI."""
115
+
116
+ default_policy = PolicyContext()
117
+ workspace_policy_config = _policy_config(
118
+ lambda: load_workspace_aptitude_config(cwd),
119
+ "workspace config",
120
+ )
121
+ policy = _apply_policy_override(
122
+ default_policy, workspace_policy_config, source="workspace_config"
123
+ )
124
+ cli_policy_config = PolicyConfig(
125
+ allowed_trust_tiers=allowed_trust_tiers_override,
126
+ allowed_lifecycle_statuses=allowed_lifecycle_statuses_override,
127
+ max_token_estimate=max_token_estimate_override,
128
+ max_content_size_bytes=max_content_size_bytes_override,
129
+ )
130
+ has_cli_override = any(
131
+ value is not None
132
+ for value in (
133
+ allowed_trust_tiers_override,
134
+ allowed_lifecycle_statuses_override,
135
+ max_token_estimate_override,
136
+ max_content_size_bytes_override,
137
+ )
138
+ )
139
+ if has_cli_override:
140
+ policy = _apply_policy_override(
141
+ policy, cli_policy_config, source="cli_override"
142
+ )
143
+
144
+ return policy
145
+
146
+
147
+ def _apply_policy_override(
148
+ base: PolicyContext,
149
+ override: PolicyConfig | None,
150
+ *,
151
+ source: str,
152
+ ) -> PolicyContext:
153
+ """Apply one stricter-only policy override layer onto an existing policy."""
154
+
155
+ if override is None:
156
+ return base
157
+
158
+ try:
159
+ validated_override = PolicyContext(
160
+ profile=base.profile,
161
+ source=source,
162
+ allowed_lifecycle_statuses=(
163
+ list(override.allowed_lifecycle_statuses)
164
+ if override.allowed_lifecycle_statuses is not None
165
+ else list(base.allowed_lifecycle_statuses)
166
+ ),
167
+ allowed_trust_tiers=(
168
+ list(override.allowed_trust_tiers)
169
+ if override.allowed_trust_tiers is not None
170
+ else list(base.allowed_trust_tiers)
171
+ ),
172
+ max_token_estimate=override.max_token_estimate,
173
+ max_content_size_bytes=override.max_content_size_bytes,
174
+ max_total_token_estimate=override.max_total_token_estimate,
175
+ max_total_content_size_bytes=override.max_total_content_size_bytes,
176
+ )
177
+ return PolicyContext(
178
+ profile=base.profile,
179
+ source=source,
180
+ allowed_lifecycle_statuses=_stricter_allowed_values(
181
+ base.allowed_lifecycle_statuses,
182
+ validated_override.allowed_lifecycle_statuses
183
+ if override.allowed_lifecycle_statuses is not None
184
+ else None,
185
+ ),
186
+ allowed_trust_tiers=_stricter_allowed_values(
187
+ base.allowed_trust_tiers,
188
+ validated_override.allowed_trust_tiers
189
+ if override.allowed_trust_tiers is not None
190
+ else None,
191
+ ),
192
+ max_token_estimate=_stricter_ceiling(
193
+ base.max_token_estimate,
194
+ validated_override.max_token_estimate,
195
+ ),
196
+ max_content_size_bytes=_stricter_ceiling(
197
+ base.max_content_size_bytes,
198
+ validated_override.max_content_size_bytes,
199
+ ),
200
+ max_total_token_estimate=_stricter_ceiling(
201
+ base.max_total_token_estimate,
202
+ validated_override.max_total_token_estimate,
203
+ ),
204
+ max_total_content_size_bytes=_stricter_ceiling(
205
+ base.max_total_content_size_bytes,
206
+ validated_override.max_total_content_size_bytes,
207
+ ),
208
+ )
209
+ except ValueError as exc:
210
+ error_source = (
211
+ "workspace config" if source == "workspace_config" else "CLI override"
212
+ )
213
+ raise InvalidResolverConfigurationError(error_source, str(exc)) from exc
214
+
215
+
216
+ def _policy_config(
217
+ loader: Callable[[], AptitudeConfig | None],
218
+ source_name: str,
219
+ ) -> PolicyConfig | None:
220
+ """Load one optional config source and return just its policy section."""
221
+
222
+ try:
223
+ config = loader()
224
+ except ValueError as exc:
225
+ raise InvalidResolverConfigurationError(source_name, str(exc)) from exc
226
+ if config is None:
227
+ return None
228
+ return config.policy
229
+
230
+
231
+ def _stricter_allowed_values(base: list[str], override: list[str] | None) -> list[str]:
232
+ if override is None:
233
+ return list(base)
234
+ override_set = set(override)
235
+ return [value for value in base if value in override_set]
236
+
237
+
238
+ def _stricter_ceiling(base: int | None, override: int | None) -> int | None:
239
+ if override is None:
240
+ return base
241
+ if base is None:
242
+ return override
243
+ return min(base, override)
244
+
245
+
246
+ def _selection_config(
247
+ loader: Callable[[], AptitudeConfig | None],
248
+ source_name: str,
249
+ ) -> SelectionConfig | None:
250
+ """Load one optional config source and return just its selection section."""
251
+
252
+ try:
253
+ config = loader()
254
+ except ValueError as exc:
255
+ raise InvalidResolverConfigurationError(source_name, str(exc)) from exc
256
+ if config is None:
257
+ return None
258
+ return config.selection
259
+
260
+
261
+ def build_resolve_use_case(
262
+ *,
263
+ selection_profile_override: str | None = None,
264
+ interaction_mode_override: str | None = None,
265
+ allowed_trust_tiers_override: list[str] | None = None,
266
+ allowed_lifecycle_statuses_override: list[str] | None = None,
267
+ max_token_estimate_override: int | None = None,
268
+ max_content_size_bytes_override: int | None = None,
269
+ cwd: Path | None = None,
270
+ ) -> tuple[ResolveSkillQueryUseCase, Callable[[], None]]:
271
+ """Create the resolve use case and its cleanup hook."""
272
+
273
+ registry_client, close = build_registry_client()
274
+ return (
275
+ ResolveSkillQueryUseCase(
276
+ registry_client,
277
+ policy_context=_effective_policy_context(
278
+ allowed_trust_tiers_override=allowed_trust_tiers_override,
279
+ allowed_lifecycle_statuses_override=allowed_lifecycle_statuses_override,
280
+ max_token_estimate_override=max_token_estimate_override,
281
+ max_content_size_bytes_override=max_content_size_bytes_override,
282
+ cwd=cwd,
283
+ ),
284
+ selection_preferences=_effective_selection_preferences(
285
+ selection_profile_override=selection_profile_override,
286
+ interaction_mode_override=interaction_mode_override,
287
+ cwd=cwd,
288
+ ),
289
+ ),
290
+ close,
291
+ )
292
+
293
+
294
+ def build_install_use_case(
295
+ *,
296
+ selection_profile_override: str | None = None,
297
+ interaction_mode_override: str | None = None,
298
+ allowed_trust_tiers_override: list[str] | None = None,
299
+ allowed_lifecycle_statuses_override: list[str] | None = None,
300
+ max_token_estimate_override: int | None = None,
301
+ max_content_size_bytes_override: int | None = None,
302
+ cwd: Path | None = None,
303
+ ) -> tuple[InstallSkillUseCase, Callable[[], None]]:
304
+ """Create the install use case and its cleanup hook."""
305
+
306
+ registry_client, close = build_registry_client()
307
+ return (
308
+ InstallSkillUseCase(
309
+ registry_client,
310
+ policy_context=_effective_policy_context(
311
+ allowed_trust_tiers_override=allowed_trust_tiers_override,
312
+ allowed_lifecycle_statuses_override=allowed_lifecycle_statuses_override,
313
+ max_token_estimate_override=max_token_estimate_override,
314
+ max_content_size_bytes_override=max_content_size_bytes_override,
315
+ cwd=cwd,
316
+ ),
317
+ selection_preferences=_effective_selection_preferences(
318
+ selection_profile_override=selection_profile_override,
319
+ interaction_mode_override=interaction_mode_override,
320
+ cwd=cwd,
321
+ ),
322
+ ),
323
+ close,
324
+ )
325
+
326
+
327
+ def build_sync_use_case() -> tuple[SyncFromLockUseCase, Callable[[], None]]:
328
+ """Create the sync use case and its cleanup hook."""
329
+
330
+ registry_client, close = build_registry_client()
331
+ return SyncFromLockUseCase(registry_client), close
@@ -0,0 +1,59 @@
1
+ """Application DTO package."""
2
+
3
+ from aptitude_resolver.application.dto.install_dto import (
4
+ InstallRequestDto,
5
+ InstallResultDto,
6
+ InstalledSkillDto,
7
+ SyncRequestDto,
8
+ SyncResultDto,
9
+ )
10
+ from aptitude_resolver.application.dto.resolve_request_dto import ResolveQueryRequestDto
11
+ from aptitude_resolver.application.dto.resolve_result_dto import (
12
+ ConflictDto,
13
+ DiscoveryCandidateDto,
14
+ ExecutionPlanDto,
15
+ ExecutionStepDto,
16
+ GovernanceSnapshotDto,
17
+ LockedEdgeDto,
18
+ LockedSkillDto,
19
+ LockfileDto,
20
+ LockRootDto,
21
+ PolicySnapshotDto,
22
+ SelectionSnapshotDto,
23
+ PolicyEvaluationDto,
24
+ ResolvedEdgeDto,
25
+ ResolvedGraphDto,
26
+ ResolvedSkillNodeDto,
27
+ ResolveCoordinateDto,
28
+ ResolveQueryResultDto,
29
+ ResolveSkillSummaryDto,
30
+ TraceEntryDto,
31
+ )
32
+
33
+ __all__ = [
34
+ "ConflictDto",
35
+ "DiscoveryCandidateDto",
36
+ "ExecutionPlanDto",
37
+ "ExecutionStepDto",
38
+ "GovernanceSnapshotDto",
39
+ "InstallRequestDto",
40
+ "InstallResultDto",
41
+ "InstalledSkillDto",
42
+ "LockedEdgeDto",
43
+ "LockedSkillDto",
44
+ "LockfileDto",
45
+ "LockRootDto",
46
+ "PolicySnapshotDto",
47
+ "SelectionSnapshotDto",
48
+ "PolicyEvaluationDto",
49
+ "ResolvedEdgeDto",
50
+ "ResolvedGraphDto",
51
+ "ResolvedSkillNodeDto",
52
+ "ResolveCoordinateDto",
53
+ "ResolveQueryRequestDto",
54
+ "ResolveQueryResultDto",
55
+ "ResolveSkillSummaryDto",
56
+ "SyncRequestDto",
57
+ "SyncResultDto",
58
+ "TraceEntryDto",
59
+ ]
@@ -0,0 +1,88 @@
1
+ """DTOs for install and local materialization flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from aptitude_resolver.application.dto.resolve_result_dto import (
11
+ DiscoveryCandidateDto,
12
+ ExecutionPlanDto,
13
+ LockfileDto,
14
+ PolicyEvaluationDto,
15
+ ResolvedGraphDto,
16
+ ResolveCoordinateDto,
17
+ TraceEntryDto,
18
+ )
19
+
20
+
21
+ class InstallRequestDto(BaseModel):
22
+ """Install request coming from the CLI layer."""
23
+
24
+ model_config = ConfigDict(frozen=True)
25
+
26
+ query: str
27
+ target: Path
28
+ version: str | None = None
29
+ select_slug: str | None = None
30
+ interaction_mode: Literal["auto", "always", "never"] | None = None
31
+ prompt_capable: bool = False
32
+ selection_source: str | None = None
33
+
34
+
35
+ class SyncRequestDto(BaseModel):
36
+ """Sync request coming from the CLI layer."""
37
+
38
+ model_config = ConfigDict(frozen=True)
39
+
40
+ lock_path: Path
41
+ target: Path
42
+
43
+
44
+ class InstalledSkillDto(BaseModel):
45
+ """One exact coordinate materialized locally."""
46
+
47
+ model_config = ConfigDict(frozen=True)
48
+
49
+ slug: str
50
+ version: str
51
+ install_path: str
52
+
53
+
54
+ class InstallResultDto(BaseModel):
55
+ """Install command output."""
56
+
57
+ model_config = ConfigDict(frozen=True)
58
+
59
+ requested_query: str
60
+ requested_version: str | None = None
61
+ status: Literal["selection_required", "installed"]
62
+ selection_mode: str | None = None
63
+ candidates: list[DiscoveryCandidateDto] = Field(default_factory=list)
64
+ selected_coordinate: ResolveCoordinateDto | None = None
65
+ graph: ResolvedGraphDto | None = None
66
+ lockfile: LockfileDto | None = None
67
+ execution_plan: ExecutionPlanDto | None = None
68
+ installed_skills: list[InstalledSkillDto] = Field(default_factory=list)
69
+ materialized_root: str | None = None
70
+ trace: list[TraceEntryDto] = Field(default_factory=list)
71
+ policy_evaluations: list[PolicyEvaluationDto] = Field(default_factory=list)
72
+
73
+
74
+ class SyncResultDto(BaseModel):
75
+ """Sync command output."""
76
+
77
+ model_config = ConfigDict(frozen=True)
78
+
79
+ lock_path: str
80
+ requested_query: str
81
+ status: Literal["synced"]
82
+ selection_mode: str | None = None
83
+ selected_coordinate: ResolveCoordinateDto | None = None
84
+ lockfile: LockfileDto
85
+ execution_plan: ExecutionPlanDto
86
+ installed_skills: list[InstalledSkillDto] = Field(default_factory=list)
87
+ materialized_root: str | None = None
88
+ trace: list[TraceEntryDto] = Field(default_factory=list)
@@ -0,0 +1,20 @@
1
+ """Request DTOs for discovery-backed skill resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+
10
+ class ResolveQueryRequestDto(BaseModel):
11
+ """Query-driven input for discovery-backed resolution."""
12
+
13
+ model_config = ConfigDict(frozen=True)
14
+
15
+ query: str
16
+ version: str | None = None
17
+ select_slug: str | None = None
18
+ interaction_mode: Literal["auto", "always", "never"] | None = None
19
+ prompt_capable: bool = False
20
+ selection_source: str | None = None