claude-smart 0.2.46 → 0.2.47

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 (85) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +19 -11
  3. package/bin/claude-smart.js +290 -68
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +11 -10
  8. package/plugin/dashboard/app/layout.tsx +20 -0
  9. package/plugin/opencode/dist/server.mjs +76 -2
  10. package/plugin/opencode/server.mts +79 -2
  11. package/plugin/pyproject.toml +6 -2
  12. package/plugin/scripts/smart-install.sh +7 -1
  13. package/plugin/src/claude_smart/cli.py +210 -22
  14. package/plugin/src/claude_smart/context_format.py +9 -9
  15. package/plugin/src/claude_smart/cs_cite.py +66 -19
  16. package/plugin/uv.lock +5 -5
  17. package/plugin/vendor/reflexio/README.md +3 -3
  18. package/plugin/vendor/reflexio/pyproject.toml +1 -1
  19. package/plugin/vendor/reflexio/reflexio/README.md +3 -1
  20. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
  21. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
  22. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  23. package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
  24. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
  25. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  26. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
  27. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
  28. package/plugin/vendor/reflexio/reflexio/lib/_search.py +23 -5
  29. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
  30. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  31. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  32. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
  33. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +16 -0
  34. package/plugin/vendor/reflexio/reflexio/server/README.md +14 -2
  35. package/plugin/vendor/reflexio/reflexio/server/api.py +176 -29
  36. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
  37. package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
  38. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  39. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  40. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  41. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  42. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
  43. package/plugin/vendor/reflexio/reflexio/server/services/README.md +3 -1
  44. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
  45. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  46. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  47. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  48. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +45 -19
  49. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
  50. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  51. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +129 -111
  52. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
  53. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
  54. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  55. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
  56. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
  57. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  58. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +14 -2
  59. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +2 -0
  60. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
  61. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  62. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +5 -3
  63. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
  64. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +262 -107
  65. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
  66. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  67. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  68. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  69. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  70. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  71. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  72. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +19 -3
  73. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
  74. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  75. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
  76. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +13 -0
  77. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
  78. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  79. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  80. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  81. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  82. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  83. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  84. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
  85. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
@@ -0,0 +1,211 @@
1
+ """Recency adjustment helpers for unified search ranking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import math
7
+ from collections.abc import Mapping
8
+ from dataclasses import dataclass, replace
9
+ from datetime import UTC, datetime
10
+
11
+ from reflexio.server.env_utils import env_str, env_truthy
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ SECONDS_PER_DAY = 24 * 60 * 60
16
+ PLAYBOOK_HALF_LIFE_SECONDS = 90 * SECONDS_PER_DAY
17
+ _PROFILE_TTL_HALF_LIFE_FRACTION = 0.5
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ScoredItem[T]:
22
+ item: T
23
+ score: float | None
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class RecencyConfig:
28
+ enabled: bool = True
29
+ max_penalty_frac: float = 0.15
30
+ max_penalty_logit: float = 0.2
31
+ pool_size: int = 20
32
+
33
+ @classmethod
34
+ def from_env(cls, *, env: Mapping[str, str] | None = None) -> RecencyConfig:
35
+ return cls(
36
+ enabled=env_truthy(
37
+ env_str("REFLEXIO_SEARCH_RECENCY_ENABLED", "true", env=env)
38
+ ),
39
+ max_penalty_frac=_float_env(
40
+ "REFLEXIO_SEARCH_RECENCY_MAX_PENALTY_FRAC",
41
+ cls.max_penalty_frac,
42
+ env=env,
43
+ minimum=0.0,
44
+ maximum=1.0,
45
+ ),
46
+ max_penalty_logit=_float_env(
47
+ "REFLEXIO_SEARCH_RECENCY_MAX_PENALTY_LOGIT",
48
+ cls.max_penalty_logit,
49
+ env=env,
50
+ minimum=0.0,
51
+ ),
52
+ pool_size=_int_env(
53
+ "REFLEXIO_SEARCH_RECENCY_POOL_SIZE",
54
+ cls.pool_size,
55
+ env=env,
56
+ minimum=1,
57
+ ),
58
+ )
59
+
60
+ def with_overrides(self, values: Mapping[str, object] | None) -> RecencyConfig:
61
+ if not values:
62
+ return self
63
+ updates: dict[str, object] = {}
64
+ if "recency_enabled" in values:
65
+ updates["enabled"] = _bool_value(values["recency_enabled"], self.enabled)
66
+ if "recency_max_penalty_frac" in values:
67
+ updates["max_penalty_frac"] = _float_value(
68
+ values["recency_max_penalty_frac"],
69
+ self.max_penalty_frac,
70
+ minimum=0.0,
71
+ maximum=1.0,
72
+ )
73
+ if "recency_max_penalty_logit" in values:
74
+ updates["max_penalty_logit"] = _float_value(
75
+ values["recency_max_penalty_logit"],
76
+ self.max_penalty_logit,
77
+ minimum=0.0,
78
+ )
79
+ if "recency_pool_size" in values:
80
+ updates["pool_size"] = _int_value(
81
+ values["recency_pool_size"], self.pool_size, minimum=1
82
+ )
83
+ return replace(self, **updates)
84
+
85
+
86
+ def decay_for_item(
87
+ item: object,
88
+ *,
89
+ entity_type: str,
90
+ now: int | None = None,
91
+ ) -> float:
92
+ """Return a freshness decay in [0, 1], where 1 means no penalty."""
93
+ now = now if now is not None else int(datetime.now(UTC).timestamp())
94
+ if entity_type == "profiles":
95
+ return _profile_decay(item, now=now)
96
+ return _timestamp_decay(
97
+ getattr(item, "created_at", None),
98
+ half_life_seconds=PLAYBOOK_HALF_LIFE_SECONDS,
99
+ now=now,
100
+ )
101
+
102
+
103
+ def decay(age_seconds: float, half_life_seconds: float) -> float:
104
+ if half_life_seconds <= 0:
105
+ return 1.0
106
+ age_seconds = max(0.0, age_seconds)
107
+ return math.exp(-age_seconds / (half_life_seconds / math.log(2)))
108
+
109
+
110
+ def multiplicative_factor(decay_value: float, max_penalty_frac: float) -> float:
111
+ return 1.0 - _clamp(max_penalty_frac, 0.0, 1.0) * (1.0 - _decay(decay_value))
112
+
113
+
114
+ def additive_penalty(decay_value: float, max_penalty_logit: float) -> float:
115
+ return max(0.0, max_penalty_logit) * (1.0 - _decay(decay_value))
116
+
117
+
118
+ def _profile_decay(item: object, *, now: int) -> float:
119
+ modified_at = _int_or_none(getattr(item, "last_modified_timestamp", None))
120
+ expires_at = _int_or_none(getattr(item, "expiration_timestamp", None))
121
+ if modified_at is None or expires_at is None or expires_at <= modified_at:
122
+ return 1.0
123
+ half_life = (expires_at - modified_at) * _PROFILE_TTL_HALF_LIFE_FRACTION
124
+ return decay(now - modified_at, half_life)
125
+
126
+
127
+ def _timestamp_decay(value: object, *, half_life_seconds: int, now: int) -> float:
128
+ timestamp = _int_or_none(value)
129
+ if timestamp is None or timestamp <= 0:
130
+ return 1.0
131
+ return decay(now - timestamp, half_life_seconds)
132
+
133
+
134
+ def _float_env(
135
+ name: str,
136
+ default: float,
137
+ *,
138
+ env: Mapping[str, str] | None,
139
+ minimum: float,
140
+ maximum: float | None = None,
141
+ ) -> float:
142
+ return _float_value(
143
+ env_str(name, str(default), env=env),
144
+ default,
145
+ minimum=minimum,
146
+ maximum=maximum,
147
+ )
148
+
149
+
150
+ def _int_env(
151
+ name: str,
152
+ default: int,
153
+ *,
154
+ env: Mapping[str, str] | None,
155
+ minimum: int,
156
+ ) -> int:
157
+ return _int_value(env_str(name, str(default), env=env), default, minimum=minimum)
158
+
159
+
160
+ def _bool_value(value: object, default: bool) -> bool:
161
+ if isinstance(value, bool):
162
+ return value
163
+ if isinstance(value, str) and value.strip():
164
+ return env_truthy(value)
165
+ return default
166
+
167
+
168
+ def _float_value(
169
+ value: object,
170
+ default: float,
171
+ *,
172
+ minimum: float,
173
+ maximum: float | None = None,
174
+ ) -> float:
175
+ try:
176
+ if not isinstance(value, str | int | float):
177
+ raise TypeError
178
+ parsed = float(value)
179
+ except (TypeError, ValueError):
180
+ logger.warning("Invalid recency float override %r; using %.3f", value, default)
181
+ return default
182
+ parsed = max(minimum, parsed)
183
+ if maximum is not None:
184
+ parsed = min(maximum, parsed)
185
+ return parsed
186
+
187
+
188
+ def _int_value(value: object, default: int, *, minimum: int) -> int:
189
+ try:
190
+ if not isinstance(value, str | int):
191
+ raise TypeError
192
+ parsed = int(value)
193
+ except (TypeError, ValueError):
194
+ logger.warning("Invalid recency integer override %r; using %d", value, default)
195
+ return default
196
+ return max(minimum, parsed)
197
+
198
+
199
+ def _int_or_none(value: object) -> int | None:
200
+ try:
201
+ return int(value) # type: ignore[arg-type]
202
+ except (TypeError, ValueError):
203
+ return None
204
+
205
+
206
+ def _decay(value: float) -> float:
207
+ return _clamp(value, 0.0, 1.0)
208
+
209
+
210
+ def _clamp(value: float, low: float, high: float) -> float:
211
+ return min(high, max(low, value))
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import logging
12
12
  from collections.abc import Callable, Sequence
13
+ from dataclasses import dataclass
13
14
  from typing import Any
14
15
 
15
16
  from reflexio.server.llm.rerank import score_pairs
@@ -21,12 +22,20 @@ from reflexio.server.tracing import profile_step
21
22
  logger = logging.getLogger(__name__)
22
23
 
23
24
 
24
- def _floor_and_sort[T](items: list[T], scores: list[float], floor: float) -> list[T]:
25
- """Return items scoring >= ``floor``, sorted by score descending."""
25
+ @dataclass(frozen=True)
26
+ class RelevanceFloorResult:
27
+ items: list[Any]
28
+ scores: list[float] | None
29
+
30
+
31
+ def _floor_and_sort[T](
32
+ items: list[T], scores: list[float], floor: float
33
+ ) -> list[tuple[T, float]]:
34
+ """Return ``(item, score)`` pairs scoring >= ``floor``, sorted descending."""
26
35
  ranked = sorted(
27
36
  zip(items, scores, strict=True), key=lambda pair: pair[1], reverse=True
28
37
  )
29
- return [item for item, score in ranked if score >= floor]
38
+ return [(item, score) for item, score in ranked if score >= floor]
30
39
 
31
40
 
32
41
  def apply_relevance_floor[T](
@@ -71,7 +80,7 @@ def apply_relevance_floor[T](
71
80
  return items[:top_k]
72
81
  span.set_data("available", True)
73
82
 
74
- survivors = _floor_and_sort(items, scores, floor)
83
+ survivors = [item for item, _score in _floor_and_sort(items, scores, floor)]
75
84
  dropped = len(items) - len(survivors)
76
85
  span.set_data("kept", len(survivors))
77
86
  span.set_data("dropped", dropped)
@@ -92,7 +101,7 @@ def apply_relevance_floors(
92
101
  top_k: int,
93
102
  *,
94
103
  content_of: Callable[[Any], str] = lambda item: item.content,
95
- ) -> list[list[Any]]:
104
+ ) -> list[RelevanceFloorResult]:
96
105
  """Floor every arm with a single cross-encoder batch.
97
106
 
98
107
  CPU cross-encoder inference does not parallelize across threads, so
@@ -108,12 +117,13 @@ def apply_relevance_floors(
108
117
  content_of: Extracts the text to score for an item.
109
118
 
110
119
  Returns:
111
- One survivor list per arm, in input order each sorted by score
112
- descending and capped at ``top_k``. On reranker unavailability,
113
- every arm returns ``items[:top_k]`` unchanged (logged).
120
+ One result per arm, in input order. Available reranker results are
121
+ sorted by score descending and left uncapped with the paired raw logits.
122
+ On reranker unavailability, every arm returns the original full item
123
+ pool with a ``None`` score sentinel (logged).
114
124
  """
115
125
  if not any(items for _, items, _ in arms):
116
- return [[] for _ in arms]
126
+ return [RelevanceFloorResult([], []) for _ in arms]
117
127
  arm_names = [name for name, _, _ in arms]
118
128
  contents: list[str] = []
119
129
  for _, items, _ in arms:
@@ -123,20 +133,21 @@ def apply_relevance_floors(
123
133
  arm="all",
124
134
  arms=arm_names,
125
135
  items=len(contents),
136
+ top_k=top_k,
126
137
  ) as span:
127
138
  try:
128
139
  scores = score_pairs(query, contents)
129
140
  except CrossEncoderUnavailableError:
130
141
  span.set_data("available", False)
131
142
  logger.warning(
132
- "event=relevance_floor_unavailable arms=%s items=%d (returning unfiltered top_k)",
143
+ "event=relevance_floor_unavailable arms=%s items=%d (returning unfiltered pool)",
133
144
  arm_names,
134
145
  len(contents),
135
146
  )
136
- return [items[:top_k] for _, items, _ in arms]
147
+ return [RelevanceFloorResult(list(items), None) for _, items, _ in arms]
137
148
  span.set_data("available", True)
138
149
 
139
- results: list[list[Any]] = []
150
+ results: list[RelevanceFloorResult] = []
140
151
  offset = 0
141
152
  for name, items, floor in arms:
142
153
  arm_scores = scores[offset : offset + len(items)]
@@ -153,5 +164,10 @@ def apply_relevance_floors(
153
164
  dropped,
154
165
  floor,
155
166
  )
156
- results.append(survivors[:top_k])
167
+ results.append(
168
+ RelevanceFloorResult(
169
+ [item for item, _score in survivors],
170
+ [score for _item, score in survivors],
171
+ )
172
+ )
157
173
  return results
@@ -14,6 +14,10 @@ class StorageError(Exception):
14
14
  return f"StorageError: {self.message}"
15
15
 
16
16
 
17
+ class SubjectWriteBarrierError(StorageError):
18
+ """Raised when a write targets a subject with an active erasure barrier."""
19
+
20
+
17
21
  def require_non_empty_session_id(value: Any) -> str:
18
22
  """Return a stripped, non-empty request ``session_id`` or raise ``StorageError``.
19
23