From c6b02e4078b36bedd27364033f1fd9be4cbe99cd Mon Sep 17 00:00:00 2001 From: thoroc Date: Sun, 23 Nov 2025 21:42:28 +0000 Subject: [PATCH 1/3] fix: make pages/fix-links.js more robust for internal links --- pages/fix-links.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pages/fix-links.js b/pages/fix-links.js index d2d5b95..360af7c 100644 --- a/pages/fix-links.js +++ b/pages/fix-links.js @@ -28,8 +28,10 @@ const BASE_PATH = '/opencode-warcraft-notifications'; // Matches: href="/something/" but not href="/opencode-warcraft-notifications/something/" // Also matches: href="/something/#anchor" and href="/" // But not href="/opencode-warcraft-notifications" (exact match) +// Match href="/..." that does not already include the base path +// Allow anchors and query strings; preserve file extensions. Exclude protocol-relative and external links. const INTERNAL_LINK_PATTERN = - /href="(\/(?!opencode-warcraft-notifications(?:\/|"|$))(?:[^"#\s][^"]*?)?)"/g; + /href="(\/(?!opencode-warcraft-notifications(?:\/|"|$))(?:[^"\s]*?))"/g; /** * Fix links in a single HTML file @@ -51,11 +53,13 @@ async function fixLinksInFile(filePath) { return match; } - // If path is base path without trailing slash, add trailing slash + // If path is exactly base path, ensure trailing slash if (path === BASE_PATH) { return `href="${BASE_PATH}/"`; } + // Preserve anchors and query strings while prefixing base path + // e.g. "/foo#bar" -> "/base/foo#bar" modified = true; return `href="${BASE_PATH}${path}"`; }); From ac863ac12f44fb4c0d339f943db22226075d6f4f Mon Sep 17 00:00:00 2001 From: thoroc Date: Sun, 23 Nov 2025 21:46:22 +0000 Subject: [PATCH 2/3] fix(search): return consistent 'no results' shape for cached empty searches --- src/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/index.ts b/src/index.ts index ab71a56..b87cbd5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -193,6 +193,21 @@ export const ResourceLoaderPlugin: Plugin = async (ctx) => { const cacheKey = `${query}:${type || 'all'}:${max_results}`; const cached = getCachedSearch(cacheKey); if (cached) { + // Ensure cached empty results use the same "no results" response shape + if (cached.length === 0) { + const categories = Array.from(index.byCategory.keys()); + return JSON.stringify({ + results: [], + message: `No resources found matching '${query}'`, + suggestions: [ + 'Try broader search terms', + 'Check spelling', + "Use resource_list({ type: 'all' }) to see all available resources", + ], + availableCategories: categories, + }); + } + return JSON.stringify(formatSearchResults(cached)); } From a09bf42fda05e61dfd71ff38afd47f089edb5162 Mon Sep 17 00:00:00 2001 From: thoroc Date: Sun, 23 Nov 2025 21:50:03 +0000 Subject: [PATCH 3/3] docs: document resource_search response shape & cache behavior --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/README.md | 60 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 119 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0ecf774..c899824 100644 --- a/README.md +++ b/README.md @@ -153,12 +153,26 @@ resource_list({ category: 'Documentation' }); resource_list({ tag: 'api' }); ``` -### resource_search +### resource_search (tool) -Search with intelligent relevance scoring. +Search resources by keyword with intelligent relevance scoring. This tool performs a full-text, multi-field search across resource names, tool names, tags, descriptions, and content. + +Arguments and validation: + +- `query` (required): Non-empty search string (case-insensitive). An empty query will be rejected. +- `type` (optional): Filter by resource type (agent, checklist, command, knowledge-base, task, template, all). Defaults to `all` when omitted. +- `max_results` (optional): Maximum number of results to return. Defaults to `10`. + +Cache semantics: + +- Cache key format: `${query}:${type || 'all'}:${max_results}` +- Cache TTL: 5 minutes (300000 ms). Cached responses (including cached empty results) are returned for this duration. +- Note: cached empty searches now return the same no-results response shape as non-cached empty searches. This ensures a consistent response shape for consumers and tests (see PR #6). + +Usage examples: ```typescript -// Search across all fields +// Search by keyword across all fields resource_search({ query: 'api documentation' }); // Search within specific type @@ -168,6 +182,57 @@ resource_search({ query: 'deployment', type: 'task' }); resource_search({ query: 'security', max_results: 5 }); ``` +Example successful response (formatted): + +```json +{ + "count": 2, + "results": [ + { + "score": 57, + "toolName": "checklist_api_documentation", + "name": "API Documentation Checklist", + "type": "checklist", + "description": "Checklist for producing API documentation", + "category": "Documentation", + "tags": ["api", "documentation"], + "matchedFields": ["name", "tags", "description"], + "snippet": "...comprehensive API documentation checklist for REST and GraphQL..." + }, + { + "score": 34, + "toolName": "knowledge_base_api_examples", + "name": "API Examples", + "type": "knowledge-base", + "description": "Sample API requests and patterns", + "category": "Development", + "tags": ["api", "examples"], + "matchedFields": ["content"], + "snippet": "...example request showing authentication headers..." + } + ] +} +``` + +Example no-results response (returned for both cached and non-cached empty searches): + +```json +{ + "results": [], + "message": "No resources found matching 'documentation'", + "suggestions": [ + "Try broader search terms", + "Check spelling", + "Use resource_list({ type: 'all' }) to see all available resources" + ], + "availableCategories": [] +} +``` + +Why this matters + +- Returning a consistent shape for empty responses (cached or not) prevents consumer and test breakage when callers assume a fixed schema. See the fix in PR #6 which merged into `fix/docs-linking-robustness`. + ### resource_info Get detailed resource metadata. diff --git a/src/README.md b/src/README.md index 4fc2a34..ca05d78 100644 --- a/src/README.md +++ b/src/README.md @@ -243,17 +243,23 @@ resource_list({ type: 'all' }); **Performance:** Fast, low context impact (metadata only) -### resource_search +### resource_search (tool) -Search resources by keyword with relevance scoring. +Search resources by keyword with multi-field relevance scoring. The tool accepts the following arguments and performs input validation (non-empty `query` required). -**Arguments:** +Arguments and validation: -- `query` (required): Search query (case-insensitive) -- `type` (optional): Filter by type -- `max_results` (optional): Maximum results to return (default: 10) +- `query` (required): Non-empty search string (case-insensitive). Empty queries are rejected. +- `type` (optional): Resource type filter (agent, checklist, command, knowledge-base, task, template, all). Defaults to `all` when omitted. +- `max_results` (optional): Maximum number of results to return. Defaults to `10`. -**Usage:** +Cache details: + +- Cache key format: `${query}:${type || 'all'}:${max_results}` +- Cache TTL: 5 minutes (300000 ms) +- Cached empty results now return the same no-results response shape as non-cached empty searches for consistency (see PR #6). + +Usage: ```typescript // Search by keyword across all fields @@ -266,9 +272,45 @@ resource_search({ query: 'deployment', type: 'task' }); resource_search({ query: 'security', max_results: 5 }); ``` -**Performance:** Fast (<100ms for 100+ resources), cached (5-min TTL) +Example successful response: + +```json +{ + "count": 1, + "results": [ + { + "score": 50, + "toolName": "checklist_api_documentation", + "name": "API Documentation Checklist", + "type": "checklist", + "description": "Checklist for producing API documentation", + "category": "Documentation", + "tags": ["api", "documentation"], + "matchedFields": ["name", "tags"], + "snippet": "...API documentation checklist with REST and GraphQL examples..." + } + ] +} +``` + +Example no-results response: + +```json +{ + "results": [], + "message": "No resources found matching 'documentation'", + "suggestions": [ + "Try broader search terms", + "Check spelling", + "Use resource_list({ type: 'all' }) to see all available resources" + ], + "availableCategories": [] +} +``` + +Performance: Fast (<100ms for 100+ resources), cached (5-min TTL) -**Scoring:** Multi-field relevance scoring (exact name: 20, tags: 15, description: 5, content: 2) +Scoring: Multi-field relevance scoring (exact name: 20, tags: 15, description: 5, content: 2) ### resource_info