chore: import upstream snapshot with attribution
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:39:48 +08:00
commit 9b395f5cc3
2400 changed files with 376116 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
# Convention Audit
`opencli convention-audit` scans adapter metadata plus source files for common
agent-native convention violations.
The command is intentionally report-first. It gives agents a shared fact base
before starting a sweep PR; `--strict` can be used later by CI gates.
## Usage
```bash
opencli convention-audit
opencli convention-audit --site twitter
opencli convention-audit twitter/search
opencli convention-audit --site pixiv -f yaml
opencli convention-audit --strict
```
Formats:
- `table` prints a grouped human-readable report.
- `yaml` is the recommended agent-facing format.
- `json` is available for stricter machine consumers.
## Rules
The first version reports these categories:
- `silent-column-drop`: source rows emit top-level keys that are not present in `columns`.
- `camelCase-in-columns`: output columns should use stable snake_case keys.
- `missing-access-metadata`: every adapter command must declare `access: 'read' | 'write'`.
- `silent-clamp`: `Math.min(...limit...)` can silently change user input instead of throwing `ArgumentError`.
- `silent-empty-fallback`: `return []` can hide fetch/parse failures from agents.
- `silent-sentinel`: `?? 'unknown'` / `|| 'N/A'` style fallbacks can turn missing data into fake data.
- `write-without-delete-pair`: write commands such as `like`, `save`, `follow`, `create`, or `post` should have an undo/delete counterpart when the site supports one.
The scanners are heuristic. Treat reports as prioritized review input by
default, then turn a specific rule into a strict CI gate only after the current
violations and exemptions are understood.
## CI Gates
`npm run check:silent-column-drop` enforces the `silent-column-drop` rule in
baseline mode. The baseline file is
`scripts/silent-column-drop-baseline.json`.
`npm run check:typed-error-lint` enforces the silent failure rules in baseline
mode:
- `silent-clamp`
- `silent-empty-fallback`
- `silent-sentinel`
The baseline file is `scripts/typed-error-lint-baseline.json`.
Each gate fails only on new violations beyond its baseline. This lets the repo
adopt the invariant immediately while existing findings are cleaned up in
separate sweep PRs.
When a sweep fixes existing silent-column-drop entries, update the baseline:
```bash
npm run build
node scripts/check-silent-column-drop.mjs --update-baseline
```
When a sweep fixes existing typed-error findings, update the baseline:
```bash
npm run build
node scripts/check-typed-error-lint.mjs --update-baseline
```
@@ -0,0 +1,134 @@
# Listing↔detail ID pairing (advisory)
> **Status:** advisory convention, not a CI gate. Use as guidance when
> designing listing columns; an audit script (`npm run advise:listing-id-pairing`)
> can surface candidate listings for human review.
When a site exposes both a **listing-class command** (`search` / `hot` /
`recent` / `top` / `feed` / ...) and a **detail-class command** (`read` /
`paper` / `article` / `job` / `view` / ...), it's usually nicer for agents
if the listing rows include an id-shaped column whose value can be passed
directly into the detail command.
Without that, the agent has to either re-search by title or scrape a URL
out of band to follow up on a listing row.
## Soft convention
> If `<site>` has both a listing command and a detail command, prefer
> giving each listing row a column whose value round-trips into the
> detail command's positional argument.
That column is whatever the detail command expects:
| Site | Listing | Detail | Round-trip column |
|------|---------|--------|-------------------|
| `hackernews` | `top` / `best` / `ask` / `jobs` / `show` / `new` | `read <id>` | `id` |
| `hupu` | `hot` / `search` | `detail <tid>` | `tid` |
| `lobsters` | `hot` / `active` / `newest` / `tag` | `read <id>` | `id` (Lobsters short_id value) |
| `arxiv` | `search` / `recent` | `paper <id>` | `id` |
| `stackoverflow` | `hot` / `search` / `bounties` / `unanswered` | `read <id>` | `id` |
| `openreview` | `search` / `venue` | `paper <id>` / `reviews <forum>` | `id` |
| `devto` | `top` / `tag` / `user` | `read <id>` | `id` |
| `bilibili` | `hot` | `video <bvid>` | `bvid` |
| `reddit` | `hot` | `read <id>` | `id` |
| `bluesky` | `user` | `thread <uri>` | `uri` |
| `1688` | `search` | `item <url-or-offer-id>` | `offer_id` |
| `tieba` | `search` | `read <id>` | `id` |
| `jike` | `feed` / `search` / `user` | `post <id>` | `id` |
| `weibo` | `feed` / `search` | `post <id>` | `id` |
## Why this matters
The agent never has eyes on a webpage. It sees the listing rows as a
plain table of strings. If the listing emits `[rank, title, author, votes]`
and the detail command needs an id, the agent has three bad options:
1. **Re-search by title** — fragile, may hit a different post for ambiguous
titles, costs an extra round-trip plus quota.
2. **Parse the URL** — assumes URL shape stays stable and is regex-able,
often breaks across A/B buckets or after a site redesign.
3. **Hand-craft a search** — pure guess; agents trained to do this leak
the failure mode silently downstream.
Surfacing the id in the listing collapses all three into a single,
unambiguous column.
## Why this is advisory, not a gate
Whether a listing should pair with a detail is a case-by-case product/UX
call. Many legitimate listings genuinely don't pair:
- **Topic-string listings** (`twitter trending`, `weibo hot`) — rows are
search keywords, not addressable entities.
- **Profile-attribute listings** (`reddit user`, `lesswrong user`,
`weibo user`) — rows are `[field, value]` pairs of one profile.
- **UI-only sessions** (`discord-app search`, app quick find surfaces)
— page ids aren't extractable from the rendered DOM.
- **Comment / reply listings** — sub-resources of a parent thread; the
detail command fetches the parent, not the comment.
A hard CI gate forced authors to either add an artificial id column or
file an exemption with a reason — both of which were higher cognitive cost
than the silent-loss bugs the rule actually catches. See PR #1311 thread
for the broader "anti-pattern vs case-by-case" filter.
## What counts as an "id-shaped column"
The advisory script (`scripts/check-listing-id-pairing.mjs`) considers any
of these column names a valid round-trip handle:
- Exact `id` / `short_id`
- Anything ending in `_id` or `Id` (e.g. `offer_id`, `paperId`)
- Domain-specific ids: `jk` (indeed), `tid` (thread id), `bvid` / `aid`
(bilibili), `sku` (retail product SKU), `asin` (amazon), `isbn`, `doi`,
`slug`
- `username` / `handle` (only when the detail command keys off the user
rather than a post)
- `url` — only when the detail command's positional argument explicitly
accepts a URL (for example `read <url-or-id>`, `article <url>`, or help
text such as `Post ID or full URL`). URL is not a valid substitute when
the detail command only says an id comes "from URL"; callers would still
need to parse the URL out of band.
- `uri` — for sites whose canonical handle is a URI scheme (e.g.
Bluesky's `at://did:.../app.bsky.feed.post/...`).
If the natural id for your site is a different shape, add the pattern to
`ID_COLUMN_PATTERNS` in the script.
## How to add an id column to a listing
1. **Find the source field.** Most listing endpoints already include the
id in their raw response — they're just dropped before the row is
shaped. Check `func`'s mapper before adding new fetch logic.
2. **Pick the column position.** Convention is `[rank, id, ...rest, url]`
so identifiers stay near the front and the URL stays last.
3. **Update the docs.** The site's `docs/adapters/browser/<site>.md`
should list the new column under "Listing columns" and call out that
it round-trips into the detail command.
4. **Optional: run the advisory script** to confirm the manifest reflects
the new column.
```bash
npm run advise:listing-id-pairing
```
## Advisory script usage
```bash
# Print a report of listings that don't currently carry an id-shaped column.
# Always exits 0 — does not fail CI.
npm run advise:listing-id-pairing
```
The script reads `cli-manifest.json`, so always rebuild the manifest
(`npx tsx src/build-manifest.ts`) after touching adapter columns.
## Related principles
- [`docs/developer/ts-adapter.md`](../developer/ts-adapter.md) — adapter
authoring conventions, including the required `access: 'read' | 'write'`
metadata.
- "Output design" reference inside the bundled
`opencli-adapter-author` skill — column naming, ordering, and id-first
conventions.