@blxzer/cursor-trellis 0.1.3 → 0.2.2

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 (76) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +45 -23
  3. package/dist/configurators/workflow.d.ts +1 -1
  4. package/dist/configurators/workflow.d.ts.map +1 -1
  5. package/dist/configurators/workflow.js +20 -9
  6. package/dist/configurators/workflow.js.map +1 -1
  7. package/dist/migrations/manifests/0.1.3.json +9 -0
  8. package/dist/migrations/manifests/0.1.4.json +9 -0
  9. package/dist/migrations/manifests/0.2.1.json +9 -0
  10. package/dist/templates/markdown/agents.md +1 -1
  11. package/dist/templates/markdown/index.d.ts +1 -0
  12. package/dist/templates/markdown/index.d.ts.map +1 -1
  13. package/dist/templates/markdown/index.js +1 -0
  14. package/dist/templates/markdown/index.js.map +1 -1
  15. package/dist/templates/markdown/prompts/run-semantic-slice-12.md.txt +13 -29
  16. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +0 -49
  17. package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +0 -85
  18. package/dist/templates/markdown/spec/guides/cursor-semantic-compliance.md.txt +7 -21
  19. package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +6 -10
  20. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +7 -13
  21. package/dist/templates/shared-hooks/inject-retrieval-plan.py +42 -23
  22. package/dist/templates/shared-hooks/inject-subagent-context.py +6 -2
  23. package/dist/templates/shared-hooks/session-start.py +17 -9
  24. package/dist/templates/trellis/index.d.ts +7 -4
  25. package/dist/templates/trellis/index.d.ts.map +1 -1
  26. package/dist/templates/trellis/index.js +11 -7
  27. package/dist/templates/trellis/index.js.map +1 -1
  28. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +2 -2
  29. package/dist/templates/trellis/scripts/common/config.py +1 -1
  30. package/dist/templates/trellis/scripts/common/parent_orchestration.py +27 -15
  31. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +1 -1
  32. package/dist/templates/trellis/scripts/common/smart_search_resolve.py +28 -3
  33. package/dist/templates/trellis/scripts/common/task_gates.py +372 -73
  34. package/dist/templates/trellis/scripts/common/task_map.py +29 -1
  35. package/dist/templates/trellis/workflow.md +9 -53
  36. package/package.json +5 -44
  37. package/scripts/postinstall.js +4 -42
  38. package/bin/smart-search.js +0 -79
  39. package/dist/templates/trellis/scripts/aggregate_retrieval_telemetry.py +0 -342
  40. package/dist/templates/trellis/scripts/batch_plan_envelope.py +0 -105
  41. package/vendor/smart-search/LICENSE +0 -21
  42. package/vendor/smart-search/README.md +0 -412
  43. package/vendor/smart-search/README.zh-CN.md +0 -430
  44. package/vendor/smart-search/npm/bin/smart-search.js +0 -63
  45. package/vendor/smart-search/npm/scripts/postinstall.js +0 -87
  46. package/vendor/smart-search/npm/scripts/resolve-prerelease-version.js +0 -108
  47. package/vendor/smart-search/npm/scripts/set-package-version.js +0 -35
  48. package/vendor/smart-search/npm/scripts/sync-python-version.js +0 -22
  49. package/vendor/smart-search/npm/scripts/test-wrapper-repair.js +0 -99
  50. package/vendor/smart-search/npm/scripts/test.js +0 -85
  51. package/vendor/smart-search/package.json +0 -42
  52. package/vendor/smart-search/pyproject.toml +0 -37
  53. package/vendor/smart-search/skills/smart-search-cli/SKILL.md +0 -387
  54. package/vendor/smart-search/skills/smart-search-cli/agents/openai.yaml +0 -3
  55. package/vendor/smart-search/skills/smart-search-cli/examples/batch-search.md +0 -98
  56. package/vendor/smart-search/skills/smart-search-cli/examples/evidence-gathering.md +0 -89
  57. package/vendor/smart-search/skills/smart-search-cli/references/cli-contract.md +0 -337
  58. package/vendor/smart-search/src/smart_search/__init__.py +0 -1
  59. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/SKILL.md +0 -387
  60. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/agents/openai.yaml +0 -3
  61. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/examples/batch-search.md +0 -98
  62. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/examples/evidence-gathering.md +0 -89
  63. package/vendor/smart-search/src/smart_search/assets/skills/smart-search-cli/references/cli-contract.md +0 -337
  64. package/vendor/smart-search/src/smart_search/cli.py +0 -2003
  65. package/vendor/smart-search/src/smart_search/config.py +0 -580
  66. package/vendor/smart-search/src/smart_search/logger.py +0 -43
  67. package/vendor/smart-search/src/smart_search/providers/__init__.py +0 -16
  68. package/vendor/smart-search/src/smart_search/providers/base.py +0 -41
  69. package/vendor/smart-search/src/smart_search/providers/context7.py +0 -141
  70. package/vendor/smart-search/src/smart_search/providers/exa.py +0 -206
  71. package/vendor/smart-search/src/smart_search/providers/jina.py +0 -136
  72. package/vendor/smart-search/src/smart_search/providers/openai_compatible.py +0 -370
  73. package/vendor/smart-search/src/smart_search/providers/zhipu.py +0 -143
  74. package/vendor/smart-search/src/smart_search/service.py +0 -2865
  75. package/vendor/smart-search/src/smart_search/sources.py +0 -429
  76. package/vendor/smart-search/src/smart_search/utils.py +0 -220
@@ -1,141 +0,0 @@
1
- import json
2
- import time
3
- from typing import Any
4
- from urllib.parse import quote
5
-
6
- import httpx
7
- from tenacity import AsyncRetrying, retry_if_exception, stop_after_attempt, wait_random_exponential
8
-
9
- from .base import BaseSearchProvider
10
- from ..config import config
11
- from ..logger import log_info
12
-
13
-
14
- RETRYABLE_STATUS_CODES = {408, 429, 500, 502, 503, 504}
15
-
16
-
17
- def _is_retryable_exception(exc) -> bool:
18
- if isinstance(exc, (httpx.TimeoutException, httpx.NetworkError, httpx.ConnectError)):
19
- return True
20
- if isinstance(exc, httpx.HTTPStatusError):
21
- return exc.response.status_code in RETRYABLE_STATUS_CODES
22
- return False
23
-
24
-
25
- def _normalize_library(item: dict[str, Any]) -> dict[str, Any]:
26
- return {
27
- "id": item.get("id") or "",
28
- "title": item.get("title") or "",
29
- "description": item.get("description") or "",
30
- "trust_score": item.get("trustScore"),
31
- "benchmark_score": item.get("benchmarkScore"),
32
- "total_snippets": item.get("totalSnippets"),
33
- "stars": item.get("stars"),
34
- "provider": "context7",
35
- }
36
-
37
-
38
- class Context7Provider(BaseSearchProvider):
39
- def __init__(self, api_url: str, api_key: str, timeout: float = 30.0):
40
- super().__init__(api_url.rstrip("/"), api_key)
41
- self.timeout = timeout
42
-
43
- def get_provider_name(self) -> str:
44
- return "Context7"
45
-
46
- async def search(self, query: str, max_results: int = 5) -> str:
47
- return await self.library(query)
48
-
49
- def _headers(self) -> dict[str, str]:
50
- headers = {
51
- "Accept": "application/json, text/plain",
52
- "X-Context7-Source": "smart-search",
53
- }
54
- if self.api_key:
55
- headers["Authorization"] = f"Bearer {self.api_key}"
56
- return headers
57
-
58
- async def library(self, name: str, query: str = "", ctx=None) -> str:
59
- request_query = f"{name} {query}".strip()
60
- endpoint = f"{self.api_url}/api/v2/search?query={quote(request_query)}"
61
- await log_info(ctx, f"Context7 library: {request_query}", config.debug_enabled)
62
- start_time = time.time()
63
- try:
64
- data = await self._get_with_retry(endpoint)
65
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
66
- raw_results = data if isinstance(data, list) else data.get("results", [])
67
- results = [_normalize_library(item) for item in raw_results or []]
68
- output = {
69
- "ok": True,
70
- "query": request_query,
71
- "provider": "context7",
72
- "results": results,
73
- "total": len(results),
74
- "elapsed_ms": elapsed_ms,
75
- }
76
- except Exception as e:
77
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
78
- output = {
79
- "ok": False,
80
- "query": request_query,
81
- "provider": "context7",
82
- "error": str(e),
83
- "elapsed_ms": elapsed_ms,
84
- }
85
- return json.dumps(output, ensure_ascii=False, indent=2)
86
-
87
- async def docs(self, library_id: str, query: str, ctx=None) -> str:
88
- endpoint = f"{self.api_url}/api/v2/context?libraryId={quote(library_id, safe='')}&query={quote(query)}"
89
- await log_info(ctx, f"Context7 docs: {library_id} {query}", config.debug_enabled)
90
- start_time = time.time()
91
- try:
92
- data = await self._get_with_retry(endpoint)
93
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
94
- snippets = data.get("codeSnippets", []) if isinstance(data, dict) else []
95
- info = data.get("infoSnippets", []) if isinstance(data, dict) else []
96
- content = json.dumps(data, ensure_ascii=False) if isinstance(data, dict) else str(data)
97
- output = {
98
- "ok": True,
99
- "library_id": library_id,
100
- "query": query,
101
- "provider": "context7",
102
- "code_snippets": snippets,
103
- "info_snippets": info,
104
- "results": snippets + info,
105
- "total": len(snippets) + len(info),
106
- "content": content,
107
- "elapsed_ms": elapsed_ms,
108
- }
109
- except Exception as e:
110
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
111
- output = {
112
- "ok": False,
113
- "library_id": library_id,
114
- "query": query,
115
- "provider": "context7",
116
- "error": str(e),
117
- "elapsed_ms": elapsed_ms,
118
- }
119
- return json.dumps(output, ensure_ascii=False, indent=2)
120
-
121
- async def _get_with_retry(self, endpoint: str) -> Any:
122
- timeout = httpx.Timeout(connect=6.0, read=self.timeout, write=10.0, pool=None)
123
- async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
124
- async for attempt in AsyncRetrying(
125
- stop=stop_after_attempt(config.retry_max_attempts + 1),
126
- wait=wait_random_exponential(multiplier=config.retry_multiplier, max=config.retry_max_wait),
127
- retry=retry_if_exception(_is_retryable_exception),
128
- reraise=True,
129
- ):
130
- with attempt:
131
- response = await client.get(endpoint, headers=self._headers())
132
- response.raise_for_status()
133
- content_type = response.headers.get("content-type", "")
134
- if "application/json" in content_type:
135
- return response.json()
136
- text = response.text
137
- try:
138
- return json.loads(text)
139
- except json.JSONDecodeError:
140
- return {"content": text, "results": []}
141
- return {}
@@ -1,206 +0,0 @@
1
- import json
2
- import time
3
- from typing import Any
4
-
5
- import httpx
6
- from tenacity import AsyncRetrying, retry_if_exception, stop_after_attempt, wait_random_exponential
7
-
8
- from .base import BaseSearchProvider
9
- from ..config import config
10
- from ..logger import log_info
11
-
12
-
13
- RETRYABLE_STATUS_CODES = {408, 429, 500, 502, 503, 504}
14
-
15
-
16
- def _is_retryable_exception(exc) -> bool:
17
- if isinstance(exc, (httpx.TimeoutException, httpx.NetworkError, httpx.ConnectError)):
18
- return True
19
- if isinstance(exc, httpx.HTTPStatusError):
20
- return exc.response.status_code in RETRYABLE_STATUS_CODES
21
- return False
22
-
23
-
24
- def _normalize_result(item: dict[str, Any], *, include_text: bool, include_highlights: bool) -> dict[str, Any]:
25
- out = {
26
- "id": item.get("id"),
27
- "title": item.get("title") or "",
28
- "url": item.get("url") or item.get("id") or "",
29
- "publishedDate": item.get("publishedDate"),
30
- "author": item.get("author") or "",
31
- "score": item.get("score"),
32
- }
33
- if include_text and "text" in item:
34
- out["text"] = item.get("text") or ""
35
- if include_highlights and "highlights" in item:
36
- out["highlights"] = item.get("highlights") or []
37
- if "image" in item:
38
- out["image"] = item.get("image")
39
- if "favicon" in item:
40
- out["favicon"] = item.get("favicon")
41
- return out
42
-
43
-
44
- def _error_payload(exc: Exception) -> dict[str, Any]:
45
- if isinstance(exc, httpx.HTTPStatusError):
46
- status_code = exc.response.status_code
47
- body = exc.response.text.strip()
48
- detail = f" - {body[:500]}" if body else ""
49
- if status_code == 429:
50
- error_type = "rate_limited"
51
- elif status_code in {400, 422}:
52
- error_type = "parameter_error"
53
- elif status_code in {401, 403}:
54
- error_type = "auth_error"
55
- else:
56
- error_type = "network_error"
57
- return {"error_type": error_type, "error": f"HTTP {status_code}: {exc.response.reason_phrase}{detail}"}
58
- if isinstance(exc, httpx.TimeoutException):
59
- return {"error_type": "timeout", "error": "request timed out"}
60
- if isinstance(exc, httpx.RequestError):
61
- return {"error_type": "network_error", "error": str(exc)}
62
- return {"error_type": "runtime_error", "error": str(exc)}
63
-
64
-
65
- class ExaSearchProvider(BaseSearchProvider):
66
- def __init__(self, api_url: str, api_key: str, timeout: float = 30.0):
67
- super().__init__(api_url, api_key)
68
- self.timeout = timeout
69
-
70
- def get_provider_name(self) -> str:
71
- return "Exa"
72
-
73
- async def search(
74
- self,
75
- query: str,
76
- num_results: int = 5,
77
- search_type: str = "neural",
78
- include_text: bool = False,
79
- include_highlights: bool = False,
80
- start_published_date: str | None = None,
81
- include_domains: list[str] | None = None,
82
- exclude_domains: list[str] | None = None,
83
- category: str | None = None,
84
- ctx=None,
85
- ) -> str:
86
- endpoint = f"{self.api_url.rstrip('/')}/search"
87
- headers = {
88
- "accept": "application/json",
89
- "content-type": "application/json",
90
- "x-api-key": self.api_key,
91
- }
92
- payload: dict[str, Any] = {
93
- "query": query,
94
- "numResults": num_results,
95
- "type": search_type,
96
- "useAutoprompt": True,
97
- }
98
- if include_text or include_highlights:
99
- payload["contents"] = {
100
- "text": include_text,
101
- "highlights": include_highlights,
102
- }
103
- if start_published_date:
104
- payload["startPublishedDate"] = start_published_date
105
- if include_domains:
106
- payload["includeDomains"] = include_domains
107
- if exclude_domains:
108
- payload["excludeDomains"] = exclude_domains
109
- if category:
110
- payload["category"] = category
111
-
112
- await log_info(ctx, f"Exa search: {query}", config.debug_enabled)
113
-
114
- start_time = time.time()
115
- try:
116
- data = await self._request_with_retry(endpoint, headers, payload, ctx)
117
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
118
-
119
- results = [
120
- _normalize_result(item, include_text=include_text, include_highlights=include_highlights)
121
- for item in data.get("results", [])
122
- ]
123
-
124
- output = {
125
- "ok": True,
126
- "query": query,
127
- "search_type": search_type,
128
- "results": results,
129
- "total": len(results),
130
- "elapsed_ms": elapsed_ms,
131
- }
132
- except Exception as e:
133
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
134
- error = _error_payload(e)
135
- output = {
136
- "ok": False,
137
- "query": query,
138
- "error_type": error["error_type"],
139
- "error": error["error"],
140
- "elapsed_ms": elapsed_ms,
141
- }
142
-
143
- await log_info(ctx, "Exa search finished!", config.debug_enabled)
144
- return json.dumps(output, ensure_ascii=False, indent=2)
145
-
146
- async def find_similar(self, url: str, num_results: int = 5, ctx=None) -> str:
147
- endpoint = f"{self.api_url.rstrip('/')}/findSimilar"
148
- headers = {
149
- "accept": "application/json",
150
- "content-type": "application/json",
151
- "x-api-key": self.api_key,
152
- }
153
- payload = {
154
- "url": url,
155
- "numResults": num_results,
156
- }
157
-
158
- await log_info(ctx, f"Exa find_similar: {url}", config.debug_enabled)
159
-
160
- start_time = time.time()
161
- try:
162
- data = await self._request_with_retry(endpoint, headers, payload, ctx)
163
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
164
-
165
- results = [
166
- _normalize_result(item, include_text=False, include_highlights=False)
167
- for item in data.get("results", [])
168
- ]
169
-
170
- output = {
171
- "ok": True,
172
- "url": url,
173
- "results": results,
174
- "total": len(results),
175
- "elapsed_ms": elapsed_ms,
176
- }
177
- except Exception as e:
178
- elapsed_ms = round((time.time() - start_time) * 1000, 2)
179
- error = _error_payload(e)
180
- output = {
181
- "ok": False,
182
- "url": url,
183
- "error_type": error["error_type"],
184
- "error": error["error"],
185
- "elapsed_ms": elapsed_ms,
186
- }
187
-
188
- await log_info(ctx, "Exa find_similar finished!", config.debug_enabled)
189
- return json.dumps(output, ensure_ascii=False, indent=2)
190
-
191
- async def _request_with_retry(
192
- self, endpoint: str, headers: dict, payload: dict, ctx=None
193
- ) -> dict[str, Any]:
194
- timeout = httpx.Timeout(connect=6.0, read=self.timeout, write=10.0, pool=None)
195
-
196
- async with httpx.AsyncClient(timeout=timeout) as client:
197
- async for attempt in AsyncRetrying(
198
- stop=stop_after_attempt(config.retry_max_attempts + 1),
199
- wait=wait_random_exponential(multiplier=config.retry_multiplier, max=config.retry_max_wait),
200
- retry=retry_if_exception(_is_retryable_exception),
201
- reraise=True,
202
- ):
203
- with attempt:
204
- response = await client.post(endpoint, headers=headers, json=payload)
205
- response.raise_for_status()
206
- return response.json()
@@ -1,136 +0,0 @@
1
- import json
2
- import time
3
- from typing import Any
4
-
5
- import httpx
6
-
7
-
8
- CHALLENGE_MARKERS = (
9
- "title: just a moment",
10
- "checking if the site connection is secure",
11
- "attention required! | cloudflare",
12
- "enable javascript and cookies to continue",
13
- )
14
-
15
-
16
- def _elapsed_ms(start: float) -> float:
17
- return round((time.time() - start) * 1000, 2)
18
-
19
-
20
- def _error_payload(exc: Exception) -> dict[str, str]:
21
- if isinstance(exc, httpx.HTTPStatusError):
22
- status_code = exc.response.status_code
23
- if status_code in {401, 403}:
24
- error_type = "auth_error"
25
- elif status_code == 422:
26
- error_type = "parameter_error"
27
- elif status_code == 429:
28
- error_type = "rate_limited"
29
- else:
30
- error_type = "network_error"
31
- body = (exc.response.text or exc.response.reason_phrase or "")[:300]
32
- return {"error_type": error_type, "error": f"HTTP {status_code}: {body}"}
33
- if isinstance(exc, httpx.TimeoutException):
34
- return {"error_type": "timeout", "error": "request timed out"}
35
- if isinstance(exc, httpx.RequestError):
36
- return {"error_type": "network_error", "error": str(exc)}
37
- return {"error_type": "runtime_error", "error": str(exc)}
38
-
39
-
40
- def _mask_secret(text: str, secret: str) -> str:
41
- return text.replace(secret, "***") if secret else text
42
-
43
-
44
- def _quality_error(content: str) -> str:
45
- lower = content.strip().lower()
46
- if not lower:
47
- return "empty response"
48
- for marker in CHALLENGE_MARKERS:
49
- if marker in lower:
50
- return f"low-quality challenge page detected: {marker}"
51
- return ""
52
-
53
-
54
- class JinaReaderProvider:
55
- def __init__(
56
- self,
57
- reader_api_url: str,
58
- api_key: str | None = None,
59
- respond_with: str = "",
60
- timeout: float = 30.0,
61
- ):
62
- self.reader_api_url = reader_api_url.rstrip("/")
63
- self.api_key = api_key or ""
64
- self.respond_with = respond_with.strip()
65
- self.timeout = timeout
66
-
67
- async def fetch(self, url: str) -> str:
68
- start = time.time()
69
- if self.respond_with and not self.api_key:
70
- return json.dumps(
71
- {
72
- "ok": False,
73
- "provider": "jina",
74
- "url": url,
75
- "error_type": "config_error",
76
- "error": "JINA_RESPOND_WITH requires JINA_API_KEY.",
77
- "elapsed_ms": _elapsed_ms(start),
78
- },
79
- ensure_ascii=False,
80
- indent=2,
81
- )
82
-
83
- headers = {"X-Return-Format": "markdown", "Accept": "text/plain, text/markdown, */*"}
84
- if self.respond_with:
85
- headers["X-Respond-With"] = self.respond_with
86
- if self.api_key:
87
- headers["Authorization"] = f"Bearer {self.api_key}"
88
-
89
- endpoint = f"{self.reader_api_url}/{url}"
90
- try:
91
- timeout = httpx.Timeout(connect=6.0, read=self.timeout, write=10.0, pool=None)
92
- async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
93
- response = await client.get(endpoint, headers=headers)
94
- response.raise_for_status()
95
- content = response.text.strip()
96
- quality_error = _quality_error(content)
97
- if quality_error:
98
- return json.dumps(
99
- {
100
- "ok": False,
101
- "provider": "jina",
102
- "url": url,
103
- "error_type": "quality_error",
104
- "error": quality_error,
105
- "content": content,
106
- "elapsed_ms": _elapsed_ms(start),
107
- },
108
- ensure_ascii=False,
109
- indent=2,
110
- )
111
- return json.dumps(
112
- {
113
- "ok": True,
114
- "provider": "jina",
115
- "url": url,
116
- "content": content,
117
- "elapsed_ms": _elapsed_ms(start),
118
- },
119
- ensure_ascii=False,
120
- indent=2,
121
- )
122
- except Exception as e:
123
- error = _error_payload(e)
124
- message = _mask_secret(error["error"], self.api_key)
125
- return json.dumps(
126
- {
127
- "ok": False,
128
- "provider": "jina",
129
- "url": url,
130
- "error_type": error["error_type"],
131
- "error": message,
132
- "elapsed_ms": _elapsed_ms(start),
133
- },
134
- ensure_ascii=False,
135
- indent=2,
136
- )