From ccc0cf44768e7aa6f4c92e810ea4271e030b0412 Mon Sep 17 00:00:00 2001 From: only-cli Date: Tue, 25 Aug 2026 09:46:23 -0400 Subject: [PATCH] docs: explain the search shapes a site definition can use, cover every DuckDuckGo search verb CONTRIBUTING described a command as a URL template only, so a contributor adding a docs site had no way to learn that a search verb can also name a static index (sphinx, rdoc, nodedoc), a JSON endpoint (api), or fall back to DuckDuckGo with a site: filter, or how to pick between them. It also still counted six files in src/. The rust, java, ts, and php search verbs had no test; only cpp's did. #25 asked for both verbs per language to be covered offline. --- CONTRIBUTING.md | 11 ++++++++++- tests/sites.test.js | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14d75de..ed7a84f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Node 20+. Tests run fully offline against saved fixtures in `tests/pages/`, so a ## Code style -Plain JavaScript, ESM, JSDoc types, no build step. Match the code around you. Comments explain constraints and trade-offs, not what the next line does; if a comment restates the code, delete it. Small functions, few files: if you are adding a new file to `src/`, pause and check whether the logic belongs in one of the six that exist. +Plain JavaScript, ESM, JSDoc types, no build step. Match the code around you. Comments explain constraints and trade-offs, not what the next line does; if a comment restates the code, delete it. Small functions, few files: if you are adding a new file to `src/`, pause and check whether the logic belongs in one of the files that already exist. ## Writing style @@ -37,6 +37,15 @@ A definition needs no wiring: `oc [args]` resolves against `clis/* Per-site CLIs live in `clis/`, one JSON file per domain: the domain plus a `commands` map of name, help line, and URL template, exactly like the existing files. Keep it under 50 lines, no OpenAPI. If the site has a public JSON API, point the commands at that instead of the HTML pages. If your definition needs logic, it is trying to become an adapter, and the answer is to improve the generic engine instead. +A `search` verb deserves a moment's thought, because most documentation sites render search in the browser and hand oc an empty page. Check what the site actually ships, in this order: + +- **A server-rendered results page.** Point `open` at it, the way `pkg.go.dev` and Microsoft Learn's RSS endpoint do. Nothing else is needed. +- **A public JSON endpoint behind the results page.** Use the `api` shape: `api` is the endpoint template, `page` the human URL to remember for the session, `results` the dot path to the list, `fields` the paths to each result's `title`, `url`, and `text`, and `total` the path to the count. `clis/developer.mozilla.org.json` is the model. The site's own ranking comes back as a numbered results page for a few hundred tokens. +- **A static search index.** Sphinx sites (docs.python.org, most Read the Docs projects) publish `searchindex.js`; RDoc sites publish `js/search_index.js`; the Node.js API docs publish `all.json`. Set `sphinx`, `rdoc`, or `nodedoc` to the docs root and oc fetches the index once a day, ranks it locally, and prints only the result list. Any Sphinx or RDoc site works with no code. +- **None of the above.** Fall back to DuckDuckGo with a baked-in filter: `"open": "https://html.duckduckgo.com/html/?q=site%3Aexample.org+{query}"`, as `rust`, `java`, `ts`, `php`, and `cpp` do. Say so in the README row ("search via DuckDuckGo") so nobody mistakes it for the site's own search. + +Adding a fifth search backend is a change to `src/`, not to a JSON file, and needs the same case as any other new code: a site that many agents reach for, an index or endpoint that a template cannot describe, and offline tests against a saved copy of the index. + ## Reporting bugs Open an issue with the exact command, the output you got, and the output you expected. If the page is public, include the URL. If distillation mangled a page, a saved copy of the HTML as a fixture is the most useful thing you can attach. diff --git a/tests/sites.test.js b/tests/sites.test.js index 267d9e6..c0fad80 100644 --- a/tests/sites.test.js +++ b/tests/sites.test.js @@ -117,12 +117,24 @@ test('the second wave of language docs resolves the same way', () => { assert.equal( resolveSite('rust', ['std', 'vec/struct.Vec']).url, 'https://doc.rust-lang.org/std/vec/struct.Vec.html'); + assert.equal( + resolveSite('rust', ['search', 'Vec', 'retain']).url, + 'https://html.duckduckgo.com/html/?q=site%3Adoc.rust-lang.org+Vec%20retain'); assert.equal( resolveSite('java', ['api', 'java.base/java/util/HashMap']).url, 'https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/HashMap.html'); + assert.equal( + resolveSite('java', ['search', 'HashMap', 'computeIfAbsent']).url, + 'https://html.duckduckgo.com/html/?q=site%3Adocs.oracle.com+javase+HashMap%20computeIfAbsent'); assert.equal( resolveSite('ts', ['handbook', '2/everyday-types']).url, 'https://www.typescriptlang.org/docs/handbook/2/everyday-types.html'); + assert.equal( + resolveSite('ts', ['search', 'satisfies']).url, + 'https://html.duckduckgo.com/html/?q=site%3Atypescriptlang.org+satisfies'); + assert.equal( + resolveSite('php', ['search', 'array', 'functions']).url, + 'https://html.duckduckgo.com/html/?q=site%3Aphp.net+array%20functions'); assert.equal( resolveSite('learn', ['dotnet', 'system.string']).url, 'https://learn.microsoft.com/en-us/dotnet/api/system.string');