Skip to content

Commit b7e619a

Browse files
committed
✨ 同步原版 v0.16.0
1 parent ae9e344 commit b7e619a

11 files changed

Lines changed: 225 additions & 103 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@minidoracat/openspec-tw": minor
3+
---
4+
5+
同步原版 v0.16.0
6+
7+
**新增 AI 工具支援**
8+
- iFlow-cli
9+
- Antigravity
10+
11+
**程式碼重構**
12+
- 新增 TomlSlashCommandConfigurator 基底類別
13+
- 重構 Gemini 和 Qwen 配置器使用新基底類別
14+
15+
**功能改進**
16+
- 新增 IDE 重啟提示(繁體中文本地化)
17+
18+
**Bug 修復**
19+
- 修正 Qwen Code slash 命令檔名(.md → .toml)

CLAUDE.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pnpm run release:local # 本地發布測試
5454
# 原版同步(使用 openspec-upstream/ 目錄)
5555
cd openspec-upstream && git fetch origin --tags # 獲取最新標籤
5656
git tag -l | sort -V | tail -5 # 查看最新版本
57-
git checkout v0.15.0 # 檢出目標版本
57+
git checkout v0.16.0 # 檢出目標版本
5858
cd .. && diff -r --brief src openspec-upstream/src # 比對差異
5959
```
6060

@@ -111,11 +111,11 @@ cd .. && diff -r --brief src openspec-upstream/src # 比對差異
111111
| 原版追蹤 | `openspec-upstream/` | 不提交 | 原版 git 倉庫,用於追蹤原版更新 |
112112

113113
### 版本對應關係
114-
- **當前版本**:v0.15.0(查看 `package.json` 中的 `version` 欄位)
115-
- **原版版本**:v0.15.0(Git tag)
116-
- **同步狀態**:✅ 已同步(基於原版 v0.15.0)
117-
- **同步日期**:2025-11-18
118-
- **核心功能**:新增 Qwen Code、Qoder、CoStrict、Gemini CLI、RooCode 支援;改進工具檢測邏輯
114+
- **當前版本**:v0.16.0(查看 `package.json` 中的 `version` 欄位)
115+
- **原版版本**:v0.16.0(Git tag)
116+
- **同步狀態**:✅ 已同步(基於原版 v0.16.0)
117+
- **同步日期**:2025-11-25
118+
- **核心功能**:新增 iFlow-cli、Antigravity 支援;重構 TOML 基底類別;新增 IDE 重啟提示;修復 Qwen Code TOML 檔名
119119
- **本地化狀態**:✅ 完成繁體中文本地化
120120

121121
### 緊急情況處理

src/core/configurators/iflow.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import path from "path";
2+
import { ToolConfigurator } from "./base.js";
3+
import { FileSystemUtils } from "../../utils/file-system.js";
4+
import { TemplateManager } from "../templates/index.js";
5+
import { OPENSPEC_MARKERS } from "../config.js";
6+
7+
export class IflowConfigurator implements ToolConfigurator {
8+
name = "iFlow";
9+
configFileName = "IFLOW.md";
10+
isAvailable = true;
11+
12+
async configure(projectPath: string, openspecDir: string): Promise<void> {
13+
const filePath = path.join(projectPath, this.configFileName);
14+
const content = TemplateManager.getClaudeTemplate();
15+
16+
await FileSystemUtils.updateFileWithMarkers(
17+
filePath,
18+
content,
19+
OPENSPEC_MARKERS.start,
20+
OPENSPEC_MARKERS.end
21+
);
22+
}
23+
}

src/core/configurators/registry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ClineConfigurator } from './cline.js';
44
import { CodeBuddyConfigurator } from './codebuddy.js';
55
import { CostrictConfigurator } from './costrict.js';
66
import { QoderConfigurator } from './qoder.js';
7+
import { IflowConfigurator } from './iflow.js';
78
import { AgentsStandardConfigurator } from './agents.js';
89
import { QwenConfigurator } from './qwen.js';
910

@@ -16,6 +17,7 @@ export class ToolRegistry {
1617
const codeBuddyConfigurator = new CodeBuddyConfigurator();
1718
const costrictConfigurator = new CostrictConfigurator();
1819
const qoderConfigurator = new QoderConfigurator();
20+
const iflowConfigurator = new IflowConfigurator();
1921
const agentsConfigurator = new AgentsStandardConfigurator();
2022
const qwenConfigurator = new QwenConfigurator();
2123
// Register with the ID that matches the checkbox value
@@ -24,6 +26,7 @@ export class ToolRegistry {
2426
this.tools.set('codebuddy', codeBuddyConfigurator);
2527
this.tools.set('costrict', costrictConfigurator);
2628
this.tools.set('qoder', qoderConfigurator);
29+
this.tools.set('iflow', iflowConfigurator);
2730
this.tools.set('agents', agentsConfigurator);
2831
this.tools.set('qwen', qwenConfigurator);
2932
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { SlashCommandConfigurator } from './base.js';
2+
import { SlashCommandId } from '../../templates/index.js';
3+
4+
const FILE_PATHS: Record<SlashCommandId, string> = {
5+
proposal: '.agent/workflows/openspec-proposal.md',
6+
apply: '.agent/workflows/openspec-apply.md',
7+
archive: '.agent/workflows/openspec-archive.md'
8+
};
9+
10+
const DESCRIPTIONS: Record<SlashCommandId, string> = {
11+
proposal: 'Scaffold a new OpenSpec change and validate strictly.',
12+
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
13+
archive: 'Archive a deployed OpenSpec change and update specs.'
14+
};
15+
16+
export class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
17+
readonly toolId = 'antigravity';
18+
readonly isAvailable = true;
19+
20+
protected getRelativePath(id: SlashCommandId): string {
21+
return FILE_PATHS[id];
22+
}
23+
24+
protected getFrontmatter(id: SlashCommandId): string | undefined {
25+
const description = DESCRIPTIONS[id];
26+
return `---\ndescription: ${description}\n---`;
27+
}
28+
}
Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { FileSystemUtils } from '../../../utils/file-system.js';
2-
import { SlashCommandConfigurator } from './base.js';
3-
import { SlashCommandId, TemplateManager } from '../../templates/index.js';
4-
import { OPENSPEC_MARKERS } from '../../config.js';
1+
import { TomlSlashCommandConfigurator } from './toml-base.js';
2+
import { SlashCommandId } from '../../templates/index.js';
53

64
const FILE_PATHS: Record<SlashCommandId, string> = {
75
proposal: '.gemini/commands/openspec/proposal.toml',
@@ -15,69 +13,15 @@ const DESCRIPTIONS: Record<SlashCommandId, string> = {
1513
archive: 'Archive a deployed OpenSpec change and update specs.'
1614
};
1715

18-
export class GeminiSlashCommandConfigurator extends SlashCommandConfigurator {
16+
export class GeminiSlashCommandConfigurator extends TomlSlashCommandConfigurator {
1917
readonly toolId = 'gemini';
2018
readonly isAvailable = true;
2119

2220
protected getRelativePath(id: SlashCommandId): string {
2321
return FILE_PATHS[id];
2422
}
2523

26-
protected getFrontmatter(_id: SlashCommandId): string | undefined {
27-
// TOML doesn't use separate frontmatter - it's all in one structure
28-
return undefined;
29-
}
30-
31-
// Override to generate TOML format with markers inside the prompt field
32-
async generateAll(projectPath: string, _openspecDir: string): Promise<string[]> {
33-
const createdOrUpdated: string[] = [];
34-
35-
for (const target of this.getTargets()) {
36-
const body = this.getBody(target.id);
37-
const filePath = FileSystemUtils.joinPath(projectPath, target.path);
38-
39-
if (await FileSystemUtils.fileExists(filePath)) {
40-
await this.updateBody(filePath, body);
41-
} else {
42-
const tomlContent = this.generateTOML(target.id, body);
43-
await FileSystemUtils.writeFile(filePath, tomlContent);
44-
}
45-
46-
createdOrUpdated.push(target.path);
47-
}
48-
49-
return createdOrUpdated;
50-
}
51-
52-
private generateTOML(id: SlashCommandId, body: string): string {
53-
const description = DESCRIPTIONS[id];
54-
55-
// TOML format with triple-quoted string for multi-line prompt
56-
// Markers are inside the prompt value
57-
return `description = "${description}"
58-
59-
prompt = """
60-
${OPENSPEC_MARKERS.start}
61-
${body}
62-
${OPENSPEC_MARKERS.end}
63-
"""
64-
`;
65-
}
66-
67-
// Override updateBody to handle TOML format
68-
protected async updateBody(filePath: string, body: string): Promise<void> {
69-
const content = await FileSystemUtils.readFile(filePath);
70-
const startIndex = content.indexOf(OPENSPEC_MARKERS.start);
71-
const endIndex = content.indexOf(OPENSPEC_MARKERS.end);
72-
73-
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
74-
throw new Error(`Missing OpenSpec markers in ${filePath}`);
75-
}
76-
77-
const before = content.slice(0, startIndex + OPENSPEC_MARKERS.start.length);
78-
const after = content.slice(endIndex);
79-
const updatedContent = `${before}\n${body}\n${after}`;
80-
81-
await FileSystemUtils.writeFile(filePath, updatedContent);
24+
protected getDescription(id: SlashCommandId): string {
25+
return DESCRIPTIONS[id];
8226
}
8327
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { SlashCommandConfigurator } from './base.js';
2+
import { SlashCommandId } from '../../templates/index.js';
3+
4+
const FILE_PATHS: Record<SlashCommandId, string> = {
5+
proposal: '.iflow/commands/openspec-proposal.md',
6+
apply: '.iflow/commands/openspec-apply.md',
7+
archive: '.iflow/commands/openspec-archive.md'
8+
};
9+
10+
const FRONTMATTER: Record<SlashCommandId, string> = {
11+
proposal: `---
12+
name: /openspec-proposal
13+
id: openspec-proposal
14+
category: OpenSpec
15+
description: Scaffold a new OpenSpec change and validate strictly.
16+
---`,
17+
apply: `---
18+
name: /openspec-apply
19+
id: openspec-apply
20+
category: OpenSpec
21+
description: Implement an approved OpenSpec change and keep tasks in sync.
22+
---`,
23+
archive: `---
24+
name: /openspec-archive
25+
id: openspec-archive
26+
category: OpenSpec
27+
description: Archive a deployed OpenSpec change and update specs.
28+
---`
29+
};
30+
31+
export class IflowSlashCommandConfigurator extends SlashCommandConfigurator {
32+
readonly toolId = 'iflow';
33+
readonly isAvailable = true;
34+
35+
protected getRelativePath(id: SlashCommandId): string {
36+
return FILE_PATHS[id];
37+
}
38+
39+
protected getFrontmatter(id: SlashCommandId): string {
40+
return FRONTMATTER[id];
41+
}
42+
}

src/core/configurators/slash/qwen.ts

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,23 @@
55
*
66
* @implements {SlashCommandConfigurator}
77
*/
8-
import { SlashCommandConfigurator } from './base.js';
8+
import { TomlSlashCommandConfigurator } from './toml-base.js';
99
import { SlashCommandId } from '../../templates/index.js';
1010

1111
/**
1212
* Mapping of slash command IDs to their corresponding file paths in .qwen/commands directory.
1313
* @type {Record<SlashCommandId, string>}
1414
*/
1515
const FILE_PATHS: Record<SlashCommandId, string> = {
16-
proposal: '.qwen/commands/openspec-proposal.md',
17-
apply: '.qwen/commands/openspec-apply.md',
18-
archive: '.qwen/commands/openspec-archive.md'
16+
proposal: '.qwen/commands/openspec-proposal.toml',
17+
apply: '.qwen/commands/openspec-apply.toml',
18+
archive: '.qwen/commands/openspec-archive.toml'
1919
};
2020

21-
/**
22-
* YAML frontmatter definitions for Qwen command files.
23-
* These provide metadata for each slash command to ensure proper recognition by Qwen Code.
24-
* @type {Record<SlashCommandId, string>}
25-
*/
26-
const FRONTMATTER: Record<SlashCommandId, string> = {
27-
proposal: `---
28-
name: /openspec-proposal
29-
id: openspec-proposal
30-
category: OpenSpec
31-
description: Scaffold a new OpenSpec change and validate strictly.
32-
---`,
33-
apply: `---
34-
name: /openspec-apply
35-
id: openspec-apply
36-
category: OpenSpec
37-
description: Implement an approved OpenSpec change and keep tasks in sync.
38-
---`,
39-
archive: `---
40-
name: /openspec-archive
41-
id: openspec-archive
42-
category: OpenSpec
43-
description: Archive a deployed OpenSpec change and update specs.
44-
---`
21+
const DESCRIPTIONS: Record<SlashCommandId, string> = {
22+
proposal: 'Scaffold a new OpenSpec change and validate strictly.',
23+
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
24+
archive: 'Archive a deployed OpenSpec change and update specs.'
4525
};
4626

4727
/**
@@ -53,7 +33,7 @@ description: Archive a deployed OpenSpec change and update specs.
5333
* - /openspec-apply: Apply an approved OpenSpec change
5434
* - /openspec-archive: Archive a deployed OpenSpec change
5535
*/
56-
export class QwenSlashCommandConfigurator extends SlashCommandConfigurator {
36+
export class QwenSlashCommandConfigurator extends TomlSlashCommandConfigurator {
5737
/** Unique identifier for the Qwen tool */
5838
readonly toolId = 'qwen';
5939

@@ -69,12 +49,7 @@ export class QwenSlashCommandConfigurator extends SlashCommandConfigurator {
6949
return FILE_PATHS[id];
7050
}
7151

72-
/**
73-
* Returns the YAML frontmatter for a given slash command ID.
74-
* @param {SlashCommandId} id - The slash command identifier
75-
* @returns {string} The YAML frontmatter string
76-
*/
77-
protected getFrontmatter(id: SlashCommandId): string {
78-
return FRONTMATTER[id];
52+
protected getDescription(id: SlashCommandId): string {
53+
return DESCRIPTIONS[id];
7954
}
8055
}

src/core/configurators/slash/registry.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { CrushSlashCommandConfigurator } from './crush.js';
1717
import { CostrictSlashCommandConfigurator } from './costrict.js';
1818
import { QwenSlashCommandConfigurator } from './qwen.js';
1919
import { RooCodeSlashCommandConfigurator } from './roocode.js';
20+
import { AntigravitySlashCommandConfigurator } from './antigravity.js';
21+
import { IflowSlashCommandConfigurator } from './iflow.js';
2022

2123
export class SlashCommandRegistry {
2224
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
@@ -40,6 +42,8 @@ export class SlashCommandRegistry {
4042
const costrict = new CostrictSlashCommandConfigurator();
4143
const qwen = new QwenSlashCommandConfigurator();
4244
const roocode = new RooCodeSlashCommandConfigurator();
45+
const antigravity = new AntigravitySlashCommandConfigurator();
46+
const iflow = new IflowSlashCommandConfigurator();
4347

4448
this.configurators.set(claude.toolId, claude);
4549
this.configurators.set(codeBuddy.toolId, codeBuddy);
@@ -59,6 +63,8 @@ export class SlashCommandRegistry {
5963
this.configurators.set(costrict.toolId, costrict);
6064
this.configurators.set(qwen.toolId, qwen);
6165
this.configurators.set(roocode.toolId, roocode);
66+
this.configurators.set(antigravity.toolId, antigravity);
67+
this.configurators.set(iflow.toolId, iflow);
6268
}
6369

6470
static register(configurator: SlashCommandConfigurator): void {

0 commit comments

Comments
 (0)