29 天下來,SRS Review Agent 有了四層檢測、審查報告、命令列工具、Web 介面、Docker 部署,以及 Day 28-29 用測試守住的使用文件與 API 文件。最後一天要做兩件事:
pip install 的套件設定,並把版本號定為 1.0.0第 4 週:發佈與開源
讀完這篇後,你會完成:
README.md、LICENSE、CONTRIBUTING.md、CHANGELOG.md、.gitignore
pyproject.toml:pip install . 之後就有 srs-review 指令tests/test_day30_release.py:6 個測試別人第一次看到這個專案時,會依序問:
| 問題 | 回答的檔案 |
|---|---|
| 這是什麼?做得好嗎? | README.md |
| 我可以用在公司的專案嗎? | LICENSE |
| 怎麼安裝? | README.md → docs/USAGE.md、pyproject.toml |
| 這一版改了什麼?有什麼已知問題? | CHANGELOG.md |
| 我想改進它,要注意什麼? | CONTRIBUTING.md |
還有一個看不見、但最重要的問題:不該公開的東西有沒有被一起放上去。這個專案的資料夾裡有 .env、開發用的資料庫 test.db(裡面有測試帳號)、Day 12 的 LLM 緩存。.gitignore 必須在第一次提交之前就寫好,一旦推上 GitHub,就算之後刪除,歷史紀錄裡也還找得到。
src/__init__.py:__version__ = "1.0.0" ← Day 29 建立的唯一版本來源
├─ API(/、/api/v1/health、/api/v1/info、OpenAPI)
├─ CLI(srs-review --version)
├─ pyproject.toml(dynamic version)
└─ CHANGELOG.md 最新的版本(由測試檢查)
README.md LICENSE CONTRIBUTING.md CHANGELOG.md .gitignore pyproject.toml
↓
tests/test_day30_release.py:版本一致、套件設定、README 連結、必要檔案、.gitignore
README.md、LICENSE、CONTRIBUTING.md、CHANGELOG.md、.gitignore、pyproject.toml、tests/test_day30_release.py(新增);src/__init__.py、docs/api/openapi.json(修改)專案結構變化:
srs-review-agent/
├── README.md ← 【新增】
├── LICENSE ← 【新增】MIT
├── CONTRIBUTING.md ← 【新增】
├── CHANGELOG.md ← 【新增】
├── .gitignore ← 【新增】
├── pyproject.toml ← 【新增】
├── src/__init__.py ← 修改:1.0.0
└── tests/
└── test_day30_release.py ← 【新增】6 個測試
README.md 的開頭用一個例子說明系統在做什麼,接著是快速開始。比較特別的是「準確度與限制」這一段:
## 準確度與限制
在小型、刻意放入衝突的資料集上(3 份 SRS、15 個標準答案;Mistral 7B):
| 設定 | Precision | Recall | F1 |
| v1(規則 + 驗證 + 補充) | 0.905 | 0.722 | 0.767 |
| v1 驗證 + v2 補充 + guardrail(預設) | 0.905 | 0.917 | **0.897** |
**這個數字不代表在任意規格書上的表現。** 在較大、衝突較少的規格書上,品質會明顯下降:
- 58 條需求的真實規格書:LLM 補充層回報 17 個衝突,全部是誤報
- 合成規格書(Day 27):100 條需求時 F1 約 0.24;…
把 F1 0.897 放在 README 最顯眼的地方很容易,但 Day 24 與 Day 27 已經證明,這個數字只在小型、刻意放入衝突的資料集上成立。使用者拿真實的規格書來試,第一次就會遇到大量誤報。與其讓他們自己發現,不如在 README 就寫清楚,並告訴他們該怎麼用(--no-supplement、把結果當成線索)。
# 秘密與本機設定(.env.example 要提交)
.env
# 資料庫與緩存(含帳號與檢測結果)
*.db
.cache/
# 產生的報告(reports/ 中的實驗報告要提交)
*.report.pdf
*.report.md
# 前端
frontend/node_modules/
frontend/dist/
三個刻意的區分:
.env 不提交,.env.example 要提交:範本告訴別人需要哪些設定,真正的值留在本機reports/ 中 Day 18-27 的實驗報告要提交:那是文章中所有數字的出處;CLI 產生的 *.report.pdf 則不提交*.db 一律不提交:test.db 裡有開發時註冊的測試帳號與密碼雜湊建立 pyproject.toml:
[project]
name = "srs-review-agent"
dynamic = ["version"]
requires-python = ">=3.12"
license = { text = "MIT" }
dependencies = [
"fastapi>=0.104.0",
"uvicorn>=0.24.0",
...
"reportlab>=4.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0"]
[project.scripts]
srs-review = "src.cli:main"
[tool.setuptools]
packages = ["src"]
[tool.setuptools.dynamic]
version = { attr = "src.__version__" }
dynamic = ["version"]:版本號從 src.__version__ 讀取,不在這裡再寫一次。Day 29 統一了 API 與 CLI 的版本來源,現在套件也加入[project.scripts]:安裝後產生 srs-review 指令,指向 Day 24 的 main()。python3 -m src.cli 依然可以用dev 額外依賴:pytest 只有開發時需要,pip install -e ".[dev]" 才會安裝requirements.txt 繼續保留:Day 25 的 Dockerfile 與既有的教學都用它。
只改一行:
# 單一版本來源:API(/、/api/v1/health、/api/v1/info、OpenAPI)、CLI(--version)
# 與 pyproject.toml(dynamic version)共用(Day 29-30)
__version__ = "1.0.0" # Day 30:第一個公開版本
改完之後,Day 29 的 test_openapi_file_up_to_date 會立刻失敗:OpenAPI 規格裡的版本號變了,docs/api/openapi.json 卻還是 0.29.0。執行一次 python3 -m src.export_openapi 就好。這正是 Day 29 寫那個測試的目的。
建立 tests/test_day30_release.py:
def test_changelog_matches_version():
"""測試 1:CHANGELOG 最新的版本就是 __version__"""
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
latest = re.search(r"^## \[(\d+\.\d+\.\d+)\]", changelog, re.M).group(1)
assert latest == __version__, f"CHANGELOG 最新版本 {latest},程式是 {__version__}"
def test_gitignore_protects_secrets():
"""測試 5:.env、資料庫、緩存、node_modules 不會被提交;.env.example 會"""
ignored = (ROOT / ".gitignore").read_text(encoding="utf-8").split()
for pattern in [".env", "*.db", ".cache/", "frontend/node_modules/", ".venv/"]:
assert pattern in ignored, f".gitignore 缺少 {pattern}"
assert ".env.example" not in ignored
另外 4 個測試檢查 pyproject.toml(動態版本、srs-review 指令、必要的相依套件)、README 的相對連結都存在、開源專案的基本檔案齊全、srs-review --version 的輸出。
重建 1.0.0 的映像檔,在容器裡跑測試時出現 2 個錯誤:Day 28、29 的文件測試找不到 docs/USAGE.md。原因是 Day 25 的 .dockerignore 刻意排除了 docs/,映像檔裡本來就不該有文件。
這三組測試(Day 28-30)檢查的是 repo 是否一致,不是執行環境能不能運作,所以在文件不存在時略過:
# 這是 repo 層級的檢查:Docker 映像檔依 .dockerignore 不含 docs/、examples/,在那裡略過(Day 30)
if not (ROOT / "docs" / "USAGE.md").exists():
pytest.skip("文件不在此環境中(例如 Docker 映像檔)", allow_module_level=True)
cd srs-review-agent
python3 -m pytest tests/ -q
135 passed in 4.68s
容器裡(Python 3.12):
docker build -t srs-review-agent:1.0.0 .
docker run --rm srs-review-agent:1.0.0 python -m pytest tests/ -q
# 117 passed, 3 skipped
docker run --rm srs-review-agent:1.0.0 python -m src.cli --version
# srs-review 1.0.0
略過的 3 個是 Day 28-30 的文件與發佈檢查(共 18 個測試),135 − 18 = 117。
python3 -m venv /tmp/fresh && /tmp/fresh/bin/pip install .
/tmp/fresh/bin/srs-review --version
# srs-review 1.0.0
/tmp/fresh/bin/srs-review analyze tests/fixtures/srs_small.md -o out.md
# 9 條需求,1 個衝突(高 1、中 0、低 0),待複核 1 個
這個資料夾目前還不是 git 倉庫。發佈的步驟如下(請在確認 .gitignore 後再執行):
cd srs-review-agent
git init
git add .
git status # 確認沒有 .env、test.db、.cache/、node_modules/
git commit -m "SRS Review Agent 1.0.0"
git tag v1.0.0
# 在 GitHub 建立空的倉庫後
git remote add origin git@github.com:<帳號>/srs-review-agent.git
git push -u origin main --tags
git status 那一步不要省略:.gitignore 寫錯一個字,秘密就會被提交。推送之後,git tag v1.0.0 對應到 CHANGELOG.md 的 [1.0.0]。
| 階段 | 做法 | Day 7-10 資料集 F1 |
|---|---|---|
| Day 7 | 關鍵字規則(基線) | 0.789 |
| Day 8-10 | 擴展規則、語義相似度、混合 | 0.468 / 0.524 / 0.388 |
| Day 11 | Mistral 7B 驗證 + 補充 | 0.714 |
| Day 12 | 批量驗證 + 緩存 | 0.767 |
| Day 19 | 補充 prompt v2 | 0.659(被 JSON 錯誤抵銷) |
| Day 20 | + 輸出 guardrail | 0.897 |
| Day 21 | + 自我矛盾檢查 | 0.861(預設不開) |
], 吃掉了OLLAMA_NUM_CTX 時立刻抓到文件漏寫git add README.md LICENSE CONTRIBUTING.md CHANGELOG.md .gitignore pyproject.toml \
src/__init__.py docs/api/openapi.json tests/
git commit -m "Day 30: 1.0.0 發佈(README、授權、貢獻指南、版本紀錄、套件設定)"
30 天前,Day 1 的問題是:「能不能讓 Agent 幫忙找出規格書裡的矛盾?」現在的答案是:在小型規格書上可以,而且能找到規則找不到的衝突;在大型規格書上還不行,但我們知道確切的原因。
這個系列留下的不只是一個工具,還有一套做法:先建立評測、固定資料、逐筆分析失敗、一次只改一件事、用 A/B 決定要不要採用、讓測試守住文件。這套做法不依賴 Mistral,也不依賴這個題目,換一個模型、換一個領域都能沿用。
謝謝你讀到最後。