diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b813ab9..9d93fdf 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-review", - "version": "0.0.5", + "version": "0.0.6", "description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 5574084..e7b88cd 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-review", - "version": "0.0.5", + "version": "0.0.6", "description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組", "skills": "./skills" } diff --git a/README.md b/README.md index 44a199b..4cf69ab 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 對 git diff 進行六組壞味道審查,每組一個 sub agent 平行執行;回報 `檔案:行號`、嚴重度、建議重構手法,修正與否由呼叫端決定。第 2 組同時擋「文件編號夾帶」:註解只寫「為什麼這樣寫」,議題編號、wiki 頁編號、工作包編號、commit hash、`@` 提及、外部文件連結一律不進註解,清單看 `references/comment-scope.md`。diff 是空的就直接回報「無發現」,不開任何 sub agent;六組全部回覆才進入彙整,沒東西可報的那組也要回「無發現」。安全性與 bug 審查交給 CLI 內建 review,不重複。 +### `api-doc` + +稽核 API 專案的 Swagger 文件:每個可能回傳的 HTTP 狀態碼都要宣告回覆類型,每個輸入與輸出都要有說明與真實資料範例,並沿著巢狀資料模型逐層遞迴。控制器改完或實作完成時執行,例如由 `jsc-sdlc:implement` 呼叫,與 `jsc-review:code-review` 並列為兩關收尾稽核。先跑 `tools/swagger-detect.sh` 偵測,套件與掛接設定要雙重命中才算支援;只裝套件沒掛接就回報未啟用並停手,不開任何 sub agent。原始碼的註解契約歸 `jsc-review:code-review` 第 5 組,這支只管 Swagger 文件屬性與範例,兩支不重複回報。回報 `檔案:行號`、嚴重度與建議修法,本技能不改程式碼。 + ## 參考 @@ -37,6 +41,12 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 | `references/smells.md` | 六組壞味道完整清單:定義、偵測訊號、建議重構手法、嚴重度分級;範例資料必須去識別化 | | `references/comment-scope.md` | 程式碼註解內容界線:禁止寫進註解的文件編號清單、允許項目與白名單、命中時的改法 | +## 工具 + +| 檔案 | 用途 | +| --- | --- | +| `tools/swagger-detect.sh` | 判斷專案有沒有真的啟用 Swagger。套件與設定雙重確認,缺一不算支援。輸出 `support=`、`stack=`、`package=`、`config=`;結束碼 0 支援、1 不支援、2 參數或路徑錯誤 | + ## 相關 domain - [`jsc-sdlc`](https://gitea.jsc.idv.tw/plugins/sdlc):實作階段完成後呼叫本審查 diff --git a/plugin.json b/plugin.json index 78becd3..8bfae14 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-review", - "version": "0.0.5", + "version": "0.0.6", "description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組", "skills": "./skills/" } diff --git a/references/smells.md b/references/smells.md index cc3cf63..443e65a 100644 --- a/references/smells.md +++ b/references/smells.md @@ -122,9 +122,9 @@ ### 5.1 方法描述 -- **定義**:每個公開方法要有一句話描述,並標明所屬層級:**顯示層、邏輯層、存取層**。 -- **偵測訊號**:公開方法無描述;描述未標層級;描述與方法實際行為不符。 -- **建議重構手法**:補上單句描述與層級標記;若一個方法橫跨多層,先依 Split Phase 拆分再各自標註。 +- **定義**:每個公開方法要有一句話描述,並標明所屬層級:**顯示層、邏輯層、存取層**。描述後面要接**條列式的處理步驟與規則**,讀註解就知道這個方法做了哪幾件事、依哪些規則做。 +- **偵測訊號**:公開方法無描述;描述未標層級;描述與方法實際行為不符;描述只有一句話,方法內部卻有多段流程或多條判斷規則,註解裡找不到對應的條列;條列步驟與程式碼實際順序對不上。 +- **建議重構手法**:補上單句描述與層級標記,再把處理流程拆成條列步驟、把判斷條件寫成條列規則;若一個方法橫跨多層,先依 Split Phase 拆分再各自標註;條列長到十項以上,代表方法本身太肥,同時列 1.2 並建議 Extract Function。 ### 5.2 輸入參數說明 @@ -134,9 +134,9 @@ ### 5.3 輸出說明 -- **定義**:輸出(回傳值)要說明回傳的資料內容概要。 -- **偵測訊號**:無回傳說明;未說明 null、空集合、錯誤時的回傳;回傳布林但未說明 true/false 意義。 -- **建議重構手法**:補回傳內容概要與邊界情況說明。 +- **定義**:輸出(回傳值)要說明回傳的資料內容概要。回傳值是自訂資料模型,而註解格式支援導向時(XML 的 ``、JSDoc 的 `{@link}`),說明要附上該資料模型的檔案連結,讀的人一鍵就跳到定義。 +- **偵測訊號**:無回傳說明;未說明 null、空集合、錯誤時的回傳;回傳布林但未說明 true/false 意義;回傳自訂資料模型卻只寫型別名稱純文字,沒有 `` 或 `{@link}` 導向。 +- **建議重構手法**:補回傳內容概要與邊界情況說明;自訂資料模型改用註解格式的導向標籤指到型別定義,泛型集合指到元素型別。 ### 5.4 輸入與輸出範例 @@ -150,6 +150,35 @@ - **偵測訊號**:DTO/ViewModel 僅頂層有註解;內層類別、集合元素型別的欄位無說明或無範例。 - **建議重構手法**:逐層補齊 5.1–5.4;巢狀過深(≥ 3 層)時同時評估 Extract Class 是否被濫用。 +### 5.6 內含功能的導向連結 + +- **定義**:一個功能內部呼叫了其他功能時,註解要交代呼叫了誰、為什麼呼叫。註解格式支援導向就用導向標籤(XML 的 ``、JSDoc 的 `{@link}`),讓 IDE 直接跳過去。 +- **偵測訊號**:方法內呼叫其他公開方法或服務,註解卻完全沒提;提了但只寫純文字方法名,沒有 `` 或 `{@link}`;寫了連結卻沒寫用途,讀的人還是要自己點進去猜。 +- **建議重構手法**:在條列步驟裡把被呼叫的功能改成導向標籤,後面接一句用途;被呼叫的功能多到列不完,代表這個方法在當協調中心,同時列 1.2 或 3.1 評估職責搬移。 +- **注意**:註解格式不支援導向(例如純 `//` 行註解)就只寫用途,不硬造連結字串。 + +### 5.7 XML 註解標籤排版 + +- **定義**:註解採 XML 格式時,起始標籤與結束標籤**各自獨立一行**,內容夾在中間。這樣多行內容、條列與巢狀標籤才排得整齊,diff 也只動到真正改的那幾行。 +- **偵測訊號**:起始標籤、內容、結束標籤擠在同一行(例:`/// 取得訂單`);結束標籤跟在內容尾巴後面沒有換行(例:`/// 取得訂單`);``、``、``、`` 同樣擠成一行;巢狀標籤(`` 內含 ``)沒有逐層換行與縮排。 +- **建議重構手法**:把每個標籤拆成三行——起始標籤一行、內容一行或多行、結束標籤一行;巢狀標籤逐層縮排;`` 這類帶屬性的標籤,屬性留在起始標籤那一行。 +- **注意**:只針對 XML 格式註解。JSDoc、docstring 沒有結束標籤,不適用本項。 + +### 5.8 專有名詞與變數標示 + +- **定義**:註解裡提到程式碼元素時,要用該語言註解格式的標示語法標起來,不能混在純文字裡。標示的目的是讓 IDE 與文件產生工具讀得懂,並在文件上呈現成程式碼樣式。 +- **偵測訊號**:XML 註解裡直接以純文字寫參數名、型別名、成員名或程式碼字面;JSDoc 或 docstring 裡以純文字寫變數名與型別名,沒有加反引號;標錯類別(例如拿 `` 標參數名、拿 `` 標型別)。 +- **建議重構手法**:依語言慣例補標示。 + + | 註解格式 | 對象 | 標示語法 | + | --- | --- | --- | + | XML | 變數、參數 | `` | + | XML | 型別、成員 | `` | + | XML | 程式碼字面、列舉值 | `null` | + | JSDoc、docstring | 變數、型別、程式碼字面 | 反引號 `` `orderId` `` | + +- **注意**:標示規則跟著語言慣例走,不是跟著個人喜好走。IDE 的跳轉與文件工具的交叉引用都靠這些標籤,標錯等於沒標。 + ## 第 6 組:淺模組(Shallow Module) - **定義**:介面複雜度相對於功能深度過高的模組——使用它要懂的事,跟自己寫差不多(出自《A Philosophy of Software Design》:好模組要「介面簡單、實作深」)。 @@ -162,4 +191,14 @@ | --- | --- | --- | | 高 | 會造成錯誤或已阻礙修改 | 吞掉異常、重複程式碼改漏、死碼誤導 | | 中 | 持續增加維護成本 | 巨型類別、臃腫函式、巢狀地獄、Couplers 全組 | -| 低 | 可讀性與一致性 | 命名、魔術數字、註解缺漏、文件編號夾帶、淺模組 | +| 低 | 可讀性與一致性 | 命名、魔術數字、註解缺漏、文件編號夾帶、淺模組、XML 標籤排版、專有名詞未標示 | + +第 5 組新增項目的級別另外標明,避免全部壓在「註解缺漏」一句話裡: + +| 項目 | 級別 | 理由 | +| --- | --- | --- | +| 5.1 缺條列式步驟與規則 | 中 | 讀的人要重讀整個實作才知道規則,維護成本直接上升 | +| 5.3 回傳值缺資料模型連結 | 低 | 型別名稱還查得到,只是多花幾秒 | +| 5.6 缺內含功能的導向連結 | 低 | 同上,影響的是查找速度 | +| 5.7 XML 標籤排版擠在同一行 | 低 | 純排版,不影響語意 | +| 5.8 專有名詞未標示或標錯 | 低 | 影響 IDE 跳轉與文件呈現,不影響執行 | diff --git a/skills/api-doc/SKILL.md b/skills/api-doc/SKILL.md new file mode 100644 index 0000000..d2135ed --- /dev/null +++ b/skills/api-doc/SKILL.md @@ -0,0 +1,58 @@ +--- +name: api-doc +description: Audit an API project's Swagger/OpenAPI documentation: every returnable HTTP status code declares a response type, and every input and output carries a description plus a real data example, recursively down nested data models. 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` bad argument or missing path. 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 error message verbatim and stop; the caller supplies a valid project path and re-runs. Completion condition: the run has ended with an honest reason, 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. Completion condition: the controller list is non-empty and reported; an empty list ends the run with the literal 「無發現」. +4. Audit in three aspects, and every aspect **MUST run as a sub agent**; the three 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 Parameter description and example | Every input parameter and every output field has a description and an example. Examples come from real project data first — query the project's database, seed data, or fixtures. Only when real data is unreachable, derive an example by logic and mark it as a derived value in the document itself | + | 3 Recursive data model | When a parameter or return value is a data model, aspect 2 applies to every one of its properties. A model containing another model recurses to the innermost layer. Walk in from the controller and follow the input and output types down | + + Instructions for each sub agent: read only, change nothing; report each finding as `file:line`, the aspect, severity, one sentence of evidence, and the concrete fix (which attribute to add, on which member). Findings are reported in Traditional Chinese. + + Completion condition: all three aspects have returned — an aspect with nothing to report still returns 「無發現」 for itself. +5. Merge the three aspects' findings: deduplicate by location, then sort by severity. Start this step only once all three have returned. Completion condition: every finding appears exactly once, ordered 高 → 中 → 低. +6. Report the finding list. **This skill never modifies code**; the caller decides what to fix. Completion condition: the report is handed to the caller 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 | 中 | +| 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 + +- 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. diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md index b8a472f..fdb9cff 100644 --- a/skills/code-review/SKILL.md +++ b/skills/code-review/SKILL.md @@ -16,6 +16,7 @@ Review changed code against `references/smells.md` (from the book *Refactoring*) - This skill covers the *Refactoring* smells, the comment contract (group 5), and shallow modules (group 6). - Security, logic bugs, and test coverage belong to the CLI's built-in review (e.g. claude's `/security-review`); do not duplicate them. +- Swagger document auditing belongs to `jsc-review:api-doc`: response type declarations per HTTP status code, Swagger parameter descriptions, and Swagger examples down the nested data models. Group 5 here covers the source comment contract only; the two never report the same gap twice. ## Steps @@ -28,7 +29,7 @@ Review changed code against `references/smells.md` (from the book *Refactoring*) | 2 Obscurity | smells.md group 2, including 2.5 document reference leak — comments carrying issue ids, wiki page ids, work package ids, commit hashes, @ mentions, or external document links; the full banned and allowed lists live in `references/comment-scope.md` | | 3 Couplers | smells.md group 3 | | 4 Dispensables & Others | smells.md group 4 | - | 5 Comment contract | smells.md group 5 | + | 5 Comment contract | smells.md group 5, 5.1 to 5.8 — including 5.6 navigation links to the functions a method calls, 5.7 XML comment tag layout (opening and closing tag each on its own line), and 5.8 code-element markup by language convention (``, ``, `` in XML; backticks in JSDoc and docstrings) | | 6 Shallow Module | smells.md group 6 | Instructions for each sub agent: read only, change nothing; check every changed line and its enclosing function or class against the group's definitions and detection signals in smells.md; report each finding as `file:line`, smell name, severity (高、中、低 per the smells.md scale), one sentence of evidence, and the suggested refactoring. Findings are reported in Traditional Chinese. diff --git a/tools/swagger-detect.sh b/tools/swagger-detect.sh new file mode 100755 index 0000000..0bdee25 --- /dev/null +++ b/tools/swagger-detect.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env sh +# swagger-detect.sh — 判斷一個專案有沒有真的啟用 Swagger(OpenAPI)文件。 +# 用法:swagger-detect.sh [專案路徑](預設目前目錄) +# +# 規則(雙重確認,缺一不算支援): +# 1. 套件:在套件宣告檔裡找得到已知的 Swagger 套件。 +# 2. 設定:在原始碼裡找得到實際把 Swagger 接上去的呼叫或裝飾子。 +# 只找到套件不算支援。裝了沒啟用的專案很常見,只看套件會誤判, +# 讓 api-doc 稽核跑在一個根本產不出文件的專案上。 +# 設定關鍵字一律挑「接線動作」,不挑 import 或 require。光是引入套件 +# 不代表文件真的掛上去了。 +# +# 涵蓋範圍: +# dotnet 套件 Swashbuckle.AspNetCore、NSwag.AspNetCore +# 設定 AddSwaggerGen、UseSwagger、AddOpenApiDocument、UseOpenApi +# nodejs 套件 swagger-ui-express、@nestjs/swagger、fastify-swagger(含 @fastify/swagger) +# 設定 SwaggerModule.setup、swaggerUi.setup、swaggerUi.serve、 +# register 進 fastify 的 swagger 外掛 +# python 套件 fastapi、flasgger、drf-spectacular +# 設定 FastAPI( 建立 app、Swagger( 掛上 flasgger、SPECTACULAR_SETTINGS、 +# SpectacularAPIView +# +# 輸出(純文字,一行一個 key=value,呼叫端逐行讀就好): +# 第一行永遠是 support=yes 或 support=no。 +# 之後每一組偵測到套件的技術棧輸出三行: +# stack=dotnet|nodejs|python +# package=命中的套件名,多個以半形逗號相連 +# config=命中的設定關鍵字,多個以半形逗號相連;沒命中就是空字串 +# 一個套件都沒找到時只有 support=no 一行。 +# 範例: +# support=yes +# stack=dotnet +# package=Swashbuckle.AspNetCore +# config=AddSwaggerGen,UseSwagger +# +# 結束碼: +# 0 支援(至少一個技術棧同時命中套件與設定) +# 1 不支援(沒有任何技術棧同時命中) +# 2 參數個數不對、專案路徑不存在,或環境缺 grep +# +# 護欄: +# 掃描一律跳過 node_modules、.git、bin、obj、dist、build、venv、.venv、 +# __pycache__、packages、vendor,避免把相依套件自己的原始碼當成專案設定。 +# 錯誤訊息一律印繁中到 stderr,正常輸出只走 stdout。 +set -u + +if [ "$#" -gt 1 ]; then + echo "用法:swagger-detect.sh [專案路徑]" >&2 + exit 2 +fi + +DIR="${1:-.}" + +[ -d "$DIR" ] || { echo "錯誤:找不到專案路徑 $DIR。請確認路徑後重試。" >&2; exit 2; } +command -v grep >/dev/null 2>&1 || { echo "錯誤:環境缺 grep,無法掃描。" >&2; exit 2; } + +EX1=--exclude-dir=node_modules +EX2=--exclude-dir=.git +EX3=--exclude-dir=bin +EX4=--exclude-dir=obj +EX5=--exclude-dir=dist +EX6=--exclude-dir=build +EX7=--exclude-dir=venv +EX8=--exclude-dir=.venv +EX9=--exclude-dir=__pycache__ +EX10=--exclude-dir=packages +EX11=--exclude-dir=vendor + +# scan <延伸正規表示式> <副檔名樣式…>:命中回 0,沒命中回 1。 +scan() { + pattern="$1" + shift + for inc in "$@"; do + if grep -R -l -E -i \ + "$EX1" "$EX2" "$EX3" "$EX4" "$EX5" "$EX6" \ + "$EX7" "$EX8" "$EX9" "$EX10" "$EX11" \ + --include="$inc" -e "$pattern" "$DIR" >/dev/null 2>&1; then + return 0 + fi + done + return 1 +} + +# append <既有清單> <新項目>:以半形逗號相連後印出。 +append() { + if [ -z "$1" ]; then + printf '%s' "$2" + else + printf '%s,%s' "$1" "$2" + fi +} + +SUPPORT=no +RECORDS="" + +# collect <技術棧> <套件清單> <設定清單>:有套件才留紀錄,兩者都有才算支援。 +collect() { + [ -n "$2" ] || return 0 + RECORDS="${RECORDS}stack=$1 +package=$2 +config=$3 +" + [ -n "$3" ] && SUPPORT=yes + return 0 +} + +# ── dotnet ──────────────────────────────────────────────────────────────── +DOTNET_PKG="" +DOTNET_CFG="" +for pkg in 'Swashbuckle\.AspNetCore:Swashbuckle.AspNetCore' 'NSwag\.AspNetCore:NSwag.AspNetCore'; do + if scan "${pkg%%:*}" '*.csproj' '*.fsproj' '*.vbproj' '*.props' 'packages.config'; then + DOTNET_PKG=$(append "$DOTNET_PKG" "${pkg##*:}") + fi +done +if [ -n "$DOTNET_PKG" ]; then + for cfg in AddSwaggerGen UseSwagger AddOpenApiDocument UseOpenApi; do + if scan "$cfg" '*.cs' '*.fs' '*.vb'; then + DOTNET_CFG=$(append "$DOTNET_CFG" "$cfg") + fi + done +fi +collect dotnet "$DOTNET_PKG" "$DOTNET_CFG" + +# ── nodejs ──────────────────────────────────────────────────────────────── +NODE_PKG="" +NODE_CFG="" +for pkg in swagger-ui-express @nestjs/swagger fastify-swagger @fastify/swagger; do + if scan "\"$pkg\"" 'package.json'; then + NODE_PKG=$(append "$NODE_PKG" "$pkg") + fi +done +if [ -n "$NODE_PKG" ]; then + # 每一項都是「接線動作」:掛路由或註冊外掛,不是 import。 + for cfg in 'SwaggerModule\.setup:SwaggerModule.setup' \ + 'swaggerUi\.setup:swaggerUi.setup' \ + 'swaggerUi\.serve:swaggerUi.serve' \ + 'register\(.*swagger:register(swagger)'; do + if scan "${cfg%%:*}" '*.js' '*.mjs' '*.cjs' '*.ts'; then + NODE_CFG=$(append "$NODE_CFG" "${cfg##*:}") + fi + done +fi +collect nodejs "$NODE_PKG" "$NODE_CFG" + +# ── python ──────────────────────────────────────────────────────────────── +PY_PKG="" +PY_CFG="" +for pkg in fastapi flasgger drf-spectacular; do + if scan "$pkg" 'requirements*.txt' 'pyproject.toml' 'Pipfile' 'setup.py' 'setup.cfg'; then + PY_PKG=$(append "$PY_PKG" "$pkg") + fi +done +if [ -n "$PY_PKG" ]; then + for cfg in 'FastAPI\(:FastAPI(' \ + 'Swagger\(:Swagger(' \ + 'SPECTACULAR_SETTINGS:SPECTACULAR_SETTINGS' \ + 'SpectacularAPIView:SpectacularAPIView'; do + if scan "${cfg%%:*}" '*.py'; then + PY_CFG=$(append "$PY_CFG" "${cfg##*:}") + fi + done +fi +collect python "$PY_PKG" "$PY_CFG" + +# ── 輸出 ────────────────────────────────────────────────────────────────── +echo "support=$SUPPORT" +[ -n "$RECORDS" ] && printf '%s' "$RECORDS" + +[ "$SUPPORT" = yes ] && exit 0 +exit 1