@blxzer/cursor-trellis 0.2.7 → 0.2.9
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/CHANGELOG.md +195 -133
- package/README.md +14 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +4 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate-rules.d.ts +6 -0
- package/dist/commands/validate-rules.d.ts.map +1 -0
- package/dist/commands/validate-rules.js +33 -0
- package/dist/commands/validate-rules.js.map +1 -0
- package/dist/configurators/cursor2plus-local.d.ts.map +1 -1
- package/dist/configurators/cursor2plus-local.js +2 -1
- package/dist/configurators/cursor2plus-local.js.map +1 -1
- package/dist/migrations/manifests/0.2.2.json +8 -8
- package/dist/migrations/manifests/0.2.4.json +8 -8
- package/dist/migrations/manifests/0.2.5.json +8 -8
- package/dist/migrations/manifests/0.2.6.json +8 -8
- package/dist/migrations/manifests/0.2.7.json +8 -8
- package/dist/migrations/manifests/0.2.8.json +9 -0
- package/dist/migrations/manifests/0.2.9.json +9 -0
- package/dist/templates/common/bundled-skills/smart-search-cli/SKILL.md +7 -8
- package/dist/templates/common/bundled-skills/smart-search-cli/references/cli-contract.md +46 -46
- package/dist/templates/common/bundled-skills/trellis-cursor2plus-setup/SKILL.md +2 -2
- package/dist/templates/common/commands/finish-work.md +12 -0
- package/dist/templates/common/skills/check.md +4 -0
- package/dist/templates/cursor/agents/trellis-check.md +32 -0
- package/dist/templates/cursor/agents/trellis-implement.md +11 -0
- package/dist/templates/cursor/agents/trellis-research.md +29 -0
- package/dist/templates/cursor/fixtures/expected-rules.d.ts +14 -0
- package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -0
- package/dist/templates/cursor/fixtures/expected-rules.js +26 -0
- package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -0
- package/dist/templates/cursor/rules/trellis-subagent-dispatch.mdc +34 -0
- package/dist/templates/markdown/spec/guides/execution-strategy.md.txt +42 -42
- package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +12 -0
- package/dist/templates/trellis/config/execution-strategy-rules.json +30 -30
- package/dist/templates/trellis/local/README.md +61 -37
- package/dist/templates/trellis/local/index.d.ts +1 -0
- package/dist/templates/trellis/local/index.d.ts.map +1 -1
- package/dist/templates/trellis/local/index.js +1 -0
- package/dist/templates/trellis/local/index.js.map +1 -1
- package/dist/templates/trellis/local/patch_wpelc8.py +162 -38
- package/dist/templates/trellis/local/smoke.py +94 -0
- package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +2 -2
- package/dist/templates/trellis/scripts/common/cursor_retrieval_env.py +16 -2
- package/dist/templates/trellis/scripts/common/execution_strategy.py +267 -267
- package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +26 -7
- package/dist/templates/trellis/scripts/common/semantic_plan_gate.py +18 -4
- package/dist/templates/trellis/scripts/common/smart_search_evidence.py +33 -1
- package/dist/templates/trellis/scripts/task.py +12 -0
- package/dist/utils/codebase-retrieval-router.js +2 -2
- package/dist/utils/codebase-retrieval-router.js.map +1 -1
- package/dist/utils/cursor-retrieval-env.d.ts +2 -0
- package/dist/utils/cursor-retrieval-env.d.ts.map +1 -1
- package/dist/utils/cursor-retrieval-env.js +11 -2
- package/dist/utils/cursor-retrieval-env.js.map +1 -1
- package/dist/utils/mirror-check.d.ts +22 -0
- package/dist/utils/mirror-check.d.ts.map +1 -0
- package/dist/utils/mirror-check.js +90 -0
- package/dist/utils/mirror-check.js.map +1 -0
- package/dist/utils/normalize-text.d.ts +7 -0
- package/dist/utils/normalize-text.d.ts.map +1 -0
- package/dist/utils/normalize-text.js +16 -0
- package/dist/utils/normalize-text.js.map +1 -0
- package/dist/utils/retrieval-agent-instructions.d.ts.map +1 -1
- package/dist/utils/retrieval-agent-instructions.js +14 -7
- package/dist/utils/retrieval-agent-instructions.js.map +1 -1
- package/dist/utils/retrieval-execution-telemetry.d.ts +12 -3
- package/dist/utils/retrieval-execution-telemetry.d.ts.map +1 -1
- package/dist/utils/retrieval-execution-telemetry.js +37 -1
- package/dist/utils/retrieval-execution-telemetry.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 +13 -0
- package/dist/utils/retrieval-tool-classification.js.map +1 -1
- package/dist/utils/semantic-plan-gate.d.ts.map +1 -1
- package/dist/utils/semantic-plan-gate.js +11 -4
- package/dist/utils/semantic-plan-gate.js.map +1 -1
- package/dist/utils/validate-rules.d.ts +21 -0
- package/dist/utils/validate-rules.d.ts.map +1 -0
- package/dist/utils/validate-rules.js +88 -0
- package/dist/utils/validate-rules.js.map +1 -0
- package/package.json +4 -3
|
@@ -17,6 +17,7 @@ from trellis_task_models_config import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
MARKER = "/*TRELLIS_B2_WPeLc8*/"
|
|
20
|
+
INJECT_NEEDLE = "});return!UbAQWn||"
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _repo_root(start: Path) -> Path:
|
|
@@ -111,28 +112,43 @@ def resolve_model_layers(bundle_dir: Path, config: dict, providers_path: Path) -
|
|
|
111
112
|
return resolve_slots_to_slugs(merged, providers_path)
|
|
112
113
|
|
|
113
114
|
|
|
114
|
-
def
|
|
115
|
+
def configured_slot_map(bundle_dir: Path, config: dict) -> dict[str, list[str]]:
|
|
116
|
+
"""Non-secret role → [primary, fallback, …] from JSON5 only (no providers.json read)."""
|
|
117
|
+
user_path = resolve_user_models_path(config)
|
|
118
|
+
project_path = resolve_project_models_path(bundle_dir, config)
|
|
119
|
+
user_slots = extract_raw_slots(load_task_models_document(user_path))
|
|
120
|
+
project_slots = (
|
|
121
|
+
extract_raw_slots(load_task_models_document(project_path))
|
|
122
|
+
if project_path
|
|
123
|
+
else {}
|
|
124
|
+
)
|
|
125
|
+
return _merge_slot_maps(user_slots, project_slots)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _extension_candidates(config: dict) -> list[Path]:
|
|
115
129
|
for key in ("extensionJs", "extension_js"):
|
|
116
130
|
if isinstance(config.get(key), str) and config[key].strip():
|
|
117
|
-
return Path(config[key].strip()).expanduser()
|
|
131
|
+
return [Path(config[key].strip()).expanduser()]
|
|
118
132
|
env = os.environ.get("TRELLIS_CURSOR2PLUS_EXTENSION", "").strip()
|
|
119
133
|
if env:
|
|
120
|
-
return Path(env).expanduser()
|
|
134
|
+
return [Path(env).expanduser()]
|
|
121
135
|
if sys.platform == "win32":
|
|
122
136
|
local = os.environ.get("LOCALAPPDATA", "")
|
|
123
|
-
|
|
137
|
+
return [
|
|
124
138
|
Path(local) / "Programs" / "cursor" / "resources" / "app" / "extensions" / "cursor2plus" / "dist" / "extension.js",
|
|
125
139
|
Path(r"D:\cursor\resources\app\extensions\cursor2plus\dist\extension.js"),
|
|
126
140
|
]
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
if sys.platform == "darwin":
|
|
142
|
+
return [
|
|
129
143
|
Path("/Applications/Cursor.app/Contents/Resources/app/extensions/cursor2plus/dist/extension.js"),
|
|
130
144
|
]
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
return [
|
|
146
|
+
Path.home() / ".local" / "share" / "cursor" / "resources" / "app" / "extensions" / "cursor2plus" / "dist" / "extension.js",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def resolve_extension_js(bundle_dir: Path, config: dict) -> Path:
|
|
151
|
+
for c in _extension_candidates(config):
|
|
136
152
|
if c.is_file():
|
|
137
153
|
return c
|
|
138
154
|
raise SystemExit(
|
|
@@ -140,6 +156,13 @@ def resolve_extension_js(bundle_dir: Path, config: dict) -> Path:
|
|
|
140
156
|
)
|
|
141
157
|
|
|
142
158
|
|
|
159
|
+
def try_resolve_extension_js(bundle_dir: Path, config: dict) -> Path | None:
|
|
160
|
+
for c in _extension_candidates(config):
|
|
161
|
+
if c.is_file():
|
|
162
|
+
return c
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
143
166
|
def build_inject_block(model_map: dict[str, str]) -> str:
|
|
144
167
|
if not model_map:
|
|
145
168
|
raise SystemExit(
|
|
@@ -164,15 +187,27 @@ def find_wpelc8_body(source: str) -> tuple[int, int]:
|
|
|
164
187
|
return start, end
|
|
165
188
|
|
|
166
189
|
|
|
190
|
+
def probe_wpelc8_compat(source: str) -> dict[str, str]:
|
|
191
|
+
"""Non-exiting WPeLc8 / inject-anchor probe for --check-compat and smoke."""
|
|
192
|
+
start = source.find("function WPeLc8(")
|
|
193
|
+
if start < 0:
|
|
194
|
+
return {"wpelc8": "not_locatable", "inject_anchor": "unknown", "end_anchor": "unknown"}
|
|
195
|
+
end = source.find("async function*o4ZNpa", start)
|
|
196
|
+
if end < 0:
|
|
197
|
+
return {"wpelc8": "locatable", "inject_anchor": "unknown", "end_anchor": "missing"}
|
|
198
|
+
fn = source[start:end]
|
|
199
|
+
anchor = "present" if INJECT_NEEDLE in fn else "missing"
|
|
200
|
+
return {"wpelc8": "locatable", "inject_anchor": anchor, "end_anchor": "present"}
|
|
201
|
+
|
|
202
|
+
|
|
167
203
|
def apply_patch(source: str, inject: str) -> str:
|
|
168
204
|
start, end = find_wpelc8_body(source)
|
|
169
205
|
fn = source[start:end]
|
|
170
206
|
if MARKER in fn:
|
|
171
207
|
fn = remove_patch_from_fn(fn)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
fn_new = fn.replace(needle, f"}});{inject}return!UbAQWn||", 1)
|
|
208
|
+
if INJECT_NEEDLE not in fn:
|
|
209
|
+
raise SystemExit(f"inject anchor missing: {INJECT_NEEDLE!r}")
|
|
210
|
+
fn_new = fn.replace(INJECT_NEEDLE, f"}});{inject}return!UbAQWn||", 1)
|
|
176
211
|
return source[:start] + fn_new + source[end:]
|
|
177
212
|
|
|
178
213
|
|
|
@@ -240,14 +275,80 @@ def cmd_bootstrap(bundle_dir: Path) -> None:
|
|
|
240
275
|
print("Next: in Cursor Agent, run skill trellis-cursor2plus-setup", file=sys.stderr)
|
|
241
276
|
|
|
242
277
|
|
|
278
|
+
def cmd_check_compat(bundle_dir: Path, config: dict) -> int:
|
|
279
|
+
extension = try_resolve_extension_js(bundle_dir, config)
|
|
280
|
+
result: dict[str, object] = {
|
|
281
|
+
"extension": str(extension) if extension else None,
|
|
282
|
+
"patch_marker_present": False,
|
|
283
|
+
"wpelc8": "unknown",
|
|
284
|
+
"inject_anchor": "unknown",
|
|
285
|
+
"end_anchor": "unknown",
|
|
286
|
+
}
|
|
287
|
+
if extension is None:
|
|
288
|
+
result["status"] = "unknown"
|
|
289
|
+
result["hint"] = (
|
|
290
|
+
"extension.js not found — set extensionJs in config.local.json or run --bootstrap; "
|
|
291
|
+
"manual verification required"
|
|
292
|
+
)
|
|
293
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
294
|
+
return 2
|
|
295
|
+
|
|
296
|
+
source = extension.read_text(encoding="utf-8", errors="ignore")
|
|
297
|
+
result["patch_marker_present"] = MARKER in source
|
|
298
|
+
probe = probe_wpelc8_compat(source)
|
|
299
|
+
result.update(probe)
|
|
300
|
+
|
|
301
|
+
wpelc8 = probe["wpelc8"]
|
|
302
|
+
anchor = probe["inject_anchor"]
|
|
303
|
+
end_anchor = probe.get("end_anchor", "unknown")
|
|
304
|
+
|
|
305
|
+
if wpelc8 == "not_locatable" or anchor == "missing":
|
|
306
|
+
result["status"] = "fail"
|
|
307
|
+
result["hint"] = (
|
|
308
|
+
"WPeLc8 or inject anchor not found after Cursor/Cursor++ upgrade — "
|
|
309
|
+
"re-run patch with --apply --approve or revert to inherit-parent"
|
|
310
|
+
)
|
|
311
|
+
elif wpelc8 == "unknown" or anchor == "unknown" or end_anchor == "unknown":
|
|
312
|
+
result["status"] = "unknown"
|
|
313
|
+
result["hint"] = "Locator uncertain — manually verify extension.js after any upgrade"
|
|
314
|
+
else:
|
|
315
|
+
result["status"] = "ok"
|
|
316
|
+
result["hint"] = "WPeLc8 and inject anchor locatable"
|
|
317
|
+
|
|
318
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
319
|
+
return 0 if result["status"] == "ok" else (1 if result["status"] == "fail" else 2)
|
|
320
|
+
|
|
321
|
+
|
|
243
322
|
def main() -> None:
|
|
244
|
-
ap = argparse.ArgumentParser(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
323
|
+
ap = argparse.ArgumentParser(
|
|
324
|
+
description="Trellis Cursor++ WPeLc8 model map (Method 2.5)",
|
|
325
|
+
epilog=(
|
|
326
|
+
"Read-only: --print-map, --check-compat. "
|
|
327
|
+
"Write: --apply --approve (or --revert). "
|
|
328
|
+
"Never runs in CI by default."
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
ap.add_argument("--revert", action="store_true", help="Remove Trellis patch from extension.js")
|
|
332
|
+
ap.add_argument("--apply", action="store_true", help="Apply patch to extension.js")
|
|
333
|
+
ap.add_argument(
|
|
334
|
+
"--approve",
|
|
335
|
+
action="store_true",
|
|
336
|
+
help="Explicit operator approval (required for --apply writes)",
|
|
337
|
+
)
|
|
338
|
+
ap.add_argument(
|
|
339
|
+
"--dry-run",
|
|
340
|
+
action="store_true",
|
|
341
|
+
help="With --apply: validate patch without writing extension.js",
|
|
342
|
+
)
|
|
343
|
+
ap.add_argument("--bootstrap", action="store_true", help="Create config.local.json with extension path")
|
|
344
|
+
ap.add_argument("--explain", action="store_true", help="Explain trellis-task-models.json5 keys")
|
|
345
|
+
ap.add_argument("--list-models", action="store_true", help="List models from providers.json catalog")
|
|
346
|
+
ap.add_argument("--print-map", action="store_true", help="Dry-run: print resolved slug map (no file writes)")
|
|
347
|
+
ap.add_argument(
|
|
348
|
+
"--check-compat",
|
|
349
|
+
action="store_true",
|
|
350
|
+
help="Check WPeLc8 locatability in current extension.js (no file writes)",
|
|
351
|
+
)
|
|
251
352
|
args = ap.parse_args()
|
|
252
353
|
|
|
253
354
|
bundle_dir = Path(__file__).resolve().parent
|
|
@@ -259,39 +360,62 @@ def main() -> None:
|
|
|
259
360
|
return
|
|
260
361
|
|
|
261
362
|
config = _load_config(bundle_dir)
|
|
363
|
+
|
|
364
|
+
if args.check_compat:
|
|
365
|
+
raise SystemExit(cmd_check_compat(bundle_dir, config))
|
|
366
|
+
|
|
262
367
|
providers_path = resolve_providers_json(config)
|
|
263
368
|
|
|
264
369
|
if args.list_models:
|
|
265
370
|
cmd_list_models(providers_path)
|
|
266
371
|
return
|
|
267
372
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
373
|
+
needs_map = args.print_map or args.apply or args.revert
|
|
374
|
+
model_map: dict[str, str] = {}
|
|
375
|
+
notes: list[str] = []
|
|
376
|
+
if needs_map and not args.revert:
|
|
377
|
+
model_map, notes = resolve_model_layers(bundle_dir, config, providers_path)
|
|
378
|
+
if notes:
|
|
379
|
+
print("resolve:", file=sys.stderr)
|
|
380
|
+
for line in notes:
|
|
381
|
+
print(line, file=sys.stderr)
|
|
382
|
+
if any("ERROR" in line for line in notes):
|
|
383
|
+
raise SystemExit(1)
|
|
275
384
|
|
|
276
385
|
if args.print_map:
|
|
277
386
|
print(json.dumps(model_map, indent=2, sort_keys=True))
|
|
278
387
|
return
|
|
279
388
|
|
|
280
|
-
extension = resolve_extension_js(bundle_dir, config)
|
|
281
|
-
source = extension.read_text(encoding="utf-8", errors="ignore")
|
|
282
389
|
if args.revert:
|
|
390
|
+
extension = resolve_extension_js(bundle_dir, config)
|
|
391
|
+
source = extension.read_text(encoding="utf-8", errors="ignore")
|
|
283
392
|
new = remove_patch(source)
|
|
284
|
-
|
|
393
|
+
extension.write_text(new, encoding="utf-8", newline="\n")
|
|
394
|
+
print("wrote", extension)
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
if args.apply:
|
|
398
|
+
if not args.approve and not args.dry_run:
|
|
399
|
+
print(
|
|
400
|
+
"refused: --apply writes extension.js and requires explicit --approve "
|
|
401
|
+
"(use --dry-run to validate without writing)",
|
|
402
|
+
file=sys.stderr,
|
|
403
|
+
)
|
|
404
|
+
raise SystemExit(1)
|
|
405
|
+
extension = resolve_extension_js(bundle_dir, config)
|
|
406
|
+
source = extension.read_text(encoding="utf-8", errors="ignore")
|
|
285
407
|
inject = build_inject_block(model_map)
|
|
286
408
|
new = apply_patch(source, inject)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
409
|
+
if args.dry_run:
|
|
410
|
+
print("ok patch delta", len(new) - len(source))
|
|
411
|
+
return
|
|
412
|
+
extension.write_text(new, encoding="utf-8", newline="\n")
|
|
413
|
+
print("wrote", extension)
|
|
290
414
|
return
|
|
291
415
|
|
|
292
|
-
|
|
293
|
-
|
|
416
|
+
ap.print_help()
|
|
417
|
+
raise SystemExit(2)
|
|
294
418
|
|
|
295
419
|
|
|
296
420
|
if __name__ == "__main__":
|
|
297
|
-
main()
|
|
421
|
+
main()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cursor++ local bundle smoke check — whitelist metadata only, no secret reads."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from patch_wpelc8 import (
|
|
11
|
+
MARKER,
|
|
12
|
+
_load_config,
|
|
13
|
+
configured_slot_map,
|
|
14
|
+
probe_wpelc8_compat,
|
|
15
|
+
try_resolve_extension_js,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
CAVEATS = [
|
|
19
|
+
"Cursor++ is third-party; reverse-engineering is version-bound",
|
|
20
|
+
"Never reads BYOK provider catalog file contents",
|
|
21
|
+
"Patch is operator-only; not part of CI or default Trellis workflow",
|
|
22
|
+
"Re-verify after any Cursor or Cursor++ upgrade",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def detect_env() -> tuple[str, str | None]:
|
|
27
|
+
env_flag = os.environ.get("TRELLIS_CURSOR_BYOK", "").strip()
|
|
28
|
+
if env_flag == "1":
|
|
29
|
+
return "byok", "TRELLIS_CURSOR_BYOK=1"
|
|
30
|
+
if env_flag == "0":
|
|
31
|
+
return "native", "TRELLIS_CURSOR_BYOK=0"
|
|
32
|
+
|
|
33
|
+
routes = Path.home() / ".ccursor" / "routes.json"
|
|
34
|
+
if routes.is_file():
|
|
35
|
+
try:
|
|
36
|
+
data = json.loads(routes.read_text(encoding="utf-8"))
|
|
37
|
+
byok = data.get("byokMode")
|
|
38
|
+
if byok in (1, "1", True):
|
|
39
|
+
return "byok", "routes.json:byokMode"
|
|
40
|
+
if byok in (0, "0", False):
|
|
41
|
+
return "native", "routes.json:byokMode"
|
|
42
|
+
except (OSError, json.JSONDecodeError, TypeError):
|
|
43
|
+
pass
|
|
44
|
+
return "unknown", None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def detect_patch_status(bundle_dir: Path, config: dict) -> tuple[str, dict[str, object]]:
|
|
48
|
+
extension = try_resolve_extension_js(bundle_dir, config)
|
|
49
|
+
meta: dict[str, object] = {"extension": str(extension) if extension else None}
|
|
50
|
+
if extension is None:
|
|
51
|
+
return "not-applied", meta
|
|
52
|
+
|
|
53
|
+
source = extension.read_text(encoding="utf-8", errors="ignore")
|
|
54
|
+
marker_present = MARKER in source
|
|
55
|
+
meta["patch_marker_present"] = marker_present
|
|
56
|
+
probe = probe_wpelc8_compat(source)
|
|
57
|
+
meta.update(probe)
|
|
58
|
+
|
|
59
|
+
if not marker_present:
|
|
60
|
+
return "not-applied", meta
|
|
61
|
+
|
|
62
|
+
if probe["wpelc8"] == "not_locatable" or probe["inject_anchor"] == "missing":
|
|
63
|
+
return "stale", meta
|
|
64
|
+
if probe["wpelc8"] == "unknown" or probe["inject_anchor"] == "unknown":
|
|
65
|
+
return "stale", meta
|
|
66
|
+
return "applied", meta
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main() -> int:
|
|
70
|
+
bundle_dir = Path(__file__).resolve().parent
|
|
71
|
+
config = _load_config(bundle_dir)
|
|
72
|
+
env, env_source = detect_env()
|
|
73
|
+
patch_status, patch_meta = detect_patch_status(bundle_dir, config)
|
|
74
|
+
slots = configured_slot_map(bundle_dir, config)
|
|
75
|
+
|
|
76
|
+
report = {
|
|
77
|
+
"env": env,
|
|
78
|
+
"env_source": env_source,
|
|
79
|
+
"patch_status": patch_status,
|
|
80
|
+
"patch_meta": patch_meta,
|
|
81
|
+
"current_map": {
|
|
82
|
+
"configured_roles": {
|
|
83
|
+
role: {"candidates": names} for role, names in sorted(slots.items())
|
|
84
|
+
},
|
|
85
|
+
"note": "Role names from user JSON5 config only; slugs not resolved (no provider catalog read)",
|
|
86
|
+
},
|
|
87
|
+
"caveats": CAVEATS,
|
|
88
|
+
}
|
|
89
|
+
print(json.dumps(report, indent=2, sort_keys=True))
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
raise SystemExit(main())
|
|
@@ -14,8 +14,8 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
from .cursor_retrieval_env import (
|
|
17
|
-
ENV_BYOK,
|
|
18
17
|
detect_cursor_retrieval_env,
|
|
18
|
+
is_byok_conservative,
|
|
19
19
|
semantic_route_spec,
|
|
20
20
|
)
|
|
21
21
|
|
|
@@ -716,7 +716,7 @@ def _fallback_hints(
|
|
|
716
716
|
"replacesRole": "semantic",
|
|
717
717
|
})
|
|
718
718
|
cenv = cursor_env or detect_cursor_retrieval_env()
|
|
719
|
-
if cenv
|
|
719
|
+
if is_byok_conservative(cenv):
|
|
720
720
|
hints.append({
|
|
721
721
|
"when": "built-in @codebase / SemanticSearch not in agent tool list",
|
|
722
722
|
"action": (
|
|
@@ -75,12 +75,26 @@ def is_byok(env: CursorRetrievalEnv | None = None) -> bool:
|
|
|
75
75
|
return (env or detect_cursor_retrieval_env()) == ENV_BYOK
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def is_byok_conservative(env: CursorRetrievalEnv | None = None) -> bool:
|
|
79
|
+
"""Conservative semantic routing: BYOK plus unknown (no routes.json / ambiguous byokMode)."""
|
|
80
|
+
resolved = env or detect_cursor_retrieval_env()
|
|
81
|
+
return resolved == ENV_BYOK or resolved == ENV_UNKNOWN
|
|
82
|
+
|
|
83
|
+
|
|
78
84
|
def semantic_route_spec(cursor_env: CursorRetrievalEnv) -> dict[str, object]:
|
|
79
85
|
"""platform-semantic route fields for conceptual / optional semantic slots."""
|
|
80
|
-
if cursor_env
|
|
86
|
+
if is_byok_conservative(cursor_env):
|
|
87
|
+
unknown_caveat = (
|
|
88
|
+
" Unknown cursorEnv: conservative fast-context primary (same as BYOK)."
|
|
89
|
+
if cursor_env == ENV_UNKNOWN
|
|
90
|
+
else ""
|
|
91
|
+
)
|
|
81
92
|
return {
|
|
82
93
|
"commands": ["fast_context_search (fast-context MCP)"],
|
|
83
|
-
"rationale_suffix":
|
|
94
|
+
"rationale_suffix": (
|
|
95
|
+
" Cursor++ BYOK: built-in semantic unavailable; fast-context MCP primary."
|
|
96
|
+
+ unknown_caveat
|
|
97
|
+
),
|
|
84
98
|
"platformNative": False,
|
|
85
99
|
"semanticBackend": "fast-context-mcp",
|
|
86
100
|
}
|