@blxzer/cursor-trellis 0.3.4 → 0.3.5

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 (95) hide show
  1. package/README.md +0 -13
  2. package/README.zh-CN.md +1 -1
  3. package/bin/cstl.js +1 -1
  4. package/bin/smart-search.js +1 -1
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +14 -2
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/commands/campaign/canvas-render.d.ts +68 -0
  9. package/dist/commands/campaign/canvas-render.d.ts.map +1 -0
  10. package/dist/commands/campaign/canvas-render.js +414 -0
  11. package/dist/commands/campaign/canvas-render.js.map +1 -0
  12. package/dist/commands/campaign/compose.d.ts +15 -0
  13. package/dist/commands/campaign/compose.d.ts.map +1 -0
  14. package/dist/commands/campaign/compose.js +110 -0
  15. package/dist/commands/campaign/compose.js.map +1 -0
  16. package/dist/commands/campaign/index.d.ts +13 -0
  17. package/dist/commands/campaign/index.d.ts.map +1 -0
  18. package/dist/commands/campaign/index.js +134 -0
  19. package/dist/commands/campaign/index.js.map +1 -0
  20. package/dist/commands/campaign/kind-map.d.ts +6 -0
  21. package/dist/commands/campaign/kind-map.d.ts.map +1 -0
  22. package/dist/commands/campaign/kind-map.js +21 -0
  23. package/dist/commands/campaign/kind-map.js.map +1 -0
  24. package/dist/commands/campaign/mcp-server.d.ts +6 -0
  25. package/dist/commands/campaign/mcp-server.d.ts.map +1 -0
  26. package/dist/commands/campaign/mcp-server.js +167 -0
  27. package/dist/commands/campaign/mcp-server.js.map +1 -0
  28. package/dist/commands/campaign/render.d.ts +5 -0
  29. package/dist/commands/campaign/render.d.ts.map +1 -0
  30. package/dist/commands/campaign/render.js +103 -0
  31. package/dist/commands/campaign/render.js.map +1 -0
  32. package/dist/commands/campaign/trellis-load.d.ts +13 -0
  33. package/dist/commands/campaign/trellis-load.d.ts.map +1 -0
  34. package/dist/commands/campaign/trellis-load.js +122 -0
  35. package/dist/commands/campaign/trellis-load.js.map +1 -0
  36. package/dist/commands/campaign/types.d.ts +61 -0
  37. package/dist/commands/campaign/types.d.ts.map +1 -0
  38. package/dist/commands/campaign/types.js +3 -0
  39. package/dist/commands/campaign/types.js.map +1 -0
  40. package/dist/commands/init.d.ts.map +1 -1
  41. package/dist/commands/init.js +11 -1
  42. package/dist/commands/init.js.map +1 -1
  43. package/dist/commands/rpc/client.d.ts +51 -0
  44. package/dist/commands/rpc/client.d.ts.map +1 -0
  45. package/dist/commands/rpc/client.js +147 -0
  46. package/dist/commands/rpc/client.js.map +1 -0
  47. package/dist/commands/rpc/index.d.ts +10 -0
  48. package/dist/commands/rpc/index.d.ts.map +1 -0
  49. package/dist/commands/rpc/index.js +157 -0
  50. package/dist/commands/rpc/index.js.map +1 -0
  51. package/dist/commands/rpc/serve.d.ts +23 -0
  52. package/dist/commands/rpc/serve.d.ts.map +1 -0
  53. package/dist/commands/rpc/serve.js +60 -0
  54. package/dist/commands/rpc/serve.js.map +1 -0
  55. package/dist/commands/rpc/smoke.d.ts +16 -0
  56. package/dist/commands/rpc/smoke.d.ts.map +1 -0
  57. package/dist/commands/rpc/smoke.js +92 -0
  58. package/dist/commands/rpc/smoke.js.map +1 -0
  59. package/dist/commands/sdk/index.d.ts +9 -0
  60. package/dist/commands/sdk/index.d.ts.map +1 -0
  61. package/dist/commands/sdk/index.js +58 -0
  62. package/dist/commands/sdk/index.js.map +1 -0
  63. package/dist/commands/sdk/run.d.ts +42 -0
  64. package/dist/commands/sdk/run.d.ts.map +1 -0
  65. package/dist/commands/sdk/run.js +245 -0
  66. package/dist/commands/sdk/run.js.map +1 -0
  67. package/dist/commands/sdk/status.d.ts +14 -0
  68. package/dist/commands/sdk/status.d.ts.map +1 -0
  69. package/dist/commands/sdk/status.js +69 -0
  70. package/dist/commands/sdk/status.js.map +1 -0
  71. package/dist/templates/common/bundled-skills/smart-search-cli/SKILL.md +35 -29
  72. package/dist/templates/common/bundled-skills/smart-search-cli/references/cli-contract.md +11 -25
  73. package/dist/templates/cursor/rules/retrieval-routing.mdc +1 -1
  74. package/dist/templates/markdown/agents.md +2 -0
  75. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +14 -0
  76. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +14 -0
  77. package/dist/templates/trellis/scripts/common/parent_orchestration.py +341 -2
  78. package/dist/templates/trellis/scripts/common/task_map.py +638 -614
  79. package/dist/templates/trellis/scripts/common/task_store.py +47 -1
  80. package/dist/templates/trellis/scripts/task.py +32 -0
  81. package/dist/utils/codebase-retrieval-router.d.ts.map +1 -1
  82. package/dist/utils/codebase-retrieval-router.js +14 -0
  83. package/dist/utils/codebase-retrieval-router.js.map +1 -1
  84. package/dist/utils/cursor-sdk-gate.d.ts +10 -0
  85. package/dist/utils/cursor-sdk-gate.d.ts.map +1 -0
  86. package/dist/utils/cursor-sdk-gate.js +32 -0
  87. package/dist/utils/cursor-sdk-gate.js.map +1 -0
  88. package/dist/utils/project-capabilities.d.ts +30 -3
  89. package/dist/utils/project-capabilities.d.ts.map +1 -1
  90. package/dist/utils/project-capabilities.js +182 -11
  91. package/dist/utils/project-capabilities.js.map +1 -1
  92. package/dist/utils/readiness.d.ts.map +1 -1
  93. package/dist/utils/readiness.js +26 -0
  94. package/dist/utils/readiness.js.map +1 -1
  95. package/package.json +4 -3
@@ -21,14 +21,13 @@
21
21
  - `smart-search fetch URL [--format json|markdown|content] [--output PATH]`
22
22
  - `smart-search exa-search QUERY [--num-results N] [--search-type neural|keyword|auto] [--include-text] [--include-highlights] [--start-published-date YYYY-MM-DD] [--include-domains DOMAIN...] [--exclude-domains DOMAIN...] [--category NAME] [--format json|markdown|content] [--output PATH]`
23
23
  - `smart-search exa-similar URL [--num-results N] [--format json|markdown|content] [--output PATH]`
24
- - `smart-search zhipu-search QUERY [--count N] [--search-engine NAME] [--search-recency-filter VALUE] [--search-domain-filter DOMAIN] [--content-size medium|high] [--format json|markdown|content] [--output PATH]` — **DEPRECATED**: emits a stderr warning on every invocation; the subcommand, the `research_discovery` zhipu branch, and `providers/zhipu.py` will be removed on the schedule in README § "Deprecation notices".
25
24
  - `smart-search context7-library NAME [QUERY] [--format json|markdown|content] [--output PATH]`
26
25
  - `smart-search context7-docs LIBRARY_ID QUERY [--format json|markdown|content] [--output PATH]`
27
26
  - `smart-search research QUERY [--budget quick|standard|deep] [--locale-scope cn|en|both] [--evidence-dir PATH] [--fallback auto|off] [--dry-run] [--progress] [--format json|markdown|content] [--output PATH]`
28
27
  - `smart-search map URL [--instructions TEXT] [--max-depth N] [--max-breadth N] [--limit N] [--timeout SECONDS] [--format json|markdown|content] [--output PATH]`
29
28
  - `smart-search doctor [--format json|markdown|content] [--output PATH]`
30
29
  - `smart-search diagnose openai-compatible [--timeout SECONDS] [--format json|markdown] [--output PATH]`
31
- - `smart-search setup [--lang zh|en] [--advanced] [--non-interactive] [--openai-compatible-api-url URL] [--openai-compatible-api-key KEY] [--openai-compatible-model ID] [--openai-compatible-stream true|false] [--validation-level fast|balanced|strict] [--fallback-mode auto|off] [--minimum-profile standard|off] [--exa-key KEY] [--context7-key KEY] [--zhipu-key KEY] [--zhipu-api-url URL] [--zhipu-search-engine ENGINE] [--jina-key KEY] [--jina-reader-api-url URL] [--jina-respond-with MODE] [--jina-timeout SECONDS] [--tavily-api-url URL] [--tavily-key KEY] [--firecrawl-api-url URL] [--firecrawl-key KEY] [--format json|markdown|content] [--output PATH]`
30
+ - `smart-search setup [--lang zh|en] [--advanced] [--non-interactive] [--openai-compatible-api-url URL] [--openai-compatible-api-key KEY] [--openai-compatible-model ID] [--openai-compatible-stream true|false] [--validation-level fast|balanced|strict] [--fallback-mode auto|off] [--minimum-profile standard|off] [--exa-key KEY] [--context7-key KEY] [--jina-key KEY] [--jina-reader-api-url URL] [--jina-respond-with MODE] [--jina-timeout SECONDS] [--tavily-api-url URL] [--tavily-key KEY] [--firecrawl-api-url URL] [--firecrawl-key KEY] [--format json|markdown|content] [--output PATH]`
32
31
  - `smart-search config path [--format json|markdown|content] [--output PATH]`
33
32
  - `smart-search config list [--format json|markdown|content] [--output PATH]`
34
33
  - `smart-search config set KEY VALUE [--format json|markdown|content] [--output PATH]`
@@ -47,7 +46,6 @@ Top-level aliases must normalize to the same service behavior as their full comm
47
46
  | `map` | `m` |
48
47
  | `exa-search` | `exa`, `x` |
49
48
  | `exa-similar` | `xs` |
50
- | `zhipu-search` | `z`, `zp` |
51
49
  | `context7-library` | `c7`, `ctx7` |
52
50
  | `context7-docs` | `c7d`, `c7docs`, `ctx7-docs` |
53
51
  | `research` | `rs` |
@@ -71,7 +69,7 @@ Successful search output includes `ok`, `query`, `primary_api_mode`, `content`,
71
69
 
72
70
  `--format json` is the stable machine-readable contract for agents and scripts. JSON output remains parseable and uses readable non-ASCII text when the terminal encoding supports it.
73
71
 
74
- `--format markdown` is the human-readable report format. `doctor --format markdown` must render a detailed diagnostic report with overall status, active/default/legacy config paths, log path resolution, evidence path resolution, file-logging status, masked config values with sources, minimum profile, capability status, main-search provider checks, provider connectivity checks, model metadata, and full long error/message detail instead of falling back to raw JSON. `diagnose openai-compatible --format markdown` must render a short copy-pasteable troubleshooting report with masked config, quick chat check, real search-shape `stream=false` and `stream=true` checks, a plain-language summary, and a next command. Provider list commands such as `exa-search`, `exa-similar`, `zhipu-search`, `context7-library`, and `map` render result lists or a clear no-results message.
72
+ `--format markdown` is the human-readable report format. `doctor --format markdown` must render a detailed diagnostic report with overall status, active/default/legacy config paths, log path resolution, evidence path resolution, file-logging status, masked config values with sources, minimum profile, capability status, main-search provider checks, provider connectivity checks, model metadata, and full long error/message detail instead of falling back to raw JSON. `diagnose openai-compatible --format markdown` must render a short copy-pasteable troubleshooting report with masked config, quick chat check, real search-shape `stream=false` and `stream=true` checks, a plain-language summary, and a next command. Provider list commands such as `exa-search`, `exa-similar`, `context7-library`, and `map` render result lists or a clear no-results message.
75
73
 
76
74
  `--format content` prints only the `content` field for content-bearing commands such as `search`, `fetch`, `context7-docs`, and `research`. Commands without a `content` field, including `doctor` and `config`, must print a compact non-empty text summary rather than an empty stdout.
77
75
 
@@ -90,17 +88,9 @@ Exa domain filters:
90
88
 
91
89
  Fetch output includes `ok`, `url`, `provider`, `content`, `provider_attempts`, `fallback_used`, and `elapsed_ms`.
92
90
 
93
- Zhipu Web Search API legacy setup:
94
-
95
- - `ZHIPU_API_URL` defaults to `https://open.bigmodel.cn/api`.
96
- - `ZHIPU_SEARCH_ENGINE` defaults to `search_std`.
97
- - Official Web Search API service values include `search_std`, `search_pro`, `search_pro_sogou`, and `search_pro_quark`.
98
- - `smart-search setup --zhipu-api-url URL --zhipu-search-engine ENGINE` saves these values in non-interactive mode.
99
- - Interactive setup no longer recommends or prompts for Zhipu in the default flow. Use `config set` or non-interactive flags only for explicit manual legacy compatibility.
100
- - `config set ZHIPU_SEARCH_ENGINE VALUE` must remain free-form so newly added official services do not require a CLI release.
101
- - `zhipu-search` corresponds to Zhipu Web Search API, not Zhipu Chat Completions `tools=[web_search]`, not Search Agent, and not the MCP Server.
102
- - `zhipu-search` is deprecated and not used by default routing because quota may be unavailable. Default source discovery uses bilingual `search` through Tavily / Firecrawl when configured.
103
- - `TAVILY_API_URL` only affects Tavily and does not proxy Zhipu.
91
+ Tavily setup notes:
92
+
93
+ - `TAVILY_API_URL` only affects Tavily.
104
94
  - `TAVILY_TIMEOUT_SECONDS` controls the Tavily `doctor` connectivity timeout. It defaults to `60` so slower pooled/community endpoints are not incorrectly marked unhealthy by the diagnostic check.
105
95
 
106
96
  Jina Reader setup:
@@ -124,15 +114,13 @@ Exa HTTP `400` or `422` failures are returned as `ok=false` with `error_type=par
124
114
 
125
115
  Exa similar output includes `ok`, `url`, `results`, `total`, and `elapsed_ms` when successful.
126
116
 
127
- Zhipu search output includes `ok`, `query`, `provider`, `search_engine`, `results`, `total`, and `elapsed_ms` when successful.
128
-
129
117
  Context7 library output includes `ok`, `query`, `provider`, `results`, `total`, and `elapsed_ms` when successful. Context7 docs output includes `ok`, `library_id`, `query`, `provider`, `results`, `total`, `content`, and `elapsed_ms` when successful.
130
118
 
131
119
  Map output includes `ok`, `base_url`, `results`, `response_time`, `url`, and `elapsed_ms` when successful.
132
120
 
133
121
  Research executor output includes `ok`, `mode=deep_research_execution`, `query_mode=research`, `question`, `budget`, `research_plan`, `routing_decision`, `stage_results`, `discovery_sources`, `final_answer`, `content`, `citations`, `evidence_items`, `gap_check`, `provider_attempts`, `providers_used`, `fallback_used`, `degraded`, `route_policy_version`, `evidence_dir`, `minimum_profile_ok`, `capability_status`, and `elapsed_ms`. The embedded `research_plan` carries `intent_signals`, `decomposition`, `capability_plan`, `evidence_policy`, `steps`, and `gap_check`. Citations must come only from fetched/read `evidence_items`; discovery sources are candidates until fetched. If evidence cannot close, `research` returns degraded gaps instead of unsupported claims.
134
122
 
135
- Diagnostic output masks keys, reports `config_file` / `config_dir` / `config_dir_source` / `default_config_file` / Windows legacy config metadata / `config_dir_override_value` / `config_dir_override_matches_default` / `log_dir_config_value` / `resolved_log_dir` / `evidence_dir_config_value` / `resolved_evidence_dir` / `file_logging_enabled` / `config_sources` / `primary_api_mode` / `primary_api_mode_source` / provider timeout values / `capability_status` / `minimum_profile_ok`, and includes `main_search_connection_tests` plus connection test objects for Exa, Tavily, Zhipu, Context7, and Firecrawl. `primary_connection_test` remains as a backward-compatible alias for the first configured main provider check. OpenAI-compatible provider health must be validated through `/chat/completions`; `/models` is supplementary metadata and must not be the health gate. Firecrawl currently reports whether `FIRECRAWL_API_KEY` is configured; it is not a live Firecrawl request.
123
+ Diagnostic output masks keys, reports `config_file` / `config_dir` / `config_dir_source` / `default_config_file` / Windows legacy config metadata / `config_dir_override_value` / `config_dir_override_matches_default` / `log_dir_config_value` / `resolved_log_dir` / `evidence_dir_config_value` / `resolved_evidence_dir` / `file_logging_enabled` / `config_sources` / `primary_api_mode` / `primary_api_mode_source` / provider timeout values / `capability_status` / `minimum_profile_ok`, and includes `main_search_connection_tests` plus connection test objects for Exa, Tavily, Context7, and Firecrawl. `primary_connection_test` remains as a backward-compatible alias for the first configured main provider check. OpenAI-compatible provider health must be validated through `/chat/completions`; `/models` is supplementary metadata and must not be the health gate. xAI Responses health is validated through `/responses` (a lightweight probe in `doctor`; `diagnose xai` adds a search-shape probe with server-side tools). Firecrawl currently reports whether `FIRECRAWL_API_KEY` is configured; it is not a live Firecrawl request.
136
124
 
137
125
  When a Windows user reports that different versions seem to use different config paths, diagnose in this order: `config_dir_source`, `config_dir_override_value`, `config_dir_override_matches_default`, then `legacy_windows_config_exists`. A source of `environment` with `config_dir_override_matches_default=true` means the active path is pinned by `SMART_SEARCH_CONFIG_DIR` but is functionally the same as the current default. Do not delete either config file or the user-level override until the upgraded CLI has been verified with `config path` and `doctor` checks.
138
126
 
@@ -163,7 +151,6 @@ Each `steps[]` item must include `id`, `subquestion_id`, `tool`, `purpose`, `com
163
151
  Capability boundaries:
164
152
 
165
153
  - `search`: broad bilingual discovery and synthesis through `main_search`; use returned `routing_decision`, `provider_attempts`, `fallback_used`, and `source_warning` as orchestration signals, not as claim proof.
166
- - `zhipu-search`: deprecated manual compatibility command. Do not include it in default research plans.
167
154
  - `context7-library` and `context7-docs`: library, SDK, API, framework, and documentation intent. Prefer Context7 before Exa for docs/API questions.
168
155
  - `exa-search`: low-noise source discovery for official domains, papers, product pages, known domains, and trusted pages. It is not the default second hop for every high-risk or verification task.
169
156
  - `exa-similar`: adjacent-source discovery when a known reliable URL is available.
@@ -258,15 +245,15 @@ Agent timeout handling contract:
258
245
 
259
246
  ## Provider Routing
260
247
 
261
- - `search` builds `main_search` from `OPENAI_COMPATIBLE_API_URL` + `OPENAI_COMPATIBLE_API_KEY`, which registers OpenAI-compatible Chat Completions.
248
+ - `search` builds `main_search` from `XAI_API_KEY` (xAI Responses with server-side `web_search`/`x_search` tools) and/or `OPENAI_COMPATIBLE_API_URL` + `OPENAI_COMPATIBLE_API_KEY` (Chat Completions). One is enough; when both are configured, `SMART_SEARCH_MAIN_SEARCH_ROUTE` (ordered CSV of `xai-responses,openai-compatible`) sets priority, and a single entry disables cross-route fallback.
262
249
  - OpenAI-compatible relays/gateways use Chat Completions `/chat/completions` through `OPENAI_COMPATIBLE_*`.
263
250
  - `OPENAI_COMPATIBLE_STREAM` and `search --stream/--no-stream` affect only the OpenAI-compatible Chat Completions transport for search/fetch. They do not change provider-internal ranking/URL description tasks.
264
251
  - Legacy `SMART_SEARCH_API_URL`, `SMART_SEARCH_API_KEY`, `SMART_SEARCH_API_MODE`, and `SMART_SEARCH_MODEL` are unsupported config keys. `config set` / `config unset` must return a parameter error for them.
265
252
  - Standard minimum profile requires `main_search`, `docs_search`, and fetch capability. Missing required capabilities produce a configuration error.
266
253
  - Jina satisfies fetch capability only when `JINA_API_KEY` is configured. Anonymous Jina Reader does not satisfy `standard`.
267
254
  - Same-capability fallback is allowed; cross-capability fallback is not. Context7 is not used for unrelated broad web queries, and page extraction providers are not used as docs search providers.
268
- - `main_search`: OpenAI-compatible Chat Completions.
269
- - `web_search`: `search` runs bilingual web_search source discovery through Tavily / Firecrawl when configured. Zhipu is deprecated from default routing and is not selected automatically for Chinese/current/domestic searches.
255
+ - `main_search`: xAI Responses (`XAI_*`) or OpenAI-compatible Chat Completions (`OPENAI_COMPATIBLE_*`), ordered by `SMART_SEARCH_MAIN_SEARCH_ROUTE` when both are configured (default `xai-responses -> openai-compatible`).
256
+ - `web_search`: `search` runs bilingual web_search source discovery through Tavily / Firecrawl when configured.
270
257
  - `docs_search`: explicit keyword-based docs/API/library/framework intent. Context7 is first for library/API/docs intent, then Exa for official-domain, paper, product-page, trusted-site, or low-noise supplemental discovery.
271
258
  - Fetch capability: Tavily first, then Jina Reader with `JINA_API_KEY`, then Firecrawl.
272
259
  - `search --validation strict` uses the same bilingual web_search policy as balanced mode when source discovery providers are configured. Strict queries without primary, docs, fetch, or explicit source evidence can still fail with `evidence_error`; use `--extra-sources N`, source-first commands such as `exa-search`, or `fetch` when citable evidence is required.
@@ -278,17 +265,16 @@ Agent timeout handling contract:
278
265
  - `research` uses capability-first plus provider-advantage routing. Fallback remains same-capability only; low-quality fetches, challenge pages, empty content, auth/rate/timeout/provider errors, and runtime errors are failed attempts that may trigger same-capability fallback.
279
266
  - `map` uses Tavily only.
280
267
  - `exa-search` and `exa-similar` use Exa only.
281
- - `zhipu-search` uses Zhipu only and is retained as a deprecated manual compatibility command.
282
268
  - `context7-library` and `context7-docs` use Context7 only.
283
269
  - Runtime config priority is environment variables first, then local config file, then defaults.
284
270
  - `setup` and `config` read/write the local Smart Search config file and do not call providers.
285
- - Use `config set OPENAI_COMPATIBLE_MODEL ...` to change the main-search model.
271
+ - Use `config set OPENAI_COMPATIBLE_MODEL ...` (or `XAI_MODEL ...` for the xAI route) to change the main-search model; use `config set SMART_SEARCH_MAIN_SEARCH_ROUTE ...` to change route priority.
286
272
 
287
273
  ## Routing Heuristics
288
274
 
289
275
  - Use `exa-search --include-domains` when official documentation domains are known.
290
276
  - Use `context7-library` / `context7-docs` for explicit docs/API/SDK/library/framework intent when Context7 is configured.
291
- - Use the bilingual `search` pair for Chinese, domestic, current, or mixed-language source discovery. Do not use Zhipu unless the user explicitly asks for the deprecated manual route.
277
+ - Use the bilingual `search` pair for Chinese, domestic, current, or mixed-language source discovery.
292
278
  - Use `exa-search --start-published-date` for recency-constrained source discovery.
293
279
  - Use `exa-similar` when a known good page is available and adjacent sources are needed.
294
280
  - Use `search --format content` when a human wants only the generated answer body.
@@ -56,4 +56,4 @@ Use codegraph for **call chains**, **cross-package trap disambiguation**, **exte
56
56
 
57
57
  ## External facts
58
58
 
59
- Web/current events: **smart-search** first per `.cstl/spec/guides/retrieval-daily-guide.md`.
59
+ Web/current events: **smart-search** first per `.cstl/spec/guides/retrieval-daily-guide.md` (see **External-knowledge gate** for search-or-not).
@@ -23,6 +23,8 @@ Only a handful of Trellis entry points are meant for **manual `/` invocation**.
23
23
 
24
24
  For **any external / current / web fact**, run **`python ./.cstl/scripts/run_smart_search.py "<question>" --intent deep-research --json`** first. That script is the **only** Trellis web-research evidence entrypoint (it shells out to the `smart-search` CLI). Do not guess paths under package source trees or sibling repos. Platform built-in web tools (Cursor `WebSearch` / `WebFetch`, or native web tools elsewhere) are **downgrade-only fallbacks**, used solely when smart-search is unavailable (`doctor` not ok, status `not_configured` / `failed`, or search timeout). Do not reach for built-in web search while smart-search is healthy. On Cursor, `smart-search-cli` is an **internal workflow skill name** only (not shipped under `.cursor/skills/`); follow `.cstl/spec/guides/retrieval-daily-guide.md` and `.cursor/rules/retrieval-routing.mdc` for the executable contract.
25
25
 
26
+ **External-knowledge gate:** If the answer would be wrong because the **world or a third-party API moved** and that matters → use smart-search (cheap `docs` / `broad-search` when enough; `deep-research` when multi-source). If truth lives only in this workspace → do not default to web. When unsure, prefer a cheap probe over guessing. See retrieval-daily-guide § External-knowledge gate.
27
+
26
28
  Managed by cursor-trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `cstl update`.
27
29
 
28
30
  <!-- CSTL:END -->
@@ -6,6 +6,20 @@
6
6
 
7
7
  For **external / current / web facts**, **smart-search** (`run_smart_search.py`) is the **mandatory first choice**. Platform built-in web tools (Cursor `WebSearch` / `WebFetch`) are **downgrade-only**: use them solely when smart-search is unavailable (`doctor` not ok, `not_configured` / `failed`, or timeout). Never reach for built-in web search while smart-search is healthy. This rule overrides any general "use available tools" instinct — web retrieval strength is routed inside Trellis, not left to the platform default.
8
8
 
9
+ ## External-knowledge gate (search-or-not)
10
+
11
+ Ask: *If this answer were wrong because the world or a third-party API moved, would that matter?*
12
+
13
+ | Decision | Action |
14
+ | --- | --- |
15
+ | **YES** (freshness / third-party surface) | Run `run_smart_search.py` — prefer `docs` / `official-source` / `broad-search` when cheap; use `deep-research` when multi-source or claim-risk is high |
16
+ | **NO** (truth lives in this workspace) | Use rg / codegraph / `.cstl/spec` / task artifacts only — do **not** default to web |
17
+ | **Ambiguous** | Prefer a **cheap** external probe over guessing; do not skip solely to save a call |
18
+
19
+ **YES examples:** library/SDK current API or version; changelog / release notes; CVE / GitHub issue status; pricing / product status; live URLs; industry practice when designing policy.
20
+ **NO examples:** symbol location / call chains; in-repo Trellis contracts; behavior of the code you are editing; pure rename/lint inside known files.
21
+ After search: persist under `{TASK}/research/` with a **provider label**; treat hits as candidates until corroborated (repo, test, or second source).
22
+
9
23
  ## Quick matrix
10
24
 
11
25
  | Need | Tool | Notes |
@@ -75,6 +75,16 @@ POLICY_PATTERNS = [
75
75
  re.compile(r"\bresponsibilit(y|ies)\b", re.I),
76
76
  re.compile(r"不能"),
77
77
  re.compile(r"规则"),
78
+ # Workflow / task-ladder docs (AGENTS.md, workflow.md) — not code-search first
79
+ re.compile(r"\bFull\s+Task\b", re.I),
80
+ re.compile(r"\bLite\s+Task\b", re.I),
81
+ re.compile(r"\bMicro-Grill\b", re.I),
82
+ re.compile(r"\bParent\s+Task\b", re.I),
83
+ re.compile(r"\bChild\s+Task\b", re.I),
84
+ re.compile(r"\bTriage\b"),
85
+ re.compile(r"\btask\s+ladder\b", re.I),
86
+ re.compile(r"任务阶梯"),
87
+ re.compile(r"工作流"),
78
88
  ]
79
89
 
80
90
  CONCEPTUAL_PATTERNS = [
@@ -152,6 +162,10 @@ EXACT_PATTERNS = [
152
162
  re.compile(r"`[^`]+`"),
153
163
  re.compile(r"\b[\w.-]+\.(ts|tsx|js|jsx|py|rs|go|swift|kt|md|json|yaml|yml)\b", re.I),
154
164
  re.compile(r"\b(?:src|packages|extensions)/[\w./-]+", re.I),
165
+ # Meta / router-implementation queries → lexical exact (not policy docs)
166
+ re.compile(r"检索(意图)?路由"),
167
+ re.compile(r"\bintent\s*rout(?:e|ing)?\b", re.I),
168
+ re.compile(r"codebase[_-]?retrieval[_-]?router", re.I),
155
169
  ]
156
170
 
157
171
 
@@ -289,6 +289,317 @@ def build_child_prompt(
289
289
  return "\n".join(lines), errors
290
290
 
291
291
 
292
+
293
+ def _children_by_id(data: dict) -> dict[str, dict]:
294
+ return {
295
+ item.get("id"): item
296
+ for item in data.get("children", [])
297
+ if isinstance(item, dict) and isinstance(item.get("id"), str)
298
+ }
299
+
300
+
301
+ def _normalize_stages(data: dict) -> tuple[list[dict], list[str], bool]:
302
+ """Return (stages, errors, used_legacy_default)."""
303
+ errors: list[str] = []
304
+ children = _children_by_id(data)
305
+ raw_stages = data.get("stages") or []
306
+ if not isinstance(raw_stages, list):
307
+ return [], ["task-map stages must be a list"], False
308
+
309
+ if not raw_stages:
310
+ units = list(children.keys())
311
+ return (
312
+ [{"id": "default", "title": "All children (no stages declared)", "units": units}],
313
+ [],
314
+ True,
315
+ )
316
+
317
+ stages: list[dict] = []
318
+ seen_units: set[str] = set()
319
+ for idx, stage in enumerate(raw_stages):
320
+ if not isinstance(stage, dict):
321
+ errors.append(f"stages[{idx}] must be a mapping")
322
+ continue
323
+ stage_id = stage.get("id")
324
+ if not isinstance(stage_id, str) or not stage_id.strip():
325
+ errors.append(f"stages[{idx}] missing id")
326
+ continue
327
+ units = stage.get("units") or []
328
+ if not isinstance(units, list):
329
+ errors.append(f"stage `{stage_id}` units must be a list")
330
+ continue
331
+ norm_units: list[str] = []
332
+ for unit in units:
333
+ if not isinstance(unit, str) or not unit.strip():
334
+ errors.append(f"stage `{stage_id}` has invalid unit entry")
335
+ continue
336
+ if unit not in children:
337
+ errors.append(f"stage `{stage_id}` unit `{unit}` not in children[]")
338
+ continue
339
+ if unit in seen_units:
340
+ errors.append(f"unit `{unit}` appears in multiple stages")
341
+ continue
342
+ seen_units.add(unit)
343
+ norm_units.append(unit)
344
+ stages.append(
345
+ {
346
+ "id": stage_id,
347
+ "title": stage.get("title") if isinstance(stage.get("title"), str) else "",
348
+ "units": norm_units,
349
+ }
350
+ )
351
+ return stages, errors, False
352
+
353
+
354
+ def _unit_readiness(
355
+ unit_id: str,
356
+ children_by_id: dict[str, dict],
357
+ ) -> tuple[str, list[str]]:
358
+ entry = children_by_id.get(unit_id) or {}
359
+ blocked = _unmet_dependencies(entry, children_by_id)
360
+ return ("ready" if not blocked else "blocked", blocked)
361
+
362
+
363
+ def build_publish_pack(
364
+ parent_dir: Path,
365
+ *,
366
+ stage_id: str | None = None,
367
+ mode: str = "inline",
368
+ dry_run: bool = False,
369
+ ) -> tuple[str | None, list[str]]:
370
+ """Build campaign PACK index + per-unit prompts under child-prompts/."""
371
+ errors: list[str] = []
372
+ repo_root = get_repo_root()
373
+ parent_rel = _repo_rel(parent_dir, repo_root)
374
+
375
+ if not (parent_dir / "task.json").is_file():
376
+ return None, [f"parent task.json missing: {parent_dir}"]
377
+
378
+ data, _ = load_task_map(parent_dir)
379
+ if data is None:
380
+ return None, ["parent task-map.md missing or invalid"]
381
+
382
+ stages, stage_errors, legacy = _normalize_stages(data)
383
+ errors.extend(stage_errors)
384
+ if errors:
385
+ return None, errors
386
+
387
+ if stage_id:
388
+ stages = [s for s in stages if s.get("id") == stage_id]
389
+ if not stages:
390
+ return None, [f"unknown stage id: {stage_id}"]
391
+
392
+ children_by_id = _children_by_id(data)
393
+ prompts_dir = parent_dir / "child-prompts"
394
+ ready_rows: list[tuple[str, str, str]] = []
395
+ blocked_rows: list[tuple[str, str, list[str]]] = []
396
+
397
+ pack_lines = [
398
+ f"# Campaign PACK — `{parent_dir.name}`",
399
+ "",
400
+ f"- Parent: `{parent_rel}`",
401
+ f"- Generated: {_utc_now()}",
402
+ f"- Mode: `{mode}`",
403
+ "- Path rule: every worker must `task.py select <explicit-child-path>`; "
404
+ "do not rely on selected_task inheritance across windows.",
405
+ "",
406
+ "## Manual window path (PACK)",
407
+ "",
408
+ "1. Run `publish-pack` (this file) on the Parent.",
409
+ "2. For each **ready** unit: IDE **New Chat** or Agents Window → paste `child-prompts/<child>.md`.",
410
+ "3. In that session run `python ./.cstl/scripts/task.py select <child-path>`.",
411
+ "4. Parent retains `review-child` / `integrate-child`. PACK is not RUN auto-dispatch.",
412
+ "",
413
+ "## Stages",
414
+ "",
415
+ ]
416
+ if legacy:
417
+ pack_lines.append(
418
+ "> Warning: no `stages:` in task-map; using implicit `default` stage "
419
+ "with all children. Declare `stages:` for HYBRID campaign packs."
420
+ )
421
+ pack_lines.append("")
422
+
423
+ written: list[str] = []
424
+ for stage in stages:
425
+ sid = stage["id"]
426
+ title = stage.get("title") or ""
427
+ heading = f"### `{sid}`" + (f" — {title}" if title else "")
428
+ pack_lines.append(heading)
429
+ pack_lines.append("")
430
+ for unit in stage.get("units") or []:
431
+ readiness, blocked = _unit_readiness(unit, children_by_id)
432
+ child_dir = parent_dir.parent / unit
433
+ child_rel = _repo_rel(child_dir, repo_root)
434
+ state = (children_by_id.get(unit) or {}).get("state", "?")
435
+ if readiness == "ready":
436
+ prompt, prompt_errors = build_child_prompt(
437
+ parent_dir,
438
+ child_dir,
439
+ include_artifacts=False,
440
+ mode=mode,
441
+ )
442
+ if prompt_errors or not prompt:
443
+ errors.extend(prompt_errors or [f"could not build prompt for `{unit}`"])
444
+ continue
445
+ out_name = f"{unit}.md"
446
+ out_rel = f"{parent_rel}/child-prompts/{out_name}"
447
+ if not dry_run:
448
+ prompts_dir.mkdir(parents=True, exist_ok=True)
449
+ (prompts_dir / out_name).write_text(prompt + "\n", encoding="utf-8")
450
+ written.append(out_rel)
451
+ ready_rows.append((sid, unit, out_rel))
452
+ pack_lines.append(
453
+ f"- `{unit}` — `{state}` — **ready** — prompt: `child-prompts/{out_name}` "
454
+ f"(select `{child_rel}`)"
455
+ )
456
+ else:
457
+ blocked_rows.append((sid, unit, blocked))
458
+ stub = "\n".join(
459
+ [
460
+ f"# BLOCKED — `{unit}`",
461
+ "",
462
+ f"- Parent: `{parent_rel}`",
463
+ f"- Child: `{child_rel}`",
464
+ f"- Stage: `{sid}`",
465
+ "- Do not start implementation until dependencies reach "
466
+ "`integrated` or `cancelled`.",
467
+ "",
468
+ "Unmet dependencies:",
469
+ *[f"- {item}" for item in blocked],
470
+ "",
471
+ ]
472
+ )
473
+ out_name = f"{unit}.md"
474
+ out_rel = f"{parent_rel}/child-prompts/{out_name}"
475
+ if not dry_run:
476
+ prompts_dir.mkdir(parents=True, exist_ok=True)
477
+ (prompts_dir / out_name).write_text(stub, encoding="utf-8")
478
+ written.append(out_rel)
479
+ pack_lines.append(
480
+ f"- `{unit}` — `{state}` — **blocked** — {', '.join(blocked)}"
481
+ )
482
+ pack_lines.append("")
483
+
484
+ pack_lines.extend(["## Ready", ""])
485
+ if ready_rows:
486
+ for sid, unit, path in ready_rows:
487
+ pack_lines.append(f"- `{sid}` / `{unit}` → `{path}`")
488
+ else:
489
+ pack_lines.append("(none)")
490
+ pack_lines.extend(["", "## Blocked", ""])
491
+ if blocked_rows:
492
+ for sid, unit, deps in blocked_rows:
493
+ pack_lines.append(f"- `{sid}` / `{unit}` — {', '.join(deps)}")
494
+ else:
495
+ pack_lines.append("(none)")
496
+ pack_lines.append("")
497
+
498
+ pack_body = "\n".join(pack_lines)
499
+ pack_rel = f"{parent_rel}/child-prompts/PACK.md"
500
+ if not dry_run:
501
+ prompts_dir.mkdir(parents=True, exist_ok=True)
502
+ (prompts_dir / "PACK.md").write_text(pack_body + "\n", encoding="utf-8")
503
+ written.append(pack_rel)
504
+
505
+ if errors:
506
+ return None, errors
507
+
508
+ summary = [
509
+ f"# publish-pack — `{parent_dir.name}`",
510
+ "",
511
+ f"- Parent: `{parent_rel}`",
512
+ f"- dry_run: {dry_run}",
513
+ f"- legacy_no_stages: {legacy}",
514
+ f"- ready: {len(ready_rows)}",
515
+ f"- blocked: {len(blocked_rows)}",
516
+ f"- pack_index: `{pack_rel}`",
517
+ "",
518
+ ]
519
+ if written:
520
+ summary.append("## Written")
521
+ summary.append("")
522
+ for path in written:
523
+ summary.append(f"- `{path}`")
524
+ summary.append("")
525
+ elif dry_run:
526
+ summary.append("(dry-run: no files written)")
527
+ summary.append("")
528
+ summary.append(pack_body)
529
+ return "\n".join(summary), []
530
+
531
+
532
+ def build_parent_status_dict(parent_dir: Path) -> dict | None:
533
+ """Machine-readable parent orchestration snapshot (campaign UI / MCP)."""
534
+ repo_root = get_repo_root()
535
+ parent_rel = _repo_rel(parent_dir, repo_root)
536
+ data, _ = load_task_map(parent_dir)
537
+ if data is None:
538
+ return None
539
+
540
+ children_by_id = _children_by_id(data)
541
+ stages, stage_errors, legacy = _normalize_stages(data)
542
+
543
+ stage_rows: list[dict] = []
544
+ for stage in stages:
545
+ units: list[dict] = []
546
+ for unit in stage.get("units") or []:
547
+ entry = children_by_id.get(unit) or {}
548
+ readiness, blocked = _unit_readiness(unit, children_by_id)
549
+ units.append(
550
+ {
551
+ "id": unit,
552
+ "state": entry.get("state", "?"),
553
+ "readiness": readiness,
554
+ "blockedBy": blocked,
555
+ }
556
+ )
557
+ stage_rows.append(
558
+ {
559
+ "id": stage["id"],
560
+ "title": stage.get("title") or "",
561
+ "units": units,
562
+ }
563
+ )
564
+
565
+ children_rows: list[dict] = []
566
+ for child in data.get("children") or []:
567
+ if not isinstance(child, dict):
568
+ continue
569
+ cid = child.get("id")
570
+ if not isinstance(cid, str):
571
+ continue
572
+ child_dir = parent_dir.parent / cid
573
+ children_rows.append(
574
+ {
575
+ "id": cid,
576
+ "state": child.get("state", "?"),
577
+ "dependsOn": child.get("depends_on") or [],
578
+ "touches": child.get("touches") or [],
579
+ "isolation": child.get("isolation"),
580
+ "branch": child.get("branch"),
581
+ "worktreePath": child.get("worktree_path"),
582
+ "evidence": child.get("evidence"),
583
+ "ref": child.get("ref"),
584
+ "verifyMd": (child_dir / "verify.md").is_file(),
585
+ "handoffMd": (child_dir / "handoff.md").is_file(),
586
+ }
587
+ )
588
+
589
+ return {
590
+ "id": parent_dir.name,
591
+ "path": parent_rel,
592
+ "contractEpoch": data.get("contract_epoch"),
593
+ "executionTopology": data.get("execution_topology"),
594
+ "mergeLimit": data.get("merge_limit"),
595
+ "stages": stage_rows,
596
+ "children": children_rows,
597
+ "integrationQueue": data.get("integration_queue") or [],
598
+ "stageErrors": stage_errors,
599
+ "legacyStages": legacy,
600
+ }
601
+
602
+
292
603
  def build_parent_status(parent_dir: Path) -> str:
293
604
  """Render a parent task-map status summary for the reviewer."""
294
605
  repo_root = get_repo_root()
@@ -305,10 +616,37 @@ def build_parent_status(parent_dir: Path) -> str:
305
616
  f"- execution_topology: {data.get('execution_topology', '?')}",
306
617
  f"- merge_limit: {data.get('merge_limit', '?')}",
307
618
  "",
308
- "## Children",
309
- "",
310
619
  ]
311
620
 
621
+ children_by_id = _children_by_id(data)
622
+ stages, stage_errors, legacy = _normalize_stages(data)
623
+ lines.append("## Stages")
624
+ lines.append("")
625
+ if stage_errors:
626
+ for err in stage_errors:
627
+ lines.append(f"- error: {err}")
628
+ lines.append("")
629
+ elif legacy:
630
+ lines.append("stages: (none declared — implicit `default` for publish-pack)")
631
+ lines.append("")
632
+ else:
633
+ for stage in stages:
634
+ sid = stage["id"]
635
+ title = stage.get("title") or ""
636
+ heading = f"### `{sid}`" + (f" — {title}" if title else "")
637
+ lines.append(heading)
638
+ for unit in stage.get("units") or []:
639
+ entry = children_by_id.get(unit) or {}
640
+ state = entry.get("state", "?")
641
+ readiness, blocked = _unit_readiness(unit, children_by_id)
642
+ if readiness == "ready":
643
+ lines.append(f"- `{unit}` — `{state}` — ready")
644
+ else:
645
+ lines.append(f"- `{unit}` — `{state}` — blocked ({', '.join(blocked)})")
646
+ lines.append("")
647
+
648
+ lines.extend(["## Children", ""])
649
+
312
650
  children = data.get("children") or []
313
651
  if not children:
314
652
  lines.append("(no children in task-map)")
@@ -354,6 +692,7 @@ def build_parent_status(parent_dir: Path) -> str:
354
692
  lines.append("")
355
693
  lines.append("```bash")
356
694
  lines.append(f"python ./.cstl/scripts/task.py parent-status {parent_rel}")
695
+ lines.append(f"python ./.cstl/scripts/task.py publish-pack {parent_rel}")
357
696
  lines.append(f"python ./.cstl/scripts/task.py generate-child-prompt {parent_rel} <child> --mode subagent")
358
697
  lines.append(f"python ./.cstl/scripts/task.py generate-child-prompt {parent_rel} <child> --mode inline")
359
698
  lines.append(f"python ./.cstl/scripts/task.py review-child {parent_rel} <child> --check")