---
title: "Sumitsubo：用文件來檢查實作"
date: 2026-09-02T00:00:00+08:00
publishDate: 2026-09-02T00:00:00+08:00
lastmod: 2026-08-31T20:59:50+08:00
tags: ["LLM","AI","經驗","Ruby","SDD"]
toc: true
permalink: "https://blog.aotoki.me/posts/2026/09/02/sumitsubo-verify-with-document/"
language: "zh-tw"
---


[Sumitsubo](https://github.com/elct9620/sumitsubo) 是我用來將「規格」變成實際可驗證程式碼的工具，在 [Sumitsubo：把規格做成 Linter](https://blog.aotoki.me/posts/2026/08/26/sumitsubo-spec-as-linter/) 的初期試做中採用了 JSON 來描述規格，然而在調查有沒有相似工具時，意外發現直接讓 Markdown 變成結構化的資訊來源，就可以用寫文件的方式來設計工具。

<!--more-->

## 結構化資料{#structured-data}

最初選擇 JSON 的考量有很多，也包括 LLM（大型語言模型）很擅長維護這類型的檔案格式，同時需要有結構化資料才能做機械式的處理，但我卻忘記 Markdown 本身也具備這樣的特性。

舉例來說，使用 `#` 和 `##` 就可以表達深度，如果用 `\n\n`（兩次換行）就可以表達一個文字段落，基本上每一段都是可被解析的，這也是為什麼可以很輕鬆的將 Markdown 轉換成網頁，更好的是語法非常的少，解析起來並不困難。

也因此，我又花了一週的時間，將原本使用 JSON 定義規格改為使用 Markdown 並且移除 JSON 渲染為 Markdown 的能力，因為現在不需要兩者共存，只需要有 Markdown 就可以完整的表達我原本想達到的規格資訊。

但是也有新的困難出現，解析後的 Markdown 都是相同的格式，要怎麼表達不同規格上的需求呢？

## 抽象化設計{#abstraction}

要解決 Markdown 撰寫結構化的規格，比預想中還算簡單。實際上就是軟體開發常做的「抽象化」處理，如果我們能把規格轉換成某個概念，那麼應該有一種資料型態可以把所有能檢查的規格統一的描述。

在確認概念時，意外的發現 [ReqIf](https://en.wikipedia.org/wiki/Requirements_Interchange_Format) 這個標準，完整的名字是 Requirements Interchange Format 簡單說就是用來交換「需求」的標準格式，而這個需求剛好就是 PRD（Product Requirement Document）的需求，可以看作是規格文件中的一種類型。

也就是說，把規格用結構化的資料表示並不是一個全新的概念，只是以往很少被拿來設計成檢查工具（Linter）而已，在 Sumitsubo 則借用 ReqIf 的概念，把規格定義為

- `Specification` - 一個規格檔案
- `Statement` - 一條規格項目，可以巢狀表示

至於如何巢狀，只要簡單地用 `#` 和 `##` 就可以區分出來，而且 Markdown 幾乎是天然的支援這樣的設計，把標題當作識別（Identity）也非常自然，因此就可以寫出像是這樣的規格。

```md
## `MD-001` 解析 Markdown 規格

當使用者提供符合條件的 .md 檔案時，則解析成結構化的規格。

| Given | 當 .spec/glossary.md 存在時 |
| When  | 使用者呼叫 `sumi verify`    |
| Then  | 回傳 `exit 0`              |
```

因為撰寫跟維護都非常容易，而且能很好的傳遞行為的意圖，對 AI 在參考規格實作時，就可以減少漏掉某條需求的問題，對人來說也算是相當好維護的。

## 語法樹比較{#compare-syntax-tree}

會轉換成 Markdown 實際上是因為語法樹（Syntax Tree）的需求，透過 JSON 表達一個介面契約（Contract）的時候，很難滿足不同語言的需要，這也讓使用 [Tree Sitter](https://tree-sitter.github.io/tree-sitter/) 支援多語言的特性變得意義不大。

幸好已經有像是 [Semgrep](https://semgrep.dev/) 和 [ast-grep](https://github.com/ast-grep/ast-grep) 這樣使用 Tree Sitter 的工具給出例子，透過 Tree Sitter 是可以用來「尋找某段符合條件的程式碼」這樣的目的，也因此就可以利用這個特性，來限制 AI 在實作時，可以穩定的定義某些公開方法，而不是隨時在改變。

````md
## `Sumitsubo::Place.of`

產生一個 Place 物件，用來表示某一個規格所定義的位置。

```ruby
class Sumitsubo::Place
  def self.of(path, line)
  end
end
```
````

像這個樣子，我就可以要求在搜尋範圍內的檔案中，必須要有一個 `Place` 物件，並且具備一個 Class Method 叫做 `of(path, line)` 如何實作的不重要，但是我必須看到這樣的程式碼存在。

當某一天 AI 在修改時破壞了這個約定，就會在 `sumi verify` 看到這樣的訊息

```bash
$ sumi verify
.spec/contract/place.md:3 ruby Sumitsubo::Place.of is defined nowhere this specification includes, and one the reading cannot see never is
1 difference
```

來告訴 AI 這個是規格上的約定不該去修改，那麼在驗證 AI 生成的實作，所需要確認的就可以縮減到文件的範圍，而不是在整個專案層級中檢查跟搜尋。

很多時候，我們關心的是公開方法跟行為，內部的私有方法本身是被封裝的時候不一定是最優先要確認的，在 AI 大量產生程式碼的時代，檢查就會更加的吃力，但有這樣的機制做防護，除錯的範圍就會變小，也更容易溝通。

現階段 Sumitsubo 的功能還處於很初期的階段，但我相信未來應該也會陸續出現這類型的工具，來應對未來軟體開發的需求，不單純是測試、語法檢查，可能也會逐漸把規格（意圖）和實作對齊，即使是像 Sumitsubo 這樣只能檢查少部分，但也會比完全不檢查或者難以檢查再更好一些。
