Files
review/skills/api-doc/SKILL.md
jiantw83 5e7de7a792 fix(frontmatter): 修正 api-doc 技能 SKILL.md frontmatter 的 YAML 純量語法錯誤
What:
- 修正 skills/api-doc/SKILL.md frontmatter 裡 description 欄位的 YAML 語法錯誤。
- 整串 description 加上單引號,內部撇號改寫成兩個單引號,內容文字一個字都沒變。
- 同步更新 plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json 三個 manifest 版本號,從 0.1.1 進到 0.1.2。

Why:
- description 內含「冒號加空白」,屬於未加引號的 YAML plain scalar,違反 YAML 語法規定。
- Antigravity 解析 frontmatter 時當場中斷,整支技能被靜默丟棄,沒有任何錯誤訊息;磁碟上 34 支技能,Antigravity 只認得 28 支。
- 準則要求 description 用英文撰寫,不能把「: 」改成全形冒號迴避語法問題,只能加引號修正。

How:
- 整串 description 值加上單引號,內部撇號寫成兩個單引號跳脫,其餘字元不動。
- 用 git show HEAD: 取出改前的原始值,把改後的單引號純量還原後做字串相等比對,確認逐字相同、字元數一致。
- 執行 ste100-lint.sh、check-behaviors.sh、lint-frontmatter.sh 三支檢查腳本,退出碼皆為 0;git diff --numstat 顯示只動了 frontmatter 那一行。

Who:
- 本次修到 review 技能組的 api-doc 技能,屬稽核 API 專案 Swagger/OpenAPI 文件的功能。
2026-08-31 19:04:02 +08:00

80 lines
12 KiB
Markdown

---
name: api-doc
description: 'Audit an API project''s Swagger/OpenAPI documentation: every returnable HTTP status code declares a response type, every input and output carries a description, and a real data example sits on scalar members only — a data-model member takes a description and hands the example duty down to its own properties, recursively to the innermost scalar. Run after controller work or an implementation is complete, e.g. from jsc-sdlc implement, next to jsc-review:code-review. Detection runs first via tools/swagger-detect.sh; without both the Swagger package and its wiring, report unsupported and stop. The source comment contract belongs to jsc-review:code-review group 5; this skill covers Swagger document attributes only. Findings carry file:line, severity, and the fix; this skill never modifies code.'
---
# api-doc
Audit whether an API project's Swagger (OpenAPI) documentation is complete enough for a caller to integrate against it without reading the implementation.
## When to run
1. **After controller work is complete**: one controller or one related group of controllers is done.
2. **When an implementation is complete**: all todos of a work package that touched API endpoints are done. This is the call site in `jsc-sdlc:implement`, right next to `jsc-review:code-review`.
3. **Never on a project without Swagger**: step 1 below decides this in code, not by judgment.
## Division of labor
- This skill covers **Swagger document attributes only**: response type declarations, Swagger parameter descriptions, and Swagger examples.
- The comment contract — method description, layer tag, parameter description, return description, examples, nested-structure comments — belongs to `jsc-review:code-review` group 5 (`references/smells.md` 5.1 to 5.8). Never report the same gap twice; when a nested model already fails 5.5, leave it to `code-review`.
- Security, logic bugs, and test coverage belong to the CLI's built-in review.
## Steps
1. Detect Swagger support: run `tools/swagger-detect.sh {project path}` (defaults to the current directory). The script confirms package **and** wiring, so an installed-but-never-enabled project comes back unsupported. Read its exit code: `0` supported; `1` unsupported; `2` one of three causes — more than one argument, a project path that does not exist, or no `grep` in the environment. Completion condition: the exit code and the `support=`, `stack=`, `package=`, `config=` lines are captured.
2. On exit code `1`, report the literal 「本專案未啟用 Swagger 文件,略過 API 文件稽核」 plus the `stack=` and `package=` lines the script printed, and stop. Spawn no sub agent. On exit code `2`, report the script's stderr message verbatim, then route by which of the three causes that message names: the usage line means the call passed more than one argument, so re-run with at most one path; 「找不到專案路徑」 means the caller supplies an existing project path and re-runs; 「環境缺 grep」 means the environment itself is broken, so report that `grep` must be installed and stop — re-running with another path never clears this one. Spawn no sub agent in any of the three. Completion condition: the run has ended with the cause named and the matching next action stated, or exit code `0` moved it to step 3.
3. List the audit scope: every controller in the project, or only the controllers touched by `git diff` when the caller asked for a scoped run. On a scoped run, read git's exit code: `0` the changed controllers are the scope; any non-zero code means git refused (not a repository, unknown base revision, unreadable object) — report git's stderr verbatim and stop, spawn no sub agent. Never fall back to the whole project there: the caller asked for one scope, and quietly auditing a much larger one buries the git failure under a far longer run. `jsc-review:code-review` step 1 stops on the same failure, so both skills answer a broken `git diff` the same way. Completion condition: the controller list is non-empty and reported; an empty list ends the run with the literal 「無發現」, and a git failure ends it with git's error.
4. Audit in two aspects, and every aspect **MUST run as a sub agent**; the two may run in parallel:
| Aspect | Scope |
| --- | --- |
| 1 Status codes | For every action, every HTTP status code it can actually return — success, validation failure, authorization failure, not found, server error — has a declared response type and body schema (`ProducesResponseType`, `@ApiResponse`, FastAPI `responses=`, drf-spectacular `@extend_schema`). A status code the code can produce but the document never declares is a finding; so is a declared status code the code can never produce |
| 2 Description and example, down every data model | Two checklists over one pass of the controller's input and output types. Checklist A — description and example: every input parameter and every output field has a description, whatever its type, and the example requirement follows the type. A scalar member — string, number, boolean, date, enum — has an example as well. A data-model member has no example of its own; it keeps the description and hands the example duty down to its properties, where checklist B collects it. A collection is judged by its element type: scalar elements make the collection scalar, so it carries one example listing a couple of elements; model elements make the collection a data model, so it carries a description only and the walk enters the element type. A dictionary is judged by its value type on the same rule. Examples come from real project data first (the project's database, seed data, or fixtures), and only when real data is unreachable is an example derived by logic and marked as a derived value in the document itself. Checklist B — recursive walk: when a parameter or return value is a data model, or a collection of one, checklist A applies to every one of its properties, and a model containing another model recurses to the innermost layer. Every scalar property on every layer carries a description and an example; every intermediate model property carries a description only. Walk in from the controller, follow the input and output types down, and end each branch on the layer whose properties are all scalar. Run checklist A on each member as the walk in checklist B reaches it, so each data model file is opened once and both checklists are answered for it |
Checklists A and B ran as two sub agents before, and they opened the same data model files twice. Aspect 2 is one sub agent now. The checklists stay listed apart so no check goes vague once they share a pass: a missing description or example is reported under A, a member the recursive walk never reached is reported under B, and field 4 of every row says which one.
An example exists so the caller reads the literal payload straight off the document, and every literal value has exactly one owner. That is why a data-model member carries none: its payload is defined by its properties' own examples, and a second copy on the parent goes stale the day a property changes. A collection follows the same test on its element type — a list of scalars fits in one literal, so the example is complete where it stands, while a list of models only repeats what the element model already owns. Judge the element type, never the collection wrapper, so `List<string>` and `string` come out the same and `List<Address>` and `Address` come out the same.
Instructions for each sub agent: read only, change nothing. Return findings as TSV rows — one finding per row, six fields separated by a single tab, in this order:
| Field | Content |
| --- | --- |
| 1 file | path relative to the repository root |
| 2 line | line number, a positive integer |
| 3 severity | `高`, `中`, or `低` on the scale below |
| 4 group | the aspect: `1` status codes, `2A` description and example, `2B` recursive walk |
| 5 item | the finding name, taken from the Severity table below |
| 6 detail | one sentence of evidence plus the concrete fix — which attribute to add, on which member |
Fields 5 and 6 are written in Traditional Chinese. Free-form prose is not accepted: `tools/merge-findings.sh` deduplicates on fields 1 and 2, so any row off this format is rejected and its finding never reaches the report. An aspect with nothing to report returns the single line 「無發現」 instead of rows.
Completion condition: both aspects have returned, each with TSV rows or with 「無發現」.
5. Merge with `tools/merge-findings.sh`. Concatenate the two aspects' TSV rows in aspect order and pipe them into the script on stdin. Start this step only once both have returned. Read its exit code: `0` take its stdout as the merged list — deduplicated by location and sorted by severity, 高 first and 低 last; `1` no aspect returned a row, so report the literal 「無發現」 and stop; `2` environment or input error, so report the script's stderr verbatim and stop, and never hand-merge as a substitute; `3` an aspect returned a malformed row, and the stderr names which row, so ask that one aspect's sub agent to re-emit in the format above and run the merge again — after a second `3`, report the malformed rows verbatim and stop. Completion condition: the merged list is on hand with every location appearing exactly once, or the run ended with 「無發現」 or with the reported error.
6. Report the finding list in Traditional Chinese, one line per merged row. **This skill never modifies code.** `jsc-hooks/hooks/write-guard.sh` in `review` mode enforces that in code: while this skill runs, it blocks write tools. Which tools those are is declared in that script's own header; never restate the list here, because a stale copy of it reads as coverage the guard does not have. Only claude has a `PreToolUse` hook, so codex, copilot, antigravity, and kiro never reach that guard — on those four this rule and the sub agent instructions are the only constraint. The caller decides what to fix.
Then release the review lock: run `jsc-hooks/hooks/write-guard.sh release`, which clears `$JSC_HOME/sessions/{sid}.lastskill`. That file is how the guard recognizes the running skill, and no event tells the guard a skill ended: leave it in place and the caller's first fix — the fix this very report asked for — is blocked by the audit that just finished. State the fallback in the report either way, because an older `jsc-hooks` treats `release` as an unknown mode and exits `0` without clearing anything: the lock then lifts by itself once the file is older than `JSC_WRITE_GUARD_TTL` (900 seconds by default), and `JSC_WRITE_GUARD=off` opens it immediately.
Completion condition: the report is handed to the caller, the release command has been run and the wait plus the `JSC_WRITE_GUARD=off` escape hatch are stated, and the fix decision is left to them.
## Severity
The 高、中、低 scale is the one in `references/smells.md`. Map this skill's findings onto it:
| Finding | Severity |
| --- | --- |
| A returnable status code has no declared response type | 高 |
| A declared response type does not match the body the code returns | 高 |
| An input or output has no description, whatever its type | 中 |
| A scalar input, output, or property has no example | 中 |
| An example sits on a data-model member while its own properties carry none | 中 |
| A data model property is missing from the recursive walk entirely | 中 |
| A placeholder example while real data was reachable | 低 |
| A derived example that is not marked as derived | 低 |
## Notes
- An example on a data-model member is a finding only when that model's properties carry none of their own. That is the example hung on the wrong layer: the caller holds one blob no property definition backs, and the layers below look answered while every one of them is empty. A model whose properties are all covered keeps any extra whole-object example it already has; this skill asks for no such example and reports none.
- Examples taken from a database must be de-identified. Never put personal data into a Swagger document.
- 「推導值」 is the literal marker for a derived example; keep it in the document text so the next reader knows the value was never observed.
- When there are no findings, report the literal 「無發現」 explicitly; never leave the report empty.