# Contributing to arcrun

感謝你考慮貢獻 arcrun！本文件說明如何新增零件（WASM component）並提交至公眾零件庫。

arcrun 的零件**主要由 AI 撰寫**。你不需要是 TinyGo 或 AssemblyScript 專家，只需要把這份文件和你的 API 文件或需求貼給 AI，讓它生成源碼，你負責編譯、測試、提交。

---

## 選擇開發語言

零件只需要輸出符合 **WASI preview1** 的 `.wasm` 檔案，與使用哪個語言無關。

| 語言 | 輸出大小 | AI 撰寫品質 | 說明 |
|------|---------|------------|------|
| **TinyGo** | 極小（10–80KB） | 優秀 | 官方零件使用；語法簡單，AI 出錯率低 |
| **AssemblyScript** | 小（20–150KB） | 良好 | TypeScript 語法，前端開發者最快上手 |
| **Rust** | 小–中（30–300KB） | 良好 | 效能最強；適合複雜演算法，工具鏈稍複雜 |

**AI 開發建議：**
- 選 **TinyGo**：Go 語法與 TypeScript 差異夠大，AI 不易把 TS 邏輯直接搬過來造成錯誤，是最穩的選擇。
- 選 **AssemblyScript**：適合已熟悉 TypeScript 的開發者，但要注意 AS 不是 TS — 提示 AI 時明確說「AssemblyScript，不是 TypeScript」。
- 選 **Rust**：效能要求高時使用；需要更詳細的提示和更仔細的審查。

---

## 零件規格：共通規則

無論使用哪個語言，零件必須遵守：

- **I/O 模型**：從 `stdin` 讀取 JSON，往 `stdout` 輸出 JSON，不使用 return value
- **回傳格式**：成功 `{"success":true,"result":...}`，失敗 `{"success":false,"error":"..."}`
- **不 panic**：任何錯誤都應輸出 `success:false` JSON，不讓執行器收到空輸出
- **不使用網路 / 檔案系統**（功能類零件）：`no_network_syscall: true`
- **允許網路**（整合類零件）：`no_network_syscall: false`，必須宣告 `credentials_required`

---

## 目錄結構

```
registry/components/my_component/
├── component.contract.yaml   # 零件規格宣告（必填）
├── main.go                   # TinyGo 源碼（TinyGo 零件）
├── assembly/index.ts         # AssemblyScript 源碼（AS 零件）
├── src/lib.rs                # Rust 源碼（Rust 零件）
└── my_component.wasm         # 編譯產出（不提交至 git，CI 自動產生）
```

---

## component.contract.yaml

所有語言共用相同的合約格式：

```yaml
# component_hash_id 由 Registry 在提交時自動派發，格式為 cmp_{8碼hex}
# 提交者不需要填這個欄位，Registry 會根據 canonical_id 確定性生成
# Workflow 引用零件時，用 component_hash_id 才能保證永久不壞：
#   component://cmp_a3f9b2c1   ← 推薦，即使 canonical_id 改名也不受影響
#   component://string_reverse  ← 方便，AI 寫 workflow 時用這個，Registry 自動解析

canonical_id: "string_reverse"     # 見下方命名規範
display_name: "字串反轉"            # 人類可讀，可中文，供 UI 顯示用
description: >                     # 語意搜尋用，見下方說明
  將字串內容倒序排列，適合測試、資料清洗、回文判斷等場景。
  不依賴外部服務，純本地運算。
category: "data"                   # api / logic / data / ai / style / anim / ui
version: "v1"
author: "@your-github-username"
wasi_target: "preview1"
stability: "floating"              # floating / stable / pinned
runtime_compat:
  - "cf-workers"
  - "workerd"
  - "wazero"
constraints:
  max_size_kb: 2048
  max_cold_start_ms: 50
  no_network_syscall: true         # 功能類 true，整合類 false
  io_model: "stdin_stdout_json"
input_schema:
  type: object
  required: [text]
  properties:
    text:
      type: string
      description: 輸入文字
output_schema:
  type: object
  properties:
    result:
      type: string
gherkin_tests:
  - scenario: "基本轉換"
    given: '{"text":"hello"}'
    then_contains: '"result"'
  - scenario: "缺少必填欄位"
    given: '{}'
    then_contains: '"success":false'
config_example: |
  transform:
    text: "{{input.text}}"
description: "我的零件功能說明。"
```

整合類零件額外加入：

```yaml
credentials_required:
  - key: my_api_token
    type: api_key
    description: "My Service API token"
    inject_as: api_token
```

### canonical_id 命名規範

`canonical_id` 是零件的永久識別符，一旦上架不能更改（改名 = 新零件）。命名不統一會導致功能重複，請遵守以下規則：

| category | 格式 | 範例 |
|----------|------|------|
| `api`（整合類） | `{服務名}` 或 `{服務名}_{動作}` | `gmail`、`gmail_send`、`google_sheets`、`google_sheets_append`、`telegram` |
| `data`（資料處理） | `{資料型別}_ops` 或 `{動詞}_{名詞}` | `string_ops`、`array_ops`、`json_transform`、`csv_parse` |
| `logic`（控制流） | `{結構名}_control` 或單詞動詞 | `if_control`、`foreach_control`、`try_catch`、`switch`、`wait` |
| `ai`（AI 類） | `ai_{動作}` | `ai_transform_compile`、`ai_summarize`、`ai_classify` |

**規則：**
- 全部小寫、底線分隔、最多 4 個單詞
- 禁止：中文、空格、大寫、連字號（`-`）、版本號混入（用 `version` 欄位表達）
- `display_name` 才是人類可讀名稱，可以是「宇宙無敵 gsheets 新增一列」，`canonical_id` 不行

**提交前自問：** 如果有人想用 AI 搜尋「幫我找一個可以新增 Google Sheets 列的零件」，他搜到的名字應該是什麼？答案就是你的 `canonical_id`。

### description 寫法（語意搜尋）

`description` 是語意搜尋的索引來源，用自然語言描述「能做什麼、適合什麼情境」，而不是重複零件名稱。

**好的 description：**
```yaml
description: >
  傳送 Gmail 電子郵件，適合 Workflow 完成後通知使用者、發送訂閱確認信、
  錯誤警報通知等場景。支援自訂主旨、內文與收件人。需要 Gmail OAuth token。
```

**不好的 description（等於沒有）：**
```yaml
description: "Gmail 發信零件"   # 只是名稱的同義詞，搜不到任何額外資訊
```

原則：把這個 description 給一個不知道這個零件存在的人看，他能判斷「這就是我要的東西」嗎？

### aliases（搜尋同義詞）

arcrun 在 `registry/aliases.yaml` 維護一份 scope 級別的同義詞表。當你的零件 `canonical_id` 以已知 scope 為前綴，Registry 建立搜尋索引時會**自動**把對應的同義詞合併進去，不需要在 contract 裡手動填。

例如 `canonical_id: google_sheets_append`，Registry 會自動從 aliases.yaml 取得 `google_sheets` scope 的同義詞（`gsheets`、`試算表`、`spreadsheet`...），搜這些詞都能找到你的零件。

**如果你的零件有額外的情境同義詞**（超出 scope 範圍），可以在 contract 內手動補充：

```yaml
canonical_id: "google_sheets_append"
aliases:
  - "新增資料列"       # 情境同義詞，超出 scope 範圍
  - "insert row"
  # google_sheets scope 的同義詞（gsheets / 試算表 / spreadsheet...）
  # 由 registry/aliases.yaml 自動合併，不需要重複填寫
```

**想新增新 scope 的同義詞**（例如你要加一個 `notion` 零件）：在 `registry/aliases.yaml` 的對應 category 下加一個新 key，開 PR，merge 後所有以 `notion_` 開頭的零件都自動繼承。

> 這個機制目前是手工維護。未來接入 KBDB 後，`canonical_id` 將獲得系統派發的唯一 hash id，同義詞表將成為 KBDB synonym graph 的初始資料。

---

## TinyGo 零件開發

### 環境安裝

```bash
# TinyGo
brew install tinygo       # macOS
# 其他平台：https://tinygo.org/getting-started/

# 本機測試執行器
brew install wasmtime     # macOS
```

### 給 AI 的提示範本

```
請幫我用 TinyGo 寫一個 arcrun WASM 零件。

需求：[你的需求]

規則：
- 從 stdin 讀取 JSON，往 stdout 輸出 JSON
- 成功回傳 {"success":true,"result":...}
- 失敗回傳 {"success":false,"error":"..."}，不 panic
- 不使用網路、不使用檔案系統（純功能類零件）
- import 只用標準庫（encoding/json, os, fmt, strings 等）

請生成 main.go 和 component.contract.yaml。
```

### main.go 範本

```go
package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type Input struct {
	Text string `json:"text"`
}

type Output struct {
	Success bool   `json:"success"`
	Result  string `json:"result,omitempty"`
	Error   string `json:"error,omitempty"`
}

func main() {
	var input Input
	if err := json.NewDecoder(os.Stdin).Decode(&input); err != nil {
		writeError("invalid input: " + err.Error())
		return
	}

	if input.Text == "" {
		writeError("text is required")
		return
	}

	// 你的邏輯
	result := "[transformed] " + input.Text

	out, _ := json.Marshal(Output{Success: true, Result: result})
	fmt.Println(string(out))
}

func writeError(msg string) {
	out, _ := json.Marshal(Output{Success: false, Error: msg})
	fmt.Println(string(out))
}
```

### 編譯

```bash
cd registry/components/my_component
tinygo build -o my_component.wasm -target wasi .
```

### 本機測試

```bash
echo '{"text":"hello world"}' | wasmtime run my_component.wasm
# 預期：{"success":true,"result":"[transformed] hello world"}

echo '{}' | wasmtime run my_component.wasm
# 預期：{"success":false,"error":"text is required"}
```

---

## AssemblyScript 零件開發

### 環境安裝

```bash
# Node.js >= 18
node --version

# 初始化 AS 專案
npm init -y
npm install --save-dev assemblyscript
npx asinit .

# 本機測試執行器
brew install wasmtime     # macOS
```

### 給 AI 的提示範本

```
請幫我用 AssemblyScript（不是 TypeScript）寫一個 arcrun WASM 零件。

需求：[你的需求]

規則：
- AssemblyScript 是 TypeScript 的子集，編譯為 WASM，不能使用 DOM / Node.js API
- 從 stdin 讀取 JSON（使用 WASI fd_read），往 stdout 輸出 JSON（使用 Console.log）
- 成功回傳 {"success":true,"result":...}
- 失敗回傳 {"success":false,"error":"..."}
- 不使用網路、不使用檔案系統

請生成 assembly/index.ts 和 component.contract.yaml。
注意：AssemblyScript 沒有 JSON.parse，需要手動解析或使用 as-json 套件。
```

### assembly/index.ts 範本

```typescript
// AssemblyScript — 注意：這不是 Node.js / TypeScript！
// 沒有 DOM、沒有 fetch、沒有 require

import { Console } from "as-wasi/assembly";
import { JSON } from "assemblyscript-json/assembly";

export function _start(): void {
  // 從 stdin 讀取輸入
  const input = Console.readAll();

  // 解析 JSON
  const parsed = JSON.parse(input);
  if (!parsed.isObj) {
    Console.log('{"success":false,"error":"invalid input"}');
    return;
  }

  const obj = parsed as JSON.Obj;
  const textVal = obj.getString("text");
  if (textVal == null) {
    Console.log('{"success":false,"error":"text is required"}');
    return;
  }

  const text = textVal.valueOf();

  // 你的邏輯
  const result = "[transformed] " + text;

  Console.log('{"success":true,"result":"' + result + '"}');
}
```

**專案依賴（package.json）：**

```json
{
  "dependencies": {
    "as-wasi": "^0.4.7",
    "assemblyscript-json": "^1.1.0"
  }
}
```

### 編譯

```bash
cd registry/components/my_component
npm install
npx asc assembly/index.ts \
  --target release \
  --outFile my_component.wasm \
  --exportRuntime \
  --use abort=~lib/wasi_abort
```

### 本機測試

```bash
echo '{"text":"hello world"}' | wasmtime run my_component.wasm
# 預期：{"success":true,"result":"[transformed] hello world"}
```

---

## Rust 零件開發

Rust 零件支援已就緒，但文件尚在完善中。如果你熟悉 Rust + WASM，歡迎參考 [wasm-wasi 官方文件](https://doc.rust-lang.org/stable/reference/linkage.html)，核心要求與其他語言相同：WASI preview1，stdin/stdout JSON I/O。

基本設定：

```bash
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 --release
```

---

## 提交至公眾 Registry

```bash
# 確保 .wasm 已編譯
ls my_component.wasm

# 提交（需要 arcrun.dev API Key）
acr parts publish ./registry/components/my_component/
```

提交後流程：

| 狀態 | 說明 |
|------|------|
| `sandbox_pending` | 沙盒驗收執行中 |
| `author_only` | 驗收通過，你自己可用 |
| `public` | 人工審核通過，所有人可用，開始累積統計 |

查詢審核進度：

```bash
acr parts publish --status <submission_id>
```

---

## 常見問題

### `no_network_syscall` 設定錯誤

- **功能類**（category: logic / data / ai）：`no_network_syscall: true`。這類零件應完全沙盒化。
- **整合類**（category: api）：`no_network_syscall: false`，因為要呼叫外部 API。

兩者都需要宣告在 `constraints` 下，設錯會在 syscall 掃描步驟被沙盒拒絕。

### `gherkin_tests` 必須包含 happy path 和 error path

至少兩個測試場景：一個輸入正確的 happy path、一個缺少必填欄位或輸入非法的 error path。

### 體積超過上限

- TinyGo：確認使用 `-target wasi`（而非 `-target wasm`），前者體積更小
- AssemblyScript：加上 `--optimize` 或 `--target release`
- Rust：使用 `--release` 並加入 `opt-level = "z"` 到 `Cargo.toml`

---

## 問題回報

開 Issue：[github.com/richblack/arcrun/issues](https://github.com/richblack/arcrun/issues)
