@blxzer/cursor-trellis 0.1.1 → 0.1.3
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.
- package/README.md +2 -0
- package/dist/migrations/manifests/0.1.1.json +9 -0
- package/dist/migrations/manifests/0.1.2.json +9 -0
- package/dist/templates/cursor/rules/retrieval-routing.mdc +9 -7
- package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +2 -1
- package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +16 -3
- package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +11 -6
- package/dist/templates/shared-hooks/inject-subagent-context.py +31 -567
- package/dist/templates/trellis/index.d.ts +2 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +4 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +78 -29
- package/dist/templates/trellis/scripts/common/cursor_retrieval_env.py +92 -0
- package/dist/templates/trellis/scripts/common/retrieval_adapter_metadata.py +100 -9
- package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +76 -31
- package/dist/templates/trellis/scripts/common/retrieval_tool_classification.py +18 -3
- package/dist/templates/trellis/scripts/common/semantic_plan_gate.py +19 -0
- package/dist/templates/trellis/scripts/common/smart_search_evidence.py +5 -2
- package/dist/templates/trellis/scripts/common/subagent_dispatch.py +527 -0
- package/dist/templates/trellis/scripts/common/task_store.py +32 -0
- package/dist/templates/trellis/scripts/cursor_retrieval_probe.py +396 -0
- package/dist/templates/trellis/scripts/cursor_retrieval_probe_prompt.md +300 -0
- package/dist/templates/trellis/scripts/retrieval_probe_matrix_template.json +126 -0
- package/dist/templates/trellis/scripts/task.py +26 -0
- package/dist/templates/trellis/workflow.md +5 -22
- package/dist/utils/codebase-retrieval-router.d.ts +5 -0
- package/dist/utils/codebase-retrieval-router.d.ts.map +1 -1
- package/dist/utils/codebase-retrieval-router.js +48 -28
- package/dist/utils/codebase-retrieval-router.js.map +1 -1
- package/dist/utils/cursor-retrieval-env.d.ts +28 -0
- package/dist/utils/cursor-retrieval-env.d.ts.map +1 -0
- package/dist/utils/cursor-retrieval-env.js +89 -0
- package/dist/utils/cursor-retrieval-env.js.map +1 -0
- package/dist/utils/project-capabilities.d.ts.map +1 -1
- package/dist/utils/project-capabilities.js +22 -15
- package/dist/utils/project-capabilities.js.map +1 -1
- package/dist/utils/retrieval-agent-instructions.d.ts.map +1 -1
- package/dist/utils/retrieval-agent-instructions.js +37 -21
- package/dist/utils/retrieval-agent-instructions.js.map +1 -1
- package/dist/utils/retrieval-tool-classification.d.ts +2 -0
- package/dist/utils/retrieval-tool-classification.d.ts.map +1 -1
- package/dist/utils/retrieval-tool-classification.js +10 -2
- package/dist/utils/retrieval-tool-classification.js.map +1 -1
- package/dist/utils/semantic-plan-gate.d.ts +8 -0
- package/dist/utils/semantic-plan-gate.d.ts.map +1 -0
- package/dist/utils/semantic-plan-gate.js +42 -0
- package/dist/utils/semantic-plan-gate.js.map +1 -0
- package/package.json +3 -2
|
@@ -12,6 +12,8 @@ from __future__ import annotations
|
|
|
12
12
|
import re
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
|
+
from .cursor_retrieval_env import ENV_BYOK, detect_cursor_retrieval_env
|
|
16
|
+
|
|
15
17
|
_SYMBOL_CANDIDATE = re.compile(
|
|
16
18
|
r"\b([A-Za-z_][\w$]{2,})\b|"
|
|
17
19
|
r"`([^`]+)`|"
|
|
@@ -104,7 +106,13 @@ def _intent_summary(intents: list[dict[str, object]]) -> str:
|
|
|
104
106
|
return "、".join(parts)
|
|
105
107
|
|
|
106
108
|
|
|
107
|
-
def _cursor_step_for_route(
|
|
109
|
+
def _cursor_step_for_route(
|
|
110
|
+
route: dict[str, object],
|
|
111
|
+
*,
|
|
112
|
+
symbol: str,
|
|
113
|
+
query: str,
|
|
114
|
+
cursor_env: str,
|
|
115
|
+
) -> str | None:
|
|
108
116
|
role = str(route.get("role", ""))
|
|
109
117
|
route_id = str(route.get("id", ""))
|
|
110
118
|
|
|
@@ -126,6 +134,11 @@ def _cursor_step_for_route(route: dict[str, object], *, symbol: str, query: str)
|
|
|
126
134
|
return (
|
|
127
135
|
f"使用 **codegraph_search**,路径侧重 `extensions/`,符号 `{symbol}`。"
|
|
128
136
|
)
|
|
137
|
+
if route_id == "lsp-navigation":
|
|
138
|
+
return (
|
|
139
|
+
f"使用 **codegraph_node**(`includeCode=true`)或 **codegraph_search** 定位 `{symbol}` "
|
|
140
|
+
"的定义/引用(Agent 无 GO_TO_DEFINITION);再用 **Read** 验证行号与正文。"
|
|
141
|
+
)
|
|
129
142
|
if role == "ast" or route_id in ("ast-codegraph",):
|
|
130
143
|
return (
|
|
131
144
|
f"使用 **codegraph_explore** 或 **codegraph_node**(`includeCode=true`),"
|
|
@@ -135,21 +148,27 @@ def _cursor_step_for_route(route: dict[str, object], *, symbol: str, query: str)
|
|
|
135
148
|
if route_id == "platform-semantic" or (
|
|
136
149
|
role == "semantic" and str(route.get("sourceFamily")) == "platform-semantic"
|
|
137
150
|
):
|
|
151
|
+
backend = str(route.get("semanticBackend", ""))
|
|
152
|
+
if cursor_env == ENV_BYOK or backend == "fast-context-mcp":
|
|
153
|
+
return (
|
|
154
|
+
"使用 **fast_context_search**(fast-context MCP)做概念/代码库语义检索;"
|
|
155
|
+
"**不要**假设 @codebase 或内置 SemanticSearch 可用;**不要**用 WebSearch 答代码库问题。"
|
|
156
|
+
)
|
|
138
157
|
return (
|
|
139
|
-
"使用 Cursor
|
|
140
|
-
"
|
|
158
|
+
"使用 Cursor **内置代码库语义搜索**(宿主工具常为 **SemanticSearch**;"
|
|
159
|
+
"**`target_directories`** 为目录 glob 数组,省略会报错;**`[]`** = 不缩范围、搜全工作区索引;"
|
|
160
|
+
"若计划或 Grep/codegraph 已指向子树可传如 `['Trellis/packages/cli']`;"
|
|
161
|
+
"无路径线索的概念题优先 **`[]`**;Native 下 **不要**用 fast-context MCP 顶替)。"
|
|
141
162
|
)
|
|
142
163
|
if role == "semantic":
|
|
164
|
+
if cursor_env == ENV_BYOK:
|
|
165
|
+
return (
|
|
166
|
+
"使用 **fast_context_search**(fast-context MCP);勿用 WebSearch 答代码库问题。"
|
|
167
|
+
)
|
|
143
168
|
return (
|
|
144
169
|
"使用 Cursor **内置代码库语义搜索**(不要用 fast-context MCP)。"
|
|
145
170
|
)
|
|
146
171
|
|
|
147
|
-
if route_id == "lsp-navigation" or role == "lsp":
|
|
148
|
-
return (
|
|
149
|
-
f"可选:若宿主暴露 **GO_TO_DEFINITION** / 查找引用,在已有候选上核对 `{symbol}`;"
|
|
150
|
-
"非 Agent 默认保证,未调用时用 **Read** + **Grep** 验证。"
|
|
151
|
-
)
|
|
152
|
-
|
|
153
172
|
if route_id == "policy-docs-rg" or str(route.get("sourceFamily")) == "policy-docs":
|
|
154
173
|
return (
|
|
155
174
|
"使用 **Grep** 在 `AGENTS.md`、`**/AGENTS.md`、`.trellis/spec`、`README.md` "
|
|
@@ -162,6 +181,10 @@ def _cursor_step_for_route(route: dict[str, object], *, symbol: str, query: str)
|
|
|
162
181
|
)
|
|
163
182
|
|
|
164
183
|
if route_id == "cross-cutting-discovery" or "deep" in route_id.lower():
|
|
184
|
+
if cursor_env == ENV_BYOK:
|
|
185
|
+
return (
|
|
186
|
+
"复杂跨模块探索:使用 **Task** 子代理(explore),再用 Grep/codegraph/Read 验证。"
|
|
187
|
+
)
|
|
165
188
|
return (
|
|
166
189
|
"复杂跨模块探索:可选用 **DEEP_SEARCH** 或 Explore 子任务,再用 Grep/codegraph 验证。"
|
|
167
190
|
)
|
|
@@ -201,6 +224,7 @@ def render_agent_instructions(
|
|
|
201
224
|
verification_list = verification if isinstance(verification, list) else []
|
|
202
225
|
|
|
203
226
|
symbol = guess_symbol_from_query(query) or "<symbol>"
|
|
227
|
+
cursor_env = str(envelope.get("cursorEnv") or detect_cursor_retrieval_env())
|
|
204
228
|
|
|
205
229
|
if locale != "zh":
|
|
206
230
|
header = "## Codebase retrieval plan\n"
|
|
@@ -211,6 +235,7 @@ def render_agent_instructions(
|
|
|
211
235
|
header.rstrip(),
|
|
212
236
|
f"问题:{query or '(空)'}",
|
|
213
237
|
f"意图:{_intent_summary(intent_list)}",
|
|
238
|
+
f"检索环境(cursorEnv):{cursor_env}",
|
|
214
239
|
"",
|
|
215
240
|
]
|
|
216
241
|
|
|
@@ -222,7 +247,9 @@ def render_agent_instructions(
|
|
|
222
247
|
for route in sorted_routes:
|
|
223
248
|
if not isinstance(route, dict):
|
|
224
249
|
continue
|
|
225
|
-
text = _cursor_step_for_route(
|
|
250
|
+
text = _cursor_step_for_route(
|
|
251
|
+
route, symbol=symbol, query=query, cursor_env=cursor_env
|
|
252
|
+
)
|
|
226
253
|
if not text:
|
|
227
254
|
continue
|
|
228
255
|
step += 1
|
|
@@ -257,34 +284,52 @@ def render_agent_instructions(
|
|
|
257
284
|
|
|
258
285
|
lines.append("")
|
|
259
286
|
lines.append(
|
|
260
|
-
"**codegraph 独用场景**:调用链、跨包 trap、extension
|
|
261
|
-
"纯字面搜索用 Grep
|
|
287
|
+
"**codegraph 独用场景**:调用链、跨包 trap、extension 符号、影响面、定义/引用定位;"
|
|
288
|
+
"纯字面搜索用 Grep(勿用 GO_TO_DEFINITION,Agent 未暴露)。"
|
|
262
289
|
)
|
|
263
290
|
|
|
264
291
|
if any(
|
|
265
292
|
isinstance(r, dict) and r.get("id") == "platform-semantic" for r in route_list
|
|
266
293
|
):
|
|
267
294
|
lines.append("")
|
|
268
|
-
if
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"
|
|
277
|
-
|
|
295
|
+
if cursor_env == ENV_BYOK:
|
|
296
|
+
if locale != "zh":
|
|
297
|
+
lines.append("**Semantic compliance (Cursor++ BYOK):**")
|
|
298
|
+
lines.append(
|
|
299
|
+
"- Run **one** `fast_context_search` before final Top-1 when "
|
|
300
|
+
"**platform-semantic** is listed; log the MCP tool name."
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
lines.append("**语义合规(Cursor++ BYOK):**")
|
|
304
|
+
lines.append(
|
|
305
|
+
"- 本计划含 **platform-semantic** 时:定 Top-1 前至少 **1 次** "
|
|
306
|
+
"**fast_context_search**(fast-context MCP),并记录工具名。"
|
|
307
|
+
)
|
|
278
308
|
else:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
309
|
+
if locale != "zh":
|
|
310
|
+
lines.append("**Semantic compliance (Cursor Native):**")
|
|
311
|
+
lines.append(
|
|
312
|
+
"- When this plan lists **platform-semantic**, run **one** Cursor built-in "
|
|
313
|
+
"codebase / semantic search before final Top-1 (not fast-context MCP)."
|
|
314
|
+
)
|
|
315
|
+
lines.append(
|
|
316
|
+
"- Log the **exact tool name** from the host (e.g. codebase_search, @codebase) "
|
|
317
|
+
"in your run notes for semantic_exec_rate."
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
lines.append("**语义合规(Cursor Native):**")
|
|
321
|
+
lines.append(
|
|
322
|
+
"- 本计划含 **platform-semantic** 时:定 Top-1 前至少执行 **1 次** "
|
|
323
|
+
"Cursor **内置代码库语义搜索**(不要用 fast-context MCP)。"
|
|
324
|
+
)
|
|
325
|
+
lines.append(
|
|
326
|
+
"- 在 run 记录中写下宿主返回的**真实工具名**(如 SemanticSearch、codebase_search、@codebase),"
|
|
327
|
+
"供 semantic_exec 统计。"
|
|
328
|
+
)
|
|
329
|
+
lines.append(
|
|
330
|
+
"- 若工具为 **SemanticSearch**:**必须提供** **`target_directories`**;"
|
|
331
|
+
"**`[]`** = 全库;已知子目录时用 glob 收窄(勿省略该字段)。"
|
|
332
|
+
)
|
|
288
333
|
|
|
289
334
|
try:
|
|
290
335
|
from .semantic_plan_gate import semantic_compliance_gate_hint # noqa: PLC0415
|
|
@@ -88,7 +88,13 @@ class ClassifiedToolCalls:
|
|
|
88
88
|
cursor_fast_context_misuse: bool = False
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def classify_tool_calls(
|
|
91
|
+
def classify_tool_calls(
|
|
92
|
+
raw: list[str],
|
|
93
|
+
*,
|
|
94
|
+
platform: str = "cursor",
|
|
95
|
+
cursor_env: str | None = None,
|
|
96
|
+
route_ids: list[str] | None = None,
|
|
97
|
+
) -> ClassifiedToolCalls:
|
|
92
98
|
tools_called = list(raw)
|
|
93
99
|
grep_count = 0
|
|
94
100
|
read_count = 0
|
|
@@ -115,13 +121,22 @@ def classify_tool_calls(raw: list[str], *, platform: str = "cursor") -> Classifi
|
|
|
115
121
|
if is_platform_semantic_tool_name(trimmed):
|
|
116
122
|
platform_semantic_executed = True
|
|
117
123
|
|
|
124
|
+
env = (cursor_env or "").strip().lower()
|
|
118
125
|
if plat == "cursor":
|
|
119
|
-
|
|
126
|
+
if env == "byok":
|
|
127
|
+
semantic_executed = platform_semantic_executed or fast_context_count > 0
|
|
128
|
+
else:
|
|
129
|
+
semantic_executed = platform_semantic_executed
|
|
120
130
|
else:
|
|
121
131
|
semantic_executed = platform_semantic_executed or fast_context_count > 0
|
|
122
132
|
|
|
123
133
|
semantic_attempted = semantic_executed or fast_context_count > 0
|
|
124
|
-
|
|
134
|
+
|
|
135
|
+
misuse = (
|
|
136
|
+
plat == "cursor"
|
|
137
|
+
and fast_context_count > 0
|
|
138
|
+
and env != "byok"
|
|
139
|
+
)
|
|
125
140
|
|
|
126
141
|
return ClassifiedToolCalls(
|
|
127
142
|
tools_called=tools_called,
|
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
from .cursor_retrieval_env import ENV_BYOK, detect_cursor_retrieval_env
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
def platform_semantic_order_from_envelope(envelope: dict[str, Any]) -> int | None:
|
|
9
11
|
routes = envelope.get("routes")
|
|
@@ -37,6 +39,22 @@ def semantic_compliance_gate_hint(
|
|
|
37
39
|
if not any(isinstance(r, dict) and r.get("id") == "platform-semantic" for r in routes):
|
|
38
40
|
return ""
|
|
39
41
|
|
|
42
|
+
cursor_env = str(envelope.get("cursorEnv") or detect_cursor_retrieval_env())
|
|
43
|
+
|
|
44
|
+
if cursor_env == ENV_BYOK:
|
|
45
|
+
if locale != "zh":
|
|
46
|
+
return (
|
|
47
|
+
"**Plan gate (REC-11, BYOK):** `platform-semantic` is step "
|
|
48
|
+
f"#{order}. Before Top-1 you MUST run one **fast_context_search** "
|
|
49
|
+
"(fast-context MCP). Do not use WebSearch for codebase questions. "
|
|
50
|
+
"Corroborate hits with Read."
|
|
51
|
+
)
|
|
52
|
+
return (
|
|
53
|
+
"**计划门控(REC-11,BYOK):** 本问 `platform-semantic` 为第 "
|
|
54
|
+
f"{order} 步。定 Top-1 前 **必须** 执行 1 次 **fast_context_search**(fast-context MCP);"
|
|
55
|
+
"**禁止** 用 WebSearch 答代码库问题。命中路径后须 **Read** 验证。"
|
|
56
|
+
)
|
|
57
|
+
|
|
40
58
|
if locale != "zh":
|
|
41
59
|
return (
|
|
42
60
|
"**Plan gate (REC-11):** `platform-semantic` is step "
|
|
@@ -48,5 +66,6 @@ def semantic_compliance_gate_hint(
|
|
|
48
66
|
"**计划门控(REC-11):** 本问 `platform-semantic` 为第 "
|
|
49
67
|
f"{order} 步。定 Top-1 前 **必须** 执行 1 次 Cursor **内置代码库语义搜索**,"
|
|
50
68
|
"并在记录中写下宿主工具名;**禁止** fast-context MCP。"
|
|
69
|
+
"若工具为 **SemanticSearch**,须传 **`target_directories`**(`[]` 全库,或已知子目录 glob 收窄)。"
|
|
51
70
|
"语义命中路径后须 **Read** 验证候选片段(verified 层)。"
|
|
52
71
|
)
|
|
@@ -125,7 +125,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
125
125
|
print_manifest(manifest, args.json)
|
|
126
126
|
return 4
|
|
127
127
|
doctor_data = parse_json_output(doctor_result.stdout)
|
|
128
|
-
if doctor_result.returncode != 0 or doctor_data.get("ok")
|
|
128
|
+
if doctor_result.returncode != 0 or not doctor_data.get("ok"):
|
|
129
129
|
manifest = build_manifest(
|
|
130
130
|
repo_root=repo_root,
|
|
131
131
|
query=args.query,
|
|
@@ -188,7 +188,7 @@ class CommandResult:
|
|
|
188
188
|
self.not_found = not_found
|
|
189
189
|
|
|
190
190
|
|
|
191
|
-
def run_command(command: list[str], cwd: Path) -> CommandResult:
|
|
191
|
+
def run_command(command: list[str], cwd: Path, timeout: int = 120) -> CommandResult:
|
|
192
192
|
try:
|
|
193
193
|
completed = subprocess.run(
|
|
194
194
|
command,
|
|
@@ -198,8 +198,11 @@ def run_command(command: list[str], cwd: Path) -> CommandResult:
|
|
|
198
198
|
errors="replace",
|
|
199
199
|
capture_output=True,
|
|
200
200
|
check=False,
|
|
201
|
+
timeout=timeout,
|
|
201
202
|
)
|
|
202
203
|
return CommandResult(completed.returncode, completed.stdout or "", completed.stderr or "")
|
|
204
|
+
except subprocess.TimeoutExpired:
|
|
205
|
+
return CommandResult(124, "", f"Command timed out after {timeout}s")
|
|
203
206
|
except FileNotFoundError as error:
|
|
204
207
|
return CommandResult(127, "", str(error), not_found=True)
|
|
205
208
|
|