- 使用者要求改判準。類別型參數與中間層的類別屬性只留說明,範例責任往下推給屬性,一路走到最內層的純量。 - 一個值只有一個出處。範例掛在父層,屬性一改就過期,讀的人拿到的是沒有屬性定義背書的一份資料。 - 集合看元素型別,不看外殼;字典看值型別。字串集合與字串判出來一樣,位址集合與位址判出來也一樣。 - 壞味道清單原本寫「輸入與輸出參數都必須有範例」,與新判準衝突,兩支技能會對同一段程式碼給出不同標準。一併改成同一套。 - 說明與範例的兩份檢核表併成一個 sub agent,同一批資料模型檔只讀一次。 - 順帶補上偵測腳本結束碼二漏掉的一個原因:環境缺 grep。原本只寫參數與路徑,遇到這個原因換路徑重跑永遠清不掉。
12 KiB
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
- After controller work is complete: one controller or one related group of controllers is done.
- 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 tojsc-review:code-review. - 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-reviewgroup 5 (references/smells.md5.1 to 5.8). Never report the same gap twice; when a nested model already fails 5.5, leave it tocode-review. - Security, logic bugs, and test coverage belong to the CLI's built-in review.
Steps
-
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:0supported;1unsupported;2one of three causes — more than one argument, a project path that does not exist, or nogrepin the environment. Completion condition: the exit code and thesupport=,stack=,package=,config=lines are captured. -
On exit code
1, report the literal 「本專案未啟用 Swagger 文件,略過 API 文件稽核」 plus thestack=andpackage=lines the script printed, and stop. Spawn no sub agent. On exit code2, 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 thatgrepmust 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 code0moved it to step 3. -
List the audit scope: every controller in the project, or only the controllers touched by
git diffwhen the caller asked for a scoped run. On a scoped run, read git's exit code:0the 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-reviewstep 1 stops on the same failure, so both skills answer a brokengit diffthe 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. -
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, FastAPIresponses=, 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 produce2 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>andstringcome out the same andList<Address>andAddresscome 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 below4 group the aspect: 1status codes,2Adescription and example,2Brecursive walk5 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.shdeduplicates 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 「無發現」.
-
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:0take its stdout as the merged list — deduplicated by location and sorted by severity, 高 first and 低 last;1no aspect returned a row, so report the literal 「無發現」 and stop;2environment or input error, so report the script's stderr verbatim and stop, and never hand-merge as a substitute;3an 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 second3, 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. -
Report the finding list in Traditional Chinese, one line per merged row. This skill never modifies code.
jsc-hooks/hooks/write-guard.shinreviewmode 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 aPreToolUsehook, 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 olderjsc-hookstreatsreleaseas an unknown mode and exits0without clearing anything: the lock then lifts by itself once the file is older thanJSC_WRITE_GUARD_TTL(900 seconds by default), andJSC_WRITE_GUARD=offopens 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=offescape 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.