| name | concept-generate |
| description | 為概念探索報告中定義的概念產生 BlockSpec JSON、程式碼產生器、提升器和渲染映射。 產生在 Semorphe 語義樹管線中支援新概念所需的所有產出物。 在 /concept.discover 之後使用,用於建立實作產出物。支援任何語言。
|
| user-invocable | true |
語言指示:所有輸出文件(報告、摘要、註解)必須使用當前對話的語言撰寫。下方模板僅為結構參考,實際用語應配合使用者的語言設定。
⛔ 調用要求
此 skill 必須透過 Skill tool 調用,不可手動替代。當由 /concept.pipeline 編排時,pipeline 會使用 Skill tool 調用此 skill。
完成時必須輸出完成標記(見最後一節)。
概念產生
使用者輸入
$ARGUMENTS
參數應為概念探索報告的路徑(來自 /concept.discover),或 {lang} {concept_name} 格式(例如 cpp do_while、python list_comprehension)。
背景
你正在為新的 Semorphe 概念產生完整的實作產出物。每個概念需要 6 個產出物才能端到端運作:
- BlockSpec JSON — 定義概念如何渲染為 Blockly 積木(含 renderMapping)
- 程式碼產生器 — 將 SemanticNode → 目標語言原始碼
- 提升器(Lifter) — 將語言 AST → SemanticNode(透過 tree-sitter)
- 渲染映射 — 將 SemanticNode 屬性 → 積木欄位/輸入。Extract 路徑由 PatternExtractor 自動從 blockDef args + concept children 推導(auto-derive),無需手寫 extractor。 若概念有動態結構,須在 renderMapping 加入
dynamicRules
- Interpreter Executor — 將 SemanticNode → 執行行為(在
src/interpreter/executors/ 中註冊)。可執行概念需實作計算邏輯,宣告性概念(如 #include)需註冊 noop executor。見 docs/technical-experiences.md §20
- 測試 — 基本的 round-trip 測試(含執行測試)
前置作業
產生前,請先閱讀這些檔案以理解現有模式:
src/core/types.ts — SemanticNode 結構、現有概念
docs/first-principles.md — P2(概念代數)的屬性結構規則
然後閱讀目標語言的既有實作:
- 核心概念:
src/languages/{lang}/core/blocks.json — 現有 BlockSpec 範例
- STD 模組:
src/languages/{lang}/std/{module}/blocks.json — 標準庫 BlockSpec
src/languages/{lang}/core/generators/ — 現有 generator 模式
src/languages/{lang}/core/lifters/ — 現有 lifter 模式
src/core/projection/pattern-renderer.ts — 渲染映射如何運作
如果是全新語言(src/languages/{lang}/ 不存在),先參考現有語言模組(如 src/languages/cpp/)的目錄結構來建立骨架。
工作流程
步驟一:解析概念定義
從探索報告或使用者輸入中,為每個概念提取(命名慣例見 /concept.discover 階段四):
- 概念名稱
- 概念類型:通用(universal)還是語言特定(
{lang}:concept)
- 建議歸屬的 Topic 層級樹節點
- 目標語言的語法模式
- 屬性(積木上的欄位)
- 子節點(子表達式/語句的輸入槽)
- 工具箱分類
步驟二:產生 BlockSpec JSON
概念所屬層級決定檔案存放位置:核心概念放 src/languages/{lang}/core/blocks.json,STD 模組概念放 src/languages/{lang}/std/{module}/blocks.json。
{
"type": "{prefix}_{concept_name}",
"conceptId": "{concept_name}",
"category": "{category}",
"message0": "{帶 %1 %2 佔位符的積木標籤}",
"args0": [
{ "type": "field_input", "name": "FIELD_NAME", "text": "default" },
{ "type": "input_value", "name": "INPUT_NAME" }
],
"output": null,
"previousStatement": null,
"nextStatement": null,
"colour": "{category_colour}",
"renderMapping": {
"fields": { "FIELD_NAME": "property_name" },
"inputs": { "INPUT_NAME": "child_slot" }
}
}
規則:
type 是投影層的積木類型名稱,前綴:u_ 為通用積木,語言特定用語言縮寫(如 c_ for C++、py_ for Python、j_ for Java)
conceptId 是語義層的概念識別碼,格式為 snake_case(通用)或 {lang}:snake_case(語言特定)
- 注意兩者的區別:
conceptId: "cpp:vector_push" 對應 type: "c_vector_push";conceptId: "sort_range" 對應 type: "u_sort_range"。conceptId 用於語義樹,type 用於 Blockly 積木
- i18n 必須使用
%{BKY_...} key:message0、tooltip、以及 field_dropdown 的 options 顯示文字,一律使用 %{BKY_KEY_NAME} 格式引用,不可硬編碼任何語言的文字。同時在 src/i18n/zh-TW/blocks.json 和 src/i18n/en/blocks.json 中新增對應的翻譯條目。參考現有 STD 模組(如 vector、cstring)的 i18n 模式。
message0 在目標語系中應盡可能易讀
i18n 標籤風格規範(強制遵守):
積木標籤的目的是讓學生不看文件就能理解積木的語義。以下規則確保跨概念的一致性:
| 規則 | 正確 ✅ | 錯誤 ❌ | 說明 |
|---|
| 中文用描述式動詞短語 | 排序 %1 | sort( %1 ) | 不抄語法,用語義描述 |
| 英文用動詞開頭短語 | Sort %1 | sort( begin, end ) | 首字母大寫,不加括號 |
| 函式名不直接當標籤 | 取絕對值 %1 | abs( %1 ) | 函式名放 tooltip,標籤用語義 |
| 語言關鍵字不當標籤 | 宣告常數 %1 %2 = %3 | const %1 %2 = %3 | 任何語言的關鍵字(C++ 的 const/auto/virtual;Python 的 def/class/lambda;Java 的 abstract/synchronized 等)都用中文/英文語義描述取代 |
| 語法符號不當標籤 | 靜態轉型為 %1(%2) | static_cast < %1 > ( %2 ) | 語言特殊語法(C++ 的 <>, [](), ~;Python 的 @;Java 的 <T> 等)不可出現在標籤中 |
| 方法呼叫語法不當標籤 | 清空 %1 | %1 .clear() | .method() 語法不可出現,用動詞描述 |
| 容器操作統一格式 | 將 %2 推入 %1 | %1 .push( %2 ) | 動詞在前,物件與參數用自然語序 |
| tooltip 必須補充說明 | tooltip: 對範圍 [begin, end) 進行升序排列 | tooltip: 排序 | tooltip 不可只是重複 message0 |
| 同類概念用相同句式 | 所有數學函式:{動詞} %1 | 取絕對值 %1 vs sqrt( %1 ) | 同 category 的標籤必須風格統一 |
| 型別/參數名不出現在標籤中 | 宣告變數 %1 | int %1 = %2 | 型別資訊放 dropdown 或 tooltip |
常見違規模式速查表(以下模式在標籤中一律禁止,適用所有語言):
| 模式 | 範例 | 應改為 |
|---|
.method() | %1 .push_back( %2 ), %1.append(%2) | 在 %1 末端加入 %2 |
func() | sizeof( %1 ), abs( %1 ), len(%1) | 取得 %1 的大小, 取絕對值 %1, %1 的長度 |
| 語言關鍵字 | const %1, auto %1, virtual %1, def %1, class %1 | 宣告常數 %1, 自動推斷 %1, 虛擬方法 %1, 定義函式 %1, 定義類別 %1 |
| C++ cast 語法 | static_cast < %1 > ( %2 ) | 靜態轉型為 %1(%2) |
| Lambda/閉包語法 | [ %1 ] ( %2 ), lambda %1: %2 | 匿名函式 擷取 %1 參數 %2 |
| 解構子語法 | ~ %1 () | 解構子 ~%1() |
| 運算子語法 | %1 operator %2 ( %3 ) | 運算子多載 %2 回傳 %1(%3) |
| 裝飾器語法 | @%1 | 套用裝飾器 %1 |
| 泛型語法 | %1<%2> | %1(型別 %2) |
產生 i18n 條目時的檢查清單:
- 讀取同 category 的現有標籤(
grep i18n JSON),確保新標籤與既有風格一致
- 中文標籤是否為描述式?(動詞 + 名詞,如「排序範圍」「取得長度」「插入元素」)
- 英文標籤是否為動詞短語?(如「Sort range」「Get length」「Insert element」)
- tooltip 是否提供了 message0 以外的額外資訊?(參數說明、行為細節、注意事項)
- 同一批次產生的多個概念之間,標籤句式是否一致?
- 標籤中是否殘留任何目標語言的關鍵字、語法符號或方法呼叫語法?(對照上方速查表逐一檢查)
- 最小化 args 數量 — 認知負載原則
- 語句積木:設定
previousStatement/nextStatement
- 表達式積木:設定
output(型別或 null 代表任意)
- 如果概念同時有語句和表達式形式,用
expressionCounterpart 產生兩者。對應 P2 概念角色語境依賴(§2.2)——statement/expression 版本的 extraState 格式必須完全相同。注意:expression counterpart 積木必須有完整的 blockDef(含 args0 定義),不可只寫 {type: "..."},否則 PatternExtractor auto-derive 會失敗
- 如果此概念在不同 Topic 下需不同積木形狀,在 Topic JSON 加
blockOverrides(§2.4)
步驟三:產生程式碼產生器
在 src/languages/{lang}/core/generators/ 的適當檔案中加入 generator 函式。
generators.set('{concept_name}', (node, ctx) => {
const prop = node.properties.prop_name
const child = generateExpression(node.children.child_slot?.[0], ctx)
return `${indent(ctx)}${formatted_code}\n`
})
規則:
- 語句層級輸出使用
indent(ctx)
- 子表達式使用
generateExpression()
- 子語句列表使用
generateBody()
- 妥善處理缺失的子節點(空字串或預設值)
- 遵循
ctx.style 的格式偏好
- 注意語言特有的語法(如 Python 的縮排、Java 的分號)
步驟四:產生提升器
在 src/languages/{lang}/core/lifters/ 的適當檔案中加入 lifter 註冊。
lifter.register('{tree_sitter_node_type}', (node, context) => {
const prop = node.childForFieldName('field')?.text ?? ''
return createNode('{concept_name}', { prop }, {
child_slot: context.liftChildren(node, 'body_field'),
})
})
Layer 引導:Layer 1 純 JSON(astPattern)、Layer 2 JSON + transform(TransformRegistry)、Layer 3 JSON + strategy(LiftStrategyRegistry)。見 §2.3。
信心等級設定規則(P1 §2.1,強制遵守):
| 信心等級 | 使用時機 | 範例 |
|---|
high | 結構完全匹配且通過語義驗證的直接映射 | number_literal → number_literal |
warning | 結構匹配但語義可能不準確(一對多映射) | binary_expression 可能是算術/比較/位元運算 |
inferred | 推測性對應(從上下文推斷) | 從使用位置推斷變數型別 |
raw_code | 無法結構化的降級 | 不支援的語法 |
關鍵規則:
- composite pattern 不可直接設
high——必須先通過語義驗證(至少驗證子節點概念是否合理)
- 一對多 AST 映射必須設
warning——例如 call_expression 可能映射到 func_call、cpp:cout、cpp:sort 等多個概念,在確定具體概念前應設 warning
- 每個 lifter 必須有降級路徑——當 AST 節點無法識別時,應降級為
raw_code 而非靜默丟棄
規則:
- 第一個參數是 tree-sitter 節點類型(不是概念名稱)— 每個語言的 tree-sitter grammar 不同
- 子節點使用
context.liftChildren()
- 具名欄位使用
node.childForFieldName()
- 可選欄位使用
?? defaultValue 處理
- 多個 tree-sitter 類型可映射到同一個概念
- 通用概念在不同語言中會有不同的 tree-sitter 映射
步驟五:產生 Interpreter Executor
在 src/interpreter/executors/ 的適當檔案中加入 executor 註冊。
可執行概念(如數學運算、I/O、控制流程):
register('{concept_name}', async (node, ctx) => {
const arg = await ctx.evaluate((node.children.arg ?? [])[0])
const result = someComputation(ctx.toNumber(arg))
return { type: 'double', value: result }
})
宣告性概念(如 #include、using namespace、註解):
register('{concept_name}', async () => {})
規則:
- 參考
src/interpreter/executors/ 中的現有 executor 模式
- 子節點評估使用
ctx.evaluate(),值轉換使用 ctx.toNumber()/ctx.toBool()
- 執行結果回傳
RuntimeValue({ type, value })
- 語句型概念不需回傳值(
return 或 void)
- 在
src/interpreter/interpreter.ts 的建構函式中 import 並呼叫 registerXxxExecutors(reg)
- 絕不靜默跳過概念——未註冊的概念會觸發
unknownConceptHandler(見 docs/technical-experiences.md §20)
步驟六:產生測試
依照 tests/ 中的模式建立測試檔案:
describe('{concept_name}', () => {
it('should lift {描述}', () => {
const code = `{最小範例}`
})
it('should generate {描述}', () => {
const node = createNode('{concept_name}', { ... }, { ... })
})
it('should round-trip {描述}', () => {
const code = `{程式碼}`
})
})
步驟六之二:四路完備性驗證(強制阻擋)
產生所有產出物後,必須執行四路完備性驗證。這是阻擋性關卡——缺少任何一路就不可繼續。
對目標概念逐一確認以下 6 條路徑全部存在:
| # | 路徑 | 驗證方式 | 缺失後果 |
|---|
| 1 | Lift | lifter 檔案中有 register('{nodeType}', ...) 或 lift-patterns.json 有條目 | ❌ 阻擋 |
| 2 | Render | blocks.json 中有 BlockSpec 條目,且 renderMapping 完整(fields + inputs 覆蓋所有語義屬性) | ❌ 阻擋 |
| 3 | Extract | BlockSpec 的 renderMapping 可被 PatternExtractor 自動反向提取(auto-derive from blockDef args + concept children);若概念有動態結構,renderMapping 須包含 dynamicRules | ❌ 阻擋 |
| 4 | Generate | generator 檔案中有 generators.set('{concept}', ...) | ❌ 阻擋 |
| 5 | Execute | executor 檔案中有 register('{concept}', ...) | ❌ 阻擋 |
| 6 | Test | 測試檔存在且包含 lift、generate、round-trip 三種測試 | ❌ 阻擋 |
grep -rn "'{concept_name}'" src/languages/{lang}/ src/interpreter/executors/ tests/
如果任何路徑缺失:
- 報告缺失的路徑清單
- 立即補全缺失的產出物
- 重新驗證直到 6/6 通過
完成標記中的 產出物:{N}/6 必須反映此驗證結果。N < 6 時不可輸出 SKILL_COMPLETE 標記。
步驟七:通用概念跨語言產生
僅適用於通用概念(conceptId 為 snake_case,無語言前綴):
通用概念必須在所有已支援的語言模組中都有對應實作。檢查 src/languages/ 下有哪些語言模組,對每個已存在的語言模組:
- 產生該語言的 generator 函式(語法不同,語義相同)
- 產生該語言的 lifter 註冊(tree-sitter 節點類型因語言而異)
- 在該語言的 block spec JSON 中加入對應的積木定義
- 產生該語言的測試
例如,新增通用概念 sort_range 時,若已有 cpp 和 python 模組,則需同時在兩個語言中產生 generator/lifter/block/test。
步驟八:更新註冊
確認新概念需要在哪些地方註冊:
src/languages/{lang}/toolbox-categories.ts 中的工具箱分類
- 適當的 Topic JSON 檔案(
src/languages/{lang}/topics/*.json)中的 levelTree 節點,將概念 ID 加入對應節點的 concepts[]
- concept registry 中的概念定義
- 如果是通用概念,更新
src/core/types.ts 的 UniversalConcept 型別
- STD 模組概念需更新 DependencyResolver 映射(§2.3)
STD 模組結構:STD 模組使用扁平結構——每個模組目錄下直接放 generators.ts、lifters.ts、blocks.json、concepts.json,不再有子目錄。
步驟九:輸出摘要
報告產生了什麼:
## {concept_name} 的產出物({language})
- [ ] BlockSpec:核心 `src/languages/{lang}/core/blocks.json` 或 STD `src/languages/{lang}/std/{module}/blocks.json`
- [ ] Generator:核心 `src/languages/{lang}/core/generators/{file}.ts` 或 STD `src/languages/{lang}/std/{module}/generators.ts`
- [ ] Lifter:核心 `src/languages/{lang}/core/lifters/{file}.ts` 或 STD `src/languages/{lang}/std/{module}/lifters.ts`
- [ ] 渲染映射:嵌入在 BlockSpec 中
- [ ] Executor:`src/interpreter/executors/{file}.ts`(可執行概念需實作邏輯,宣告性概念需 noop)
- [ ] 測試:`tests/unit/languages/{lang}/{concept_name}.test.ts`(含執行測試)
- [ ] 註冊:{加在哪裡}
### 驗證
執行 `npm test` 確認所有測試通過。
執行 `npx tsc --noEmit` 確認無型別錯誤。
準則
- 一次一個概念 — 先為一個概念產生所有產出物,再處理下一個
- 遵循現有模式 — 匹配該語言模組中鄰近檔案的程式碼風格
- 最小變更 — 產生新概念時不要重構現有程式碼
- 測試優先 — 盡可能在實作之前先寫測試
- 積木 UX — 在腦中預覽積木:學生第一眼能看懂嗎?
- 通用概念共用 — 如果產生的是通用概念,確保它在已支援的所有語言中都能運作
完成標記(強制)
此 skill 完成後,必須輸出以下格式的完成標記:
🏁 SKILL_COMPLETE: concept-generate | {lang} | {concept_name} | 產出物:{N}/6 | tsc: PASS/FAIL
如果未輸出此標記,pipeline 不會繼續下一階段。