diff --git a/openspec/changes/add-multilingual-support/proposal.md b/openspec/changes/add-multilingual-support/proposal.md new file mode 100644 index 00000000..d9ccc9a7 --- /dev/null +++ b/openspec/changes/add-multilingual-support/proposal.md @@ -0,0 +1,28 @@ +## Why + +OpenSpec 目前只支持英文,这限制了非英语用户的使用体验。添加多语言支持可以让更多开发者使用他们熟悉的语言来使用 OpenSpec,降低使用门槛并提高采用率。通过在初始化时选择语言,后续生成的所有配置文件和模板内容都将使用所选语言,确保一致的用户体验。 + +## What Changes + +- 在 `openspec init` 命令中添加语言选择步骤,让用户在初始化时选择语言(如:中文、英文等) +- 扩展 `OpenSpecConfig` 接口,添加 `language` 字段用于存储用户选择的语言 +- 修改所有模板生成逻辑(`AGENTS.md`、`project.md`、各种 AI 工具配置文件等),根据选择的语言生成对应语言的内容 +- 在 `openspec/` 目录下创建配置文件(如 `config.json`)存储语言设置,以便后续命令(如 `openspec update`)能够读取并使用相同的语言 +- 更新 `openspec update` 命令,使其能够读取并应用已配置的语言设置 +- 支持的语言:默认英文(en-US),可选中文(zh-CN)、法语(fr-FR)、日语(ja-JP)、阿拉伯语(ar-SA)等,后续可扩展 + +### Breaking Changes + +- 无 - 这是新增功能,默认行为保持不变(默认使用英文) + +## Impact + +- Affected specs: `specs/cli-init`(需要添加语言选择相关需求) +- Affected code: + - `src/core/config.ts`(扩展 `OpenSpecConfig` 接口) + - `src/core/init.ts`(添加语言选择提示和配置存储逻辑) + - `src/core/update.ts`(读取并应用语言配置) + - `src/core/templates/`(所有模板文件需要支持多语言) + - `src/core/configurators/`(所有配置器需要支持多语言模板) + - `src/utils/file-system.ts`(可能需要添加配置文件读写功能) + diff --git a/openspec/changes/add-multilingual-support/specs/cli-init/spec.md b/openspec/changes/add-multilingual-support/specs/cli-init/spec.md new file mode 100644 index 00000000..65d59ac0 --- /dev/null +++ b/openspec/changes/add-multilingual-support/specs/cli-init/spec.md @@ -0,0 +1,139 @@ +## ADDED Requirements + +### Requirement: Language Selection + +The command SHALL prompt users to select their preferred language during initialization, and all generated content SHALL use the selected language. The default language SHALL be English (en-US), with optional support for Chinese (zh-CN), French (fr-FR), Japanese (ja-JP), Arabic (ar-SA), and other languages as they become available. + +#### Scenario: Prompting for language selection in interactive mode + +- **WHEN** `openspec init` is executed interactively +- **THEN** present a language selection prompt before AI tool selection +- **AND** display available languages with their native names (e.g., "English (en-US)", "中文 (zh-CN)", "Français (fr-FR)", "日本語 (ja-JP)", "العربية (ar-SA)") +- **AND** default to English (en-US) if no selection is made +- **AND** store the selected language in `openspec/config.json` for future use +- **AND** supported language codes include: `en-US` (default), `zh-CN`, `fr-FR`, `ja-JP`, `ar-SA` + +#### Scenario: Language selection in non-interactive mode + +- **WHEN** `openspec init` is executed with `--language` option +- **THEN** validate the provided language code against supported languages +- **AND** use the specified language for all generated content +- **AND** store the language setting in `openspec/config.json` +- **AND** exit with code 1 if an invalid language code is provided, displaying available language codes + +#### Scenario: Reading existing language configuration + +- **GIVEN** `openspec/config.json` exists with a language setting +- **WHEN** `openspec init` is executed in extend mode +- **THEN** read the existing language configuration +- **AND** use the stored language for generating new content +- **AND** allow users to change the language during extend mode if desired + +### Requirement: Language Configuration Storage + +The command SHALL persist language settings in a configuration file for consistency across commands. + +#### Scenario: Creating language configuration file + +- **WHEN** language is selected during initialization +- **THEN** create `openspec/config.json` with the language setting +- **AND** store the language code in the format: `{ "language": "en-US" }` +- **AND** ensure the file is created even if only the directory structure is being extended + +#### Scenario: Configuration file format + +- **WHEN** `openspec/config.json` is created or updated +- **THEN** use valid JSON format +- **AND** include the `language` field with a valid language code +- **AND** preserve any existing configuration fields if the file already exists + +### Requirement: Multilingual Template Generation + +All generated templates and configuration files SHALL use content in the selected language. + +#### Scenario: Generating AGENTS.md in selected language + +- **WHEN** initializing OpenSpec with a selected language +- **THEN** generate `openspec/AGENTS.md` with content in the selected language +- **AND** ensure all instructions, examples, and guidance text are translated appropriately + +#### Scenario: Generating project.md in selected language + +- **WHEN** initializing OpenSpec with a selected language +- **THEN** generate `openspec/project.md` with template content in the selected language +- **AND** ensure all section headers, placeholders, and guidance text are in the selected language + +#### Scenario: Generating AI tool configuration files in selected language + +- **WHEN** configuring AI tools with a selected language +- **THEN** generate all tool-specific configuration files (e.g., `CLAUDE.md`, `.cursor/commands/*.md`) with content in the selected language +- **AND** ensure stub instructions and managed block content are translated appropriately + +#### Scenario: Generating slash command files in selected language + +- **WHEN** generating slash command files for selected AI tools +- **THEN** populate all command files with instructions in the selected language +- **AND** ensure workflow descriptions and guidance text match the selected language + +## MODIFIED Requirements + +### Requirement: File Generation + +The command SHALL generate required template files with appropriate content for immediate use in the user's selected language. + +#### Scenario: Generating template files + +- **WHEN** initializing OpenSpec +- **THEN** generate `openspec/AGENTS.md` containing complete OpenSpec instructions for AI assistants in the selected language +- **AND** generate `project.md` with project context template in the selected language +- **AND** use the language setting from `openspec/config.json` if it exists, otherwise use the language selected during initialization + +### Requirement: Directory Creation + +The command SHALL create the complete OpenSpec directory structure with all required directories and files, including the configuration file. + +#### Scenario: Creating OpenSpec structure + +- **WHEN** `openspec init` is executed +- **THEN** create the following directory structure: +``` +openspec/ +├── project.md +├── AGENTS.md +├── config.json +├── specs/ +└── changes/ + └── archive/ +``` + +### Requirement: Non-Interactive Mode + +The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases, including language selection. + +#### Scenario: Select all tools non-interactively + +- **WHEN** run with `--tools all` and `--language ` +- **THEN** automatically select every available AI tool without prompting +- **AND** use the specified language for all generated content +- **AND** proceed with initialization using the selected tools and language + +#### Scenario: Select specific tools non-interactively + +- **WHEN** run with `--tools claude,cursor` and `--language ` +- **THEN** parse the comma-separated tool IDs and validate against available tools +- **AND** use the specified language for all generated content +- **AND** proceed with initialization using only the specified valid tools + +#### Scenario: Skip tool configuration non-interactively + +- **WHEN** run with `--tools none` and `--language ` +- **THEN** skip AI tool configuration entirely +- **AND** use the specified language for generated template files +- **AND** only create the OpenSpec directory structure and template files + +#### Scenario: Help text lists available tool IDs and language codes + +- **WHEN** displaying CLI help for `openspec init` +- **THEN** show the `--tools` option description with the valid values derived from the AI tool registry +- **AND** show the `--language` option description with supported language codes (e.g., `en-US` (default), `zh-CN`, `fr-FR`, `ja-JP`, `ar-SA`) + diff --git a/openspec/changes/add-multilingual-support/tasks.md b/openspec/changes/add-multilingual-support/tasks.md new file mode 100644 index 00000000..76cf959a --- /dev/null +++ b/openspec/changes/add-multilingual-support/tasks.md @@ -0,0 +1,29 @@ +## 1. 配置和数据结构 +- [x] 1.1 扩展 `OpenSpecConfig` 接口,添加 `language: string` 字段 +- [x] 1.2 创建语言配置文件结构(`openspec/config.json` 或类似),用于持久化语言设置 +- [x] 1.3 定义支持的语言列表和语言代码(默认:`en-US`,可选:`zh-CN`, `fr-FR`, `ja-JP`, `ar-SA` 等) +- [x] 1.4 创建语言资源文件结构,用于存储各语言的模板内容 + +## 2. 语言选择功能 +- [x] 2.1 在 `init.ts` 中添加语言选择提示(在 AI 工具选择之前或之后) +- [x] 2.2 实现语言选择交互界面,支持从可用语言列表中选择 +- [x] 2.3 将选择的语言保存到配置文件中 +- [x] 2.4 在非交互模式下支持 `--language` 命令行参数 + +## 3. 模板多语言化 +- [x] 3.1 创建多语言模板系统,为每种语言准备模板内容 +- [x] 3.2 修改 `agents-template.ts`,支持根据语言生成不同内容(已完成完整翻译:中文、法语、日语、阿拉伯语) +- [x] 3.3 修改 `project-template.ts`,支持根据语言生成不同内容 +- [x] 3.4 修改所有 AI 工具配置器模板,支持多语言内容 +- [x] 3.5 修改 slash command 模板,支持多语言内容 + +## 4. 更新命令支持 +- [x] 4.1 修改 `update.ts`,使其能够读取配置文件中的语言设置 +- [x] 4.2 确保 `openspec update` 使用相同的语言生成更新后的内容 + +## 5. 测试和文档 +- [x] 5.1 添加单元测试,验证语言选择功能 +- [x] 5.2 添加集成测试,验证多语言模板生成 +- [x] 5.3 更新 CLI 帮助文档,说明 `--language` 参数 +- [ ] 5.4 更新 `AGENTS.md` 中的文档,说明多语言功能(可选,因为多语言功能本身已包含在模板中) + diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index 7bfb49cb..41953f1e 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -19,7 +19,7 @@ The command SHALL display progress indicators during initialization to provide c - Then success: "✔ AI tools configured" ### Requirement: Directory Creation -The command SHALL create the complete OpenSpec directory structure with all required directories and files. +The command SHALL create the complete OpenSpec directory structure with all required directories and files, including the configuration file. #### Scenario: Creating OpenSpec structure - **WHEN** `openspec init` is executed @@ -28,18 +28,20 @@ The command SHALL create the complete OpenSpec directory structure with all requ openspec/ ├── project.md ├── AGENTS.md +├── config.json ├── specs/ └── changes/ └── archive/ ``` ### Requirement: File Generation -The command SHALL generate required template files with appropriate content for immediate use. +The command SHALL generate required template files with appropriate content for immediate use in the user's selected language. #### Scenario: Generating template files - **WHEN** initializing OpenSpec -- **THEN** generate `openspec/AGENTS.md` containing complete OpenSpec instructions for AI assistants -- **AND** generate `project.md` with project context template +- **THEN** generate `openspec/AGENTS.md` containing complete OpenSpec instructions for AI assistants in the selected language +- **AND** generate `project.md` with project context template in the selected language +- **AND** use the language setting from `openspec/config.json` if it exists, otherwise use the language selected during initialization ### Requirement: AI Tool Configuration The command SHALL configure AI coding assistants with OpenSpec instructions using a grouped selection experience so teams can enable native integrations while always provisioning guidance for other assistants. @@ -232,30 +234,34 @@ The init command SHALL generate slash command files for supported editors using - **AND** each template includes instructions for the relevant OpenSpec workflow stage ### Requirement: Non-Interactive Mode -The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases. +The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases, including language selection. #### Scenario: Select all tools non-interactively -- **WHEN** run with `--tools all` +- **WHEN** run with `--tools all` and `--language ` - **THEN** automatically select every available AI tool without prompting -- **AND** proceed with initialization using the selected tools +- **AND** use the specified language for all generated content +- **AND** proceed with initialization using the selected tools and language #### Scenario: Select specific tools non-interactively -- **WHEN** run with `--tools claude,cursor` +- **WHEN** run with `--tools claude,cursor` and `--language ` - **THEN** parse the comma-separated tool IDs and validate against available tools +- **AND** use the specified language for all generated content - **AND** proceed with initialization using only the specified valid tools #### Scenario: Skip tool configuration non-interactively -- **WHEN** run with `--tools none` +- **WHEN** run with `--tools none` and `--language ` - **THEN** skip AI tool configuration entirely +- **AND** use the specified language for generated template files - **AND** only create the OpenSpec directory structure and template files #### Scenario: Invalid tool specification - **WHEN** run with `--tools` containing any IDs not present in the AI tool registry - **THEN** exit with code 1 and display available values (`all`, `none`, or the supported tool IDs) -#### Scenario: Help text lists available tool IDs +#### Scenario: Help text lists available tool IDs and language codes - **WHEN** displaying CLI help for `openspec init` - **THEN** show the `--tools` option description with the valid values derived from the AI tool registry +- **AND** show the `--language` option description with supported language codes (e.g., `en-US` (default), `zh-CN`, `fr-FR`, `ja-JP`, `ar-SA`) ### Requirement: Root instruction stub `openspec init` SHALL always scaffold the root-level `AGENTS.md` hand-off so every teammate finds the primary OpenSpec instructions. @@ -267,6 +273,81 @@ The command SHALL support non-interactive operation through command-line options - **AND** preserve any existing content outside the managed markers while replacing the stub text inside them - **AND** create the stub regardless of which native AI tools are selected +### Requirement: Language Selection + +The command SHALL prompt users to select their preferred language during initialization, and all generated content SHALL use the selected language. The default language SHALL be English (en-US), with optional support for Chinese (zh-CN), French (fr-FR), Japanese (ja-JP), Arabic (ar-SA), and other languages as they become available. + +#### Scenario: Prompting for language selection in interactive mode + +- **WHEN** `openspec init` is executed interactively +- **THEN** present a language selection prompt before AI tool selection +- **AND** display available languages with their native names (e.g., "English (en-US)", "中文 (zh-CN)", "Français (fr-FR)", "日本語 (ja-JP)", "العربية (ar-SA)") +- **AND** default to English (en-US) if no selection is made +- **AND** store the selected language in `openspec/config.json` for future use +- **AND** supported language codes include: `en-US` (default), `zh-CN`, `fr-FR`, `ja-JP`, `ar-SA` + +#### Scenario: Language selection in non-interactive mode + +- **WHEN** `openspec init` is executed with `--language` option +- **THEN** validate the provided language code against supported languages +- **AND** use the specified language for all generated content +- **AND** store the language setting in `openspec/config.json` +- **AND** exit with code 1 if an invalid language code is provided, displaying available language codes + +#### Scenario: Reading existing language configuration + +- **GIVEN** `openspec/config.json` exists with a language setting +- **WHEN** `openspec init` is executed in extend mode +- **THEN** read the existing language configuration +- **AND** use the stored language for generating new content +- **AND** allow users to change the language during extend mode if desired + +### Requirement: Language Configuration Storage + +The command SHALL persist language settings in a configuration file for consistency across commands. + +#### Scenario: Creating language configuration file + +- **WHEN** language is selected during initialization +- **THEN** create `openspec/config.json` with the language setting +- **AND** store the language code in the format: `{ "language": "en-US" }` +- **AND** ensure the file is created even if only the directory structure is being extended + +#### Scenario: Configuration file format + +- **WHEN** `openspec/config.json` is created or updated +- **THEN** use valid JSON format +- **AND** include the `language` field with a valid language code +- **AND** preserve any existing configuration fields if the file already exists + +### Requirement: Multilingual Template Generation + +All generated templates and configuration files SHALL use content in the selected language. + +#### Scenario: Generating AGENTS.md in selected language + +- **WHEN** initializing OpenSpec with a selected language +- **THEN** generate `openspec/AGENTS.md` with content in the selected language +- **AND** ensure all instructions, examples, and guidance text are translated appropriately + +#### Scenario: Generating project.md in selected language + +- **WHEN** initializing OpenSpec with a selected language +- **THEN** generate `openspec/project.md` with template content in the selected language +- **AND** ensure all section headers, placeholders, and guidance text are in the selected language + +#### Scenario: Generating AI tool configuration files in selected language + +- **WHEN** configuring AI tools with a selected language +- **THEN** generate all tool-specific configuration files (e.g., `CLAUDE.md`, `.cursor/commands/*.md`) with content in the selected language +- **AND** ensure stub instructions and managed block content are translated appropriately + +#### Scenario: Generating slash command files in selected language + +- **WHEN** generating slash command files for selected AI tools +- **THEN** populate all command files with instructions in the selected language +- **AND** ensure workflow descriptions and guidance text match the selected language + ## Why Manual creation of OpenSpec structure is error-prone and creates adoption friction. A standardized init command ensures: diff --git a/src/cli/index.ts b/src/cli/index.ts index 780ba1d8..9c64f73e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -41,7 +41,8 @@ program .command('init [path]') .description('Initialize OpenSpec in your project') .option('--tools ', toolsOptionDescription) - .action(async (targetPath = '.', options?: { tools?: string }) => { + .option('--language ', 'Set language for generated content (e.g., en-US, zh-CN, fr-FR, ja-JP, ar-SA). Default: en-US') + .action(async (targetPath = '.', options?: { tools?: string; language?: string }) => { try { // Validate that the path is a valid directory const resolvedPath = path.resolve(targetPath); @@ -64,6 +65,7 @@ program const initCommand = new InitCommand({ tools: options?.tools, + language: options?.language, }); await initCommand.execute(targetPath); } catch (error) { diff --git a/src/core/config.ts b/src/core/config.ts index ba60a427..f20b7d76 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -7,8 +7,27 @@ export const OPENSPEC_MARKERS = { export interface OpenSpecConfig { aiTools: string[]; + language?: string; } +export interface LanguageOption { + code: string; + name: string; + nativeName: string; +} + +export const SUPPORTED_LANGUAGES: LanguageOption[] = [ + { code: 'en-US', name: 'English', nativeName: 'English' }, + { code: 'zh-CN', name: 'Chinese', nativeName: '中文' }, + { code: 'fr-FR', name: 'French', nativeName: 'Français' }, + { code: 'ja-JP', name: 'Japanese', nativeName: '日本語' }, + { code: 'ar-SA', name: 'Arabic', nativeName: 'العربية' }, +]; + +export const DEFAULT_LANGUAGE = 'en-US'; + +export const CONFIG_FILE_NAME = 'config.json'; + export interface AIToolOption { name: string; value: string; diff --git a/src/core/configurators/agents.ts b/src/core/configurators/agents.ts index 720bb324..0033b980 100644 --- a/src/core/configurators/agents.ts +++ b/src/core/configurators/agents.ts @@ -9,9 +9,9 @@ export class AgentsStandardConfigurator implements ToolConfigurator { configFileName = 'AGENTS.md'; isAvailable = true; - async configure(projectPath: string, _openspecDir: string): Promise { + async configure(projectPath: string, _openspecDir: string, language: string = 'en-US'): Promise { const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getAgentsStandardTemplate(); + const content = TemplateManager.getAgentsStandardTemplate(language); await FileSystemUtils.updateFileWithMarkers( filePath, diff --git a/src/core/configurators/base.ts b/src/core/configurators/base.ts index 611a2845..8fabcc2c 100644 --- a/src/core/configurators/base.ts +++ b/src/core/configurators/base.ts @@ -2,5 +2,5 @@ export interface ToolConfigurator { name: string; configFileName: string; isAvailable: boolean; - configure(projectPath: string, openspecDir: string): Promise; + configure(projectPath: string, openspecDir: string, language?: string): Promise; } \ No newline at end of file diff --git a/src/core/configurators/claude.ts b/src/core/configurators/claude.ts index 59103a4d..3a3236dd 100644 --- a/src/core/configurators/claude.ts +++ b/src/core/configurators/claude.ts @@ -9,9 +9,9 @@ export class ClaudeConfigurator implements ToolConfigurator { configFileName = 'CLAUDE.md'; isAvailable = true; - async configure(projectPath: string, openspecDir: string): Promise { + async configure(projectPath: string, openspecDir: string, language: string = 'en-US'): Promise { const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getClaudeTemplate(); + const content = TemplateManager.getClaudeTemplate(language); await FileSystemUtils.updateFileWithMarkers( filePath, diff --git a/src/core/configurators/cline.ts b/src/core/configurators/cline.ts index 9f74e851..8753a281 100644 --- a/src/core/configurators/cline.ts +++ b/src/core/configurators/cline.ts @@ -9,9 +9,9 @@ export class ClineConfigurator implements ToolConfigurator { configFileName = 'CLINE.md'; isAvailable = true; - async configure(projectPath: string, openspecDir: string): Promise { + async configure(projectPath: string, openspecDir: string, language: string = 'en-US'): Promise { const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getClineTemplate(); + const content = TemplateManager.getClineTemplate(language); await FileSystemUtils.updateFileWithMarkers( filePath, diff --git a/src/core/configurators/codebuddy.ts b/src/core/configurators/codebuddy.ts index 467bdece..47d89f20 100644 --- a/src/core/configurators/codebuddy.ts +++ b/src/core/configurators/codebuddy.ts @@ -9,9 +9,9 @@ export class CodeBuddyConfigurator implements ToolConfigurator { configFileName = 'CODEBUDDY.md'; isAvailable = true; - async configure(projectPath: string, openspecDir: string): Promise { + async configure(projectPath: string, openspecDir: string, language: string = 'en-US'): Promise { const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getClaudeTemplate(); + const content = TemplateManager.getClaudeTemplate(language); await FileSystemUtils.updateFileWithMarkers( filePath, diff --git a/src/core/configurators/costrict.ts b/src/core/configurators/costrict.ts index 6a1a8d1d..d2ec96d8 100644 --- a/src/core/configurators/costrict.ts +++ b/src/core/configurators/costrict.ts @@ -9,9 +9,9 @@ export class CostrictConfigurator implements ToolConfigurator { configFileName = 'COSTRICT.md'; isAvailable = true; - async configure(projectPath: string, openspecDir: string): Promise { + async configure(projectPath: string, openspecDir: string, language: string = 'en-US'): Promise { const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getCostrictTemplate(); + const content = TemplateManager.getCostrictTemplate(language); await FileSystemUtils.updateFileWithMarkers( filePath, diff --git a/src/core/configurators/qoder.ts b/src/core/configurators/qoder.ts index db5e6dc9..fcaa470e 100644 --- a/src/core/configurators/qoder.ts +++ b/src/core/configurators/qoder.ts @@ -33,13 +33,13 @@ export class QoderConfigurator implements ToolConfigurator { * @param {string} openspecDir - Path to openspec directory (unused but required by interface) * @returns {Promise} Resolves when configuration is complete */ - async configure(projectPath: string, openspecDir: string): Promise { + async configure(projectPath: string, openspecDir: string, language: string = 'en-US'): Promise { // Construct full path to QODER.md at project root const filePath = path.join(projectPath, this.configFileName); // Get Claude-compatible instruction template // This ensures Qoder receives the same high-quality OpenSpec instructions - const content = TemplateManager.getClaudeTemplate(); + const content = TemplateManager.getClaudeTemplate(language); // Write or update file with managed content between markers // This allows future updates to refresh instructions automatically diff --git a/src/core/configurators/qwen.ts b/src/core/configurators/qwen.ts index 417b6ab7..1aeccf83 100644 --- a/src/core/configurators/qwen.ts +++ b/src/core/configurators/qwen.ts @@ -33,9 +33,9 @@ export class QwenConfigurator implements ToolConfigurator { * @param {string} _openspecDir - The path to the openspec directory (unused) * @returns {Promise} A promise that resolves when configuration is complete */ - async configure(projectPath: string, _openspecDir: string): Promise { + async configure(projectPath: string, _openspecDir: string, language: string = 'en-US'): Promise { const filePath = path.join(projectPath, this.configFileName); - const content = TemplateManager.getAgentsStandardTemplate(); + const content = TemplateManager.getAgentsStandardTemplate(language); await FileSystemUtils.updateFileWithMarkers( filePath, diff --git a/src/core/configurators/slash/base.ts b/src/core/configurators/slash/base.ts index beffd845..bd1c6651 100644 --- a/src/core/configurators/slash/base.ts +++ b/src/core/configurators/slash/base.ts @@ -22,11 +22,11 @@ export abstract class SlashCommandConfigurator { })); } - async generateAll(projectPath: string, _openspecDir: string): Promise { + async generateAll(projectPath: string, _openspecDir: string, language: string = 'en-US'): Promise { const createdOrUpdated: string[] = []; for (const target of this.getTargets()) { - const body = this.getBody(target.id); + const body = this.getBody(target.id, language); const filePath = FileSystemUtils.joinPath(projectPath, target.path); if (await FileSystemUtils.fileExists(filePath)) { @@ -48,13 +48,13 @@ export abstract class SlashCommandConfigurator { return createdOrUpdated; } - async updateExisting(projectPath: string, _openspecDir: string): Promise { + async updateExisting(projectPath: string, _openspecDir: string, language: string = 'en-US'): Promise { const updated: string[] = []; for (const target of this.getTargets()) { const filePath = FileSystemUtils.joinPath(projectPath, target.path); if (await FileSystemUtils.fileExists(filePath)) { - const body = this.getBody(target.id); + const body = this.getBody(target.id, language); await this.updateBody(filePath, body); updated.push(target.path); } @@ -66,8 +66,8 @@ export abstract class SlashCommandConfigurator { protected abstract getRelativePath(id: SlashCommandId): string; protected abstract getFrontmatter(id: SlashCommandId): string | undefined; - protected getBody(id: SlashCommandId): string { - return TemplateManager.getSlashCommandBody(id).trim(); + protected getBody(id: SlashCommandId, language: string = 'en-US'): string { + return TemplateManager.getSlashCommandBody(id, language).trim(); } // Resolve absolute path for a given slash command target. Subclasses may override diff --git a/src/core/i18n/index.ts b/src/core/i18n/index.ts new file mode 100644 index 00000000..f154fc4f --- /dev/null +++ b/src/core/i18n/index.ts @@ -0,0 +1,77 @@ +import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, LanguageOption } from '../config.js'; + +export type LanguageCode = string; + +export interface I18nResources { + [key: string]: string | I18nResources; +} + +// Language resource maps +const resources: Record = { + 'en-US': {}, + 'zh-CN': {}, + 'fr-FR': {}, + 'ja-JP': {}, + 'ar-SA': {}, +}; + +export function getLanguage(languageCode?: string): LanguageCode { + if (!languageCode) { + return DEFAULT_LANGUAGE; + } + + const normalized = languageCode.toLowerCase(); + const language = SUPPORTED_LANGUAGES.find( + (lang) => lang.code.toLowerCase() === normalized + ); + + return language?.code || DEFAULT_LANGUAGE; +} + +export function t(key: string, language: LanguageCode = DEFAULT_LANGUAGE, params?: Record): string { + const lang = getLanguage(language); + const langResources = resources[lang] || resources[DEFAULT_LANGUAGE]; + + // Simple key lookup (supports nested keys with dot notation) + const keys = key.split('.'); + let value: any = langResources; + + for (const k of keys) { + if (value && typeof value === 'object' && k in value) { + value = value[k]; + } else { + // Fallback to default language + value = resources[DEFAULT_LANGUAGE]; + for (const fallbackKey of keys) { + if (value && typeof value === 'object' && fallbackKey in value) { + value = value[fallbackKey]; + } else { + return key; // Return key if translation not found + } + } + break; + } + } + + if (typeof value !== 'string') { + return key; + } + + // Simple parameter substitution + if (params) { + return value.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => { + return params[paramKey] || match; + }); + } + + return value; +} + +// For now, return English templates by default +// This will be expanded with actual translations later +export function getTemplate(templateName: string, language: LanguageCode = DEFAULT_LANGUAGE): string { + // For initial implementation, return English templates + // Multi-language templates will be added in subsequent steps + return ''; +} + diff --git a/src/core/init.ts b/src/core/init.ts index 05e9be42..dd599951 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -22,6 +22,10 @@ import { OPENSPEC_DIR_NAME, AIToolOption, OPENSPEC_MARKERS, + SUPPORTED_LANGUAGES, + DEFAULT_LANGUAGE, + CONFIG_FILE_NAME, + LanguageOption, } from './config.js'; import { PALETTE } from './styles/palette.js'; @@ -371,15 +375,18 @@ const toolSelectionWizard = createPrompt( type InitCommandOptions = { prompt?: ToolSelectionPrompt; tools?: string; + language?: string; }; export class InitCommand { private readonly prompt: ToolSelectionPrompt; private readonly toolsArg?: string; + private readonly languageArg?: string; constructor(options: InitCommandOptions = {}) { this.prompt = options.prompt ?? ((config) => toolSelectionWizard(config)); this.toolsArg = options.tools; + this.languageArg = options.language; } async execute(targetPath: string): Promise { @@ -393,8 +400,21 @@ export class InitCommand { this.renderBanner(extendMode); + // Read existing config before language selection to detect language changes + const existingConfigBeforeLanguageChange = extendMode + ? await this.readConfigFile(openspecPath) + : null; + + // Get language configuration first (before AI tool selection) + const selectedLanguage = await this.getSelectedLanguage(openspecPath, extendMode); + + // Save language configuration + if (selectedLanguage) { + await this.saveConfigFile(openspecPath, { language: selectedLanguage }); + } + // Get configuration (after validation to avoid prompts if validation fails) - const config = await this.getConfiguration(existingToolStates, extendMode); + const config = await this.getConfiguration(existingToolStates, extendMode, openspecPath); const availableTools = AI_TOOLS.filter((tool) => tool.available); const selectedIds = new Set(config.aiTools); @@ -432,15 +452,17 @@ export class InitCommand { ) ); await this.createDirectoryStructure(openspecPath); - await this.ensureTemplateFiles(openspecPath, config); + await this.ensureTemplateFiles(openspecPath, config, existingConfigBeforeLanguageChange); } // Step 2: Configure AI tools const toolSpinner = this.startSpinner('Configuring AI tools...'); + const language = config.language || DEFAULT_LANGUAGE; const rootStubStatus = await this.configureAITools( projectPath, openspecDir, - config.aiTools + config.aiTools, + language ); toolSpinner.stopAndPersist({ symbol: PALETTE.white('▌'), @@ -474,10 +496,96 @@ export class InitCommand { private async getConfiguration( existingTools: Record, - extendMode: boolean + extendMode: boolean, + openspecPath: string ): Promise { + // Language is already selected and saved, read it from config + const config = await this.readConfigFile(openspecPath); + const selectedLanguage = config?.language || DEFAULT_LANGUAGE; const selectedTools = await this.getSelectedTools(existingTools, extendMode); - return { aiTools: selectedTools }; + return { aiTools: selectedTools, language: selectedLanguage }; + } + + private async getSelectedLanguage( + openspecPath: string, + extendMode: boolean + ): Promise { + // Non-interactive mode - check for --language flag first + const nonInteractiveLanguage = this.resolveLanguageArg(); + if (nonInteractiveLanguage !== null) { + return nonInteractiveLanguage; + } + + // Try to read existing config for default value + let defaultLanguage = DEFAULT_LANGUAGE; + if (extendMode) { + const existingConfig = await this.readConfigFile(openspecPath); + if (existingConfig?.language) { + defaultLanguage = existingConfig.language; + } + } + + // Interactive mode - prompt for language with default + return this.promptForLanguage(defaultLanguage); + } + + private resolveLanguageArg(): string | null { + if (typeof this.languageArg === 'undefined') { + return null; + } + + const raw = this.languageArg.trim(); + if (raw.length === 0) { + throw new Error( + `The --language option requires a value. Supported languages: ${SUPPORTED_LANGUAGES.map(l => l.code).join(', ')}` + ); + } + + const normalized = raw.toLowerCase(); + const language = SUPPORTED_LANGUAGES.find( + (lang) => lang.code.toLowerCase() === normalized + ); + + if (!language) { + const availableCodes = SUPPORTED_LANGUAGES.map((l) => l.code).join(', '); + throw new Error( + `Invalid language code: ${raw}. Supported languages: ${availableCodes}` + ); + } + + return language.code; + } + + private async promptForLanguage(defaultLanguage: string = DEFAULT_LANGUAGE): Promise { + const { select } = await import('@inquirer/prompts'); + + const language = await select({ + message: 'Select your preferred language:', + choices: SUPPORTED_LANGUAGES.map((lang) => ({ + name: `${lang.nativeName} (${lang.code})`, + value: lang.code, + })), + default: defaultLanguage, + }); + + return language; + } + + private async readConfigFile( + openspecPath: string + ): Promise | null> { + const configPath = path.join(openspecPath, CONFIG_FILE_NAME); + return await FileSystemUtils.readJsonFile>(configPath); + } + + private async saveConfigFile( + openspecPath: string, + config: Partial + ): Promise { + const configPath = path.join(openspecPath, CONFIG_FILE_NAME); + const existingConfig = await this.readConfigFile(openspecPath) || {}; + const mergedConfig = { ...existingConfig, ...config }; + await FileSystemUtils.writeJsonFile(configPath, mergedConfig); } private async getSelectedTools( @@ -727,9 +835,19 @@ export class InitCommand { private async ensureTemplateFiles( openspecPath: string, - config: OpenSpecConfig + config: OpenSpecConfig, + previousConfig: Partial | null = null ): Promise { - await this.writeTemplateFiles(openspecPath, config, true); + // Check if language has changed - if so, regenerate templates + // Use previousConfig if provided (read before language was saved), otherwise read from file + const existingConfig = + previousConfig || (await this.readConfigFile(openspecPath)); + const languageChanged = existingConfig?.language && + existingConfig.language !== config.language; + + // If language changed, force regeneration (skipExisting = false) + // Otherwise, skip existing files to preserve user modifications + await this.writeTemplateFiles(openspecPath, config, !languageChanged); } private async writeTemplateFiles( @@ -741,7 +859,8 @@ export class InitCommand { // Could be enhanced with prompts for project details }; - const templates = TemplateManager.getTemplates(context); + const language = config.language || DEFAULT_LANGUAGE; + const templates = TemplateManager.getTemplates(context, language); for (const template of templates) { const filePath = path.join(openspecPath, template.path); @@ -763,22 +882,24 @@ export class InitCommand { private async configureAITools( projectPath: string, openspecDir: string, - toolIds: string[] + toolIds: string[], + language: string = DEFAULT_LANGUAGE ): Promise { const rootStubStatus = await this.configureRootAgentsStub( projectPath, - openspecDir + openspecDir, + language ); for (const toolId of toolIds) { const configurator = ToolRegistry.get(toolId); if (configurator && configurator.isAvailable) { - await configurator.configure(projectPath, openspecDir); + await configurator.configure(projectPath, openspecDir, language); } const slashConfigurator = SlashCommandRegistry.get(toolId); if (slashConfigurator && slashConfigurator.isAvailable) { - await slashConfigurator.generateAll(projectPath, openspecDir); + await slashConfigurator.generateAll(projectPath, openspecDir, language); } } @@ -787,7 +908,8 @@ export class InitCommand { private async configureRootAgentsStub( projectPath: string, - openspecDir: string + openspecDir: string, + language: string = DEFAULT_LANGUAGE ): Promise { const configurator = ToolRegistry.get('agents'); if (!configurator || !configurator.isAvailable) { @@ -797,7 +919,7 @@ export class InitCommand { const stubPath = path.join(projectPath, configurator.configFileName); const existed = await FileSystemUtils.fileExists(stubPath); - await configurator.configure(projectPath, openspecDir); + await configurator.configure(projectPath, openspecDir, language); return existed ? 'updated' : 'created'; } @@ -968,3 +1090,4 @@ export class InitCommand { }).start(); } } + diff --git a/src/core/templates/i18n/ar-SA/agents-root-stub.ts b/src/core/templates/i18n/ar-SA/agents-root-stub.ts new file mode 100644 index 00000000..22a44e50 --- /dev/null +++ b/src/core/templates/i18n/ar-SA/agents-root-stub.ts @@ -0,0 +1,17 @@ +export const agentsRootStubTemplate = `# تعليمات OpenSpec + +هذه التعليمات مخصصة لمساعدي الذكاء الاصطناعي الذين يعملون في هذا المشروع. + +افتح دائمًا \`@/openspec/AGENTS.md\` عندما يكون الطلب: +- يذكر التخطيط أو المقترحات (كلمات مثل proposal، spec، change، plan) +- يقدم قدرات جديدة، أو تغييرات كبيرة، أو تحولات في البنية، أو أعمال أداء/أمان كبيرة +- يبدو غامضًا وتحتاج إلى المواصفات الرسمية قبل الترميز + +استخدم \`@/openspec/AGENTS.md\` لمعرفة: +- كيفية إنشاء وتطبيق مقترحات التغيير +- تنسيق المواصفات والاتفاقيات +- هيكل المشروع والإرشادات + +احتفظ بهذه الكتلة المدارة حتى يتمكن 'openspec update' من تحديث التعليمات. +`; + diff --git a/src/core/templates/i18n/ar-SA/agents-template.ts b/src/core/templates/i18n/ar-SA/agents-template.ts new file mode 100644 index 00000000..b10c98af --- /dev/null +++ b/src/core/templates/i18n/ar-SA/agents-template.ts @@ -0,0 +1,457 @@ +export const agentsTemplate = `# تعليمات OpenSpec + +تعليمات لمساعدي الترميز بالذكاء الاصطناعي الذين يستخدمون OpenSpec للتطوير القائم على المواصفات. + +## قائمة التحقق السريعة + +- البحث عن العمل الموجود: \`openspec spec list --long\`، \`openspec list\` (استخدم \`rg\` فقط للبحث النصي الكامل) +- تحديد النطاق: قدرة جديدة مقابل تعديل قدرة موجودة +- اختيار \`change-id\` فريد: kebab-case، بقيادة فعل (\`add-\`، \`update-\`، \`remove-\`، \`refactor-\`) +- السقالة: \`proposal.md\`، \`tasks.md\`، \`design.md\` (فقط عند الحاجة)، ومواصفات دلتا لكل قدرة متأثرة +- كتابة الدلتا: استخدم \`## ADDED|MODIFIED|REMOVED|RENAMED Requirements\`؛ قم بتضمين سيناريو واحد على الأقل \`#### Scenario:\` لكل متطلب +- التحقق: \`openspec validate [change-id] --strict\` وإصلاح المشكلات +- طلب الموافقة: لا تبدأ التنفيذ حتى يتم الموافقة على المقترح + +## سير العمل المكون من ثلاث مراحل + +### المرحلة 1: إنشاء التغييرات +قم بإنشاء مقترح عندما تحتاج إلى: +- إضافة ميزات أو وظائف +- إجراء تغييرات كبيرة (API، المخطط) +- تغيير البنية أو الأنماط +- تحسين الأداء (يغير السلوك) +- تحديث أنماط الأمان + +المحفزات (أمثلة): +- "ساعدني في إنشاء مقترح تغيير" +- "ساعدني في تخطيط تغيير" +- "أريد إنشاء مقترح" +- "أريد إنشاء مقترح مواصفات" +- "أريد إنشاء مواصفات" + +إرشادات المطابقة الفضفاضة: +- يحتوي على أحد: \`proposal\`، \`change\`، \`spec\` +- مع أحد: \`create\`، \`plan\`، \`make\`، \`start\`، \`help\` + +تخطي المقترح لـ: +- إصلاحات الأخطاء (استعادة السلوك المقصود) +- الأخطاء الإملائية، التنسيق، التعليقات +- تحديثات التبعيات (غير كبيرة) +- تغييرات التكوين +- اختبارات للسلوك الموجود + +**سير العمل** +1. راجع \`openspec/project.md\`، \`openspec list\`، و \`openspec list --specs\` لفهم السياق الحالي. +2. اختر \`change-id\` فريد بقيادة فعل وأنشئ \`proposal.md\`، \`tasks.md\`، \`design.md\` اختياري، ودلتا المواصفات تحت \`openspec/changes//\`. +3. اكتب دلتا المواصفات باستخدام \`## ADDED|MODIFIED|REMOVED Requirements\` مع سيناريو واحد على الأقل \`#### Scenario:\` لكل متطلب. +4. قم بتشغيل \`openspec validate --strict\` وحل جميع المشكلات قبل مشاركة المقترح. + +### المرحلة 2: تنفيذ التغييرات +تتبع هذه الخطوات كـ TODOs وأكملها واحدة تلو الأخرى. +1. **اقرأ proposal.md** - افهم ما يتم بناؤه +2. **اقرأ design.md** (إن وجد) - راجع القرارات التقنية +3. **اقرأ tasks.md** - احصل على قائمة التحقق للتنفيذ +4. **نفذ المهام بالتسلسل** - أكمل بالترتيب +5. **أكد الإكمال** - تأكد من أن كل عنصر في \`tasks.md\` مكتمل قبل تحديث الحالات +6. **حدّث قائمة التحقق** - بعد اكتمال كل العمل، اضبط كل مهمة على \`- [x]\` بحيث تعكس القائمة الواقع +7. **بوابة الموافقة** - لا تبدأ التنفيذ حتى يتم مراجعة المقترح والموافقة عليه + +### المرحلة 3: أرشفة التغييرات +بعد النشر، قم بإنشاء PR منفصل لـ: +- نقل \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\` +- تحديث \`specs/\` إذا تغيرت القدرات +- استخدم \`openspec archive --skip-specs --yes\` للتغييرات المتعلقة بالأدوات فقط (مرر معرف التغيير دائمًا بشكل صريح) +- قم بتشغيل \`openspec validate --strict\` لتأكيد أن التغيير المؤرشف يمر الفحوصات + +## قبل أي مهمة + +**قائمة التحقق السياقية:** +- [ ] اقرأ المواصفات ذات الصلة في \`specs/[capability]/spec.md\` +- [ ] تحقق من التغييرات المعلقة في \`changes/\` للتعارضات +- [ ] اقرأ \`openspec/project.md\` للاتفاقيات +- [ ] قم بتشغيل \`openspec list\` لرؤية التغييرات النشطة +- [ ] قم بتشغيل \`openspec list --specs\` لرؤية القدرات الموجودة + +**قبل إنشاء المواصفات:** +- تحقق دائمًا مما إذا كانت القدرة موجودة بالفعل +- فضّل تعديل المواصفات الموجودة على إنشاء مكررات +- استخدم \`openspec show [spec]\` لمراجعة الحالة الحالية +- إذا كان الطلب غامضًا، اطرح 1-2 أسئلة توضيحية قبل السقالة + +### إرشادات البحث +- تعداد المواصفات: \`openspec spec list --long\` (أو \`--json\` للنصوص البرمجية) +- تعداد التغييرات: \`openspec list\` (أو \`openspec change list --json\` - مهجور ولكن متاح) +- عرض التفاصيل: + - المواصفات: \`openspec show --type spec\` (استخدم \`--json\` للمرشحات) + - التغيير: \`openspec show --json --deltas-only\` +- البحث النصي الكامل (استخدم ripgrep): \`rg -n "Requirement:|Scenario:" openspec/specs\` + +## البدء السريع + +### أوامر CLI + +\`\`\`bash +# الأوامر الأساسية +openspec list # قائمة التغييرات النشطة +openspec list --specs # قائمة المواصفات +openspec show [item] # عرض التغيير أو المواصفات +openspec validate [item] # التحقق من التغييرات أو المواصفات +openspec archive [--yes|-y] # أرشفة بعد النشر (أضف --yes للتشغيل غير التفاعلي) + +# إدارة المشروع +openspec init [path] # تهيئة OpenSpec +openspec update [path] # تحديث ملفات التعليمات + +# الوضع التفاعلي +openspec show # مطالبات للاختيار +openspec validate # وضع التحقق المجمع + +# التصحيح +openspec show [change] --json --deltas-only +openspec validate [change] --strict +\`\`\` + +### أعلام الأوامر + +- \`--json\` - إخراج قابل للقراءة آليًا +- \`--type change|spec\` - إزالة الغموض من العناصر +- \`--strict\` - التحقق الشامل +- \`--no-interactive\` - تعطيل المطالبات +- \`--skip-specs\` - أرشفة دون تحديثات المواصفات +- \`--yes\`/\`-y\` - تخطي مطالبات التأكيد (أرشفة غير تفاعلية) + +## هيكل الدليل + +\`\`\` +openspec/ +├── project.md # اتفاقيات المشروع +├── specs/ # الحقيقة الحالية - ما تم بناؤه +│ └── [capability]/ # قدرة واحدة مركزة +│ ├── spec.md # المتطلبات والسيناريوهات +│ └── design.md # الأنماط التقنية +├── changes/ # المقترحات - ما يجب تغييره +│ ├── [change-name]/ +│ │ ├── proposal.md # لماذا، ماذا، التأثير +│ │ ├── tasks.md # قائمة التحقق للتنفيذ +│ │ ├── design.md # القرارات التقنية (اختياري؛ انظر المعايير) +│ │ └── specs/ # تغييرات دلتا +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # التغييرات المكتملة +\`\`\` + +## إنشاء مقترحات التغيير + +### شجرة القرار + +\`\`\` +طلب جديد؟ +├─ إصلاح خطأ يستعيد سلوك المواصفات؟ → إصلاح مباشر +├─ خطأ إملائي/تنسيق/تعليق؟ → إصلاح مباشر +├─ ميزة/قدرة جديدة؟ → إنشاء مقترح +├─ تغيير كبير؟ → إنشاء مقترح +├─ تغيير في البنية؟ → إنشاء مقترح +└─ غير واضح؟ → إنشاء مقترح (أكثر أمانًا) +\`\`\` + +### هيكل المقترح + +1. **إنشاء الدليل:** \`changes/[change-id]/\` (kebab-case، بقيادة فعل، فريد) + +2. **كتابة proposal.md:** +\`\`\`markdown +# التغيير: [وصف موجز للتغيير] + +## لماذا +[1-2 جملة حول المشكلة/الفرصة] + +## ما الذي يتغير +- [قائمة نقطية للتغييرات] +- [ضع علامة على التغييرات الكبيرة بـ **BREAKING**] + +## التأثير +- المواصفات المتأثرة: [اذكر القدرات] +- الكود المتأثر: [الملفات/الأنظمة الرئيسية] +\`\`\` + +3. **إنشاء دلتا المواصفات:** \`specs/[capability]/spec.md\` +\`\`\`markdown +## ADDED Requirements +### Requirement: ميزة جديدة +يجب أن يوفر النظام... + +#### Scenario: حالة النجاح +- **WHEN** يقوم المستخدم بإجراء +- **THEN** النتيجة المتوقعة + +## MODIFIED Requirements +### Requirement: ميزة موجودة +[المتطلب المعدل الكامل] + +## REMOVED Requirements +### Requirement: ميزة قديمة +**السبب**: [لماذا الإزالة] +**الترحيل**: [كيفية التعامل] +\`\`\` +إذا تأثرت قدرات متعددة، قم بإنشاء ملفات دلتا متعددة تحت \`changes/[change-id]/specs//spec.md\`—واحد لكل قدرة. + +4. **إنشاء tasks.md:** +\`\`\`markdown +## 1. التنفيذ +- [ ] 1.1 إنشاء مخطط قاعدة البيانات +- [ ] 1.2 تنفيذ نقطة نهاية API +- [ ] 1.3 إضافة مكون الواجهة الأمامية +- [ ] 1.4 كتابة الاختبارات +\`\`\` + +5. **إنشاء design.md عند الحاجة:** +قم بإنشاء \`design.md\` إذا كان أي مما يلي ينطبق؛ وإلا فاحذفه: +- تغيير عابر (خدمات/وحدات متعددة) أو نمط معماري جديد +- تبعية خارجية جديدة أو تغييرات كبيرة في نموذج البيانات +- تعقيد الأمان أو الأداء أو الترحيل +- غموض يستفيد من القرارات التقنية قبل الترميز + +هيكل \`design.md\` الأدنى: +\`\`\`markdown +## السياق +[الخلفية، القيود، أصحاب المصلحة] + +## الأهداف / غير الأهداف +- الأهداف: [...] +- غير الأهداف: [...] + +## القرارات +- القرار: [ماذا ولماذا] +- البدائل المدروسة: [الخيارات + المبرر] + +## المخاطر / المقايضات +- [المخاطر] → التخفيف + +## خطة الترحيل +[الخطوات، التراجع] + +## الأسئلة المفتوحة +- [...] +\`\`\` + +## تنسيق ملف المواصفات + +### حرج: تنسيق السيناريو + +**صحيح** (استخدم رؤوس ####): +\`\`\`markdown +#### Scenario: نجاح تسجيل دخول المستخدم +- **WHEN** يتم توفير بيانات اعتماد صالحة +- **THEN** إرجاع رمز JWT +\`\`\` + +**خطأ** (لا تستخدم نقاط أو نص عريض): +\`\`\`markdown +- **Scenario: تسجيل دخول المستخدم** ❌ +**Scenario**: تسجيل دخول المستخدم ❌ +### Scenario: تسجيل دخول المستخدم ❌ +\`\`\` + +يجب أن يكون لكل متطلب سيناريو واحد على الأقل. + +### صياغة المتطلبات +- استخدم SHALL/MUST للمتطلبات المعيارية (تجنب should/may ما لم يكن غير معياري عن قصد) + +### عمليات دلتا + +- \`## ADDED Requirements\` - قدرات جديدة +- \`## MODIFIED Requirements\` - سلوك معدل +- \`## REMOVED Requirements\` - ميزات مهجورة +- \`## RENAMED Requirements\` - تغييرات الأسماء + +رؤوس مطابقة لـ \`trim(header)\` - يتم تجاهل المسافات البيضاء. + +#### متى تستخدم ADDED مقابل MODIFIED +- ADDED: يقدم قدرة جديدة أو قدرة فرعية يمكن أن تقف بمفردها كمتطلب. فضّل ADDED عندما يكون التغيير متعامدًا (على سبيل المثال، إضافة "تكوين أمر slash") بدلاً من تغيير دلالات متطلب موجود. +- MODIFIED: يغير السلوك أو النطاق أو معايير القبول لمتطلب موجود. الصق دائمًا محتوى المتطلب الكامل والمحدث (الرأس + جميع السيناريوهات). سيستبدل الأرشيف المتطلب بالكامل بما توفره هنا؛ ستسقط دلتا الجزئية التفاصيل السابقة. +- RENAMED: استخدم عندما يتغير الاسم فقط. إذا قمت أيضًا بتغيير السلوك، استخدم RENAMED (الاسم) بالإضافة إلى MODIFIED (المحتوى) مع الإشارة إلى الاسم الجديد. + +فخ شائع: استخدام MODIFIED لإضافة قلق جديد دون تضمين النص السابق. يتسبب هذا في فقدان التفاصيل في وقت الأرشفة. إذا لم تقم بتغيير المتطلب الموجود صراحة، أضف متطلبًا جديدًا تحت ADDED بدلاً من ذلك. + +كتابة متطلب MODIFIED بشكل صحيح: +1) حدد موقع المتطلب الموجود في \`openspec/specs//spec.md\`. +2) انسخ كتلة المتطلب بالكامل (من \`### Requirement: ...\` عبر سيناريوهاته). +3) الصقه تحت \`## MODIFIED Requirements\` وقم بتحريره ليعكس السلوك الجديد. +4) تأكد من تطابق نص الرأس تمامًا (غير حساس للمسافات البيضاء) واحتفظ بسيناريو واحد على الأقل \`#### Scenario:\`. + +مثال لـ RENAMED: +\`\`\`markdown +## RENAMED Requirements +- FROM: \`### Requirement: Login\` +- TO: \`### Requirement: User Authentication\` +\`\`\` + +## استكشاف الأخطاء وإصلاحها + +### الأخطاء الشائعة + +**"يجب أن يكون للتغيير دلتا واحدة على الأقل"** +- تحقق من وجود \`changes/[name]/specs/\` مع ملفات .md +- تحقق من أن الملفات تحتوي على بادئات العمل (## ADDED Requirements) + +**"يجب أن يكون للمتطلب سيناريو واحد على الأقل"** +- تحقق من أن السيناريوهات تستخدم تنسيق \`#### Scenario:\` (4 علامات #) +- لا تستخدم نقاط أو نص عريض لرؤوس السيناريو + +**فشل تحليل السيناريو الصامت** +- التنسيق الدقيق مطلوب: \`#### Scenario: Name\` +- تصحيح مع: \`openspec show [change] --json --deltas-only\` + +### نصائح التحقق + +\`\`\`bash +# استخدم دائمًا الوضع الصارم للفحوصات الشاملة +openspec validate [change] --strict + +# تصحيح تحليل دلتا +openspec show [change] --json | jq '.deltas' + +# تحقق من متطلب محدد +openspec show [spec] --json -r 1 +\`\`\` + +## سكريبت المسار السعيد + +\`\`\`bash +# 1) استكشف الحالة الحالية +openspec spec list --long +openspec list +# بحث نصي كامل اختياري: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) اختر معرف التغيير وأنشئ السقالة +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## لماذا\\n...\\n\\n## ما الذي يتغير\\n- ...\\n\\n## التأثير\\n- ...\\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. التنفيذ\\n- [ ] 1.1 ...\\n" > openspec/changes/$CHANGE/tasks.md + +# 3) أضف الدلتا (مثال) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: المصادقة الثنائية +يجب على المستخدمين توفير عامل ثانٍ أثناء تسجيل الدخول. + +#### Scenario: OTP مطلوب +- **WHEN** يتم توفير بيانات اعتماد صالحة +- **THEN** تحدي OTP مطلوب +EOF + +# 4) التحقق +openspec validate $CHANGE --strict +\`\`\` + +## مثال متعدد القدرات + +\`\`\` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: المصادقة الثنائية + └── notifications/ + └── spec.md # ADDED: إشعار OTP بالبريد الإلكتروني +\`\`\` + +auth/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: المصادقة الثنائية +... +\`\`\` + +notifications/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: إشعار OTP بالبريد الإلكتروني +... +\`\`\` + +## أفضل الممارسات + +### البساطة أولاً +- افتراضيًا <100 سطر من الكود الجديد +- تنفيذات ملف واحد حتى يثبت عدم كفايتها +- تجنب الأطر بدون مبرر واضح +- اختر أنماطًا مملة ومثبتة + +### محفزات التعقيد +أضف التعقيد فقط مع: +- بيانات الأداء التي تظهر أن الحل الحالي بطيء جدًا +- متطلبات الحجم الملموسة (>1000 مستخدم، >100MB بيانات) +- حالات استخدام متعددة مثبتة تتطلب التجريد + +### المراجع الواضحة +- استخدم تنسيق \`file.ts:42\` لمواقع الكود +- راجع المواصفات كـ \`specs/auth/spec.md\` +- اربط التغييرات و PR ذات الصلة + +### تسمية القدرات +- استخدم فعل-اسم: \`user-auth\`، \`payment-capture\` +- غرض واحد لكل قدرة +- قاعدة قابلية الفهم في 10 دقائق +- انقسام إذا كانت الوصف يحتاج "AND" + +### تسمية معرف التغيير +- استخدم kebab-case، قصير ووصفي: \`add-two-factor-auth\` +- فضّل البادئات بقيادة الفعل: \`add-\`، \`update-\`، \`remove-\`، \`refactor-\` +- تأكد من التفرد؛ إذا تم أخذه، أضف \`-2\`، \`-3\`، إلخ + +## دليل اختيار الأدوات + +| المهمة | الأداة | لماذا | +|------|------|-----| +| العثور على الملفات حسب النمط | Glob | مطابقة النمط السريع | +| البحث في محتوى الكود | Grep | بحث regex محسّن | +| قراءة ملفات محددة | Read | الوصول المباشر للملف | +| استكشاف نطاق غير معروف | Task | تحقيق متعدد الخطوات | + +## استعادة الخطأ + +### تعارضات التغيير +1. قم بتشغيل \`openspec list\` لرؤية التغييرات النشطة +2. تحقق من المواصفات المتداخلة +3. تنسيق مع مالكي التغيير +4. النظر في دمج المقترحات + +### فشل التحقق +1. قم بتشغيل مع العلم \`--strict\` +2. تحقق من إخراج JSON للتفاصيل +3. تحقق من تنسيق ملف المواصفات +4. تأكد من تنسيق السيناريوهات بشكل صحيح + +### نقص السياق +1. اقرأ project.md أولاً +2. تحقق من المواصفات ذات الصلة +3. راجع الأرشيفات الأخيرة +4. اطلب التوضيح + +## مرجع سريع + +### مؤشرات المرحلة +- \`changes/\` - مقترح، لم يتم بناؤه بعد +- \`specs/\` - تم بناؤه ونشره +- \`archive/\` - التغييرات المكتملة + +### أغراض الملفات +- \`proposal.md\` - لماذا وماذا +- \`tasks.md\` - خطوات التنفيذ +- \`design.md\` - القرارات التقنية +- \`spec.md\` - المتطلبات والسلوك + +### أساسيات CLI +\`\`\`bash +openspec list # ما الذي قيد التقدم؟ +openspec show [item] # عرض التفاصيل +openspec validate --strict # هل هو صحيح؟ +openspec archive [--yes|-y] # وضع علامة كمكتمل (أضف --yes للأتمتة) +\`\`\` + +تذكر: المواصفات هي الحقيقة. التغييرات هي مقترحات. حافظ على مزامنتها. +`; diff --git a/src/core/templates/i18n/ar-SA/project-template.ts b/src/core/templates/i18n/ar-SA/project-template.ts new file mode 100644 index 00000000..38a9fc5a --- /dev/null +++ b/src/core/templates/i18n/ar-SA/project-template.ts @@ -0,0 +1,34 @@ +import { ProjectContext } from '../../project-template.js'; + +export const projectTemplate = (context: ProjectContext = {}) => `# سياق ${context.projectName || 'المشروع'} + +## الهدف +${context.description || '[اوصف الغرض والأهداف لمشروعك]'} + +## المكدس التقني +${context.techStack?.length ? context.techStack.map(tech => `- ${tech}`).join('\n') : '- [اذكر التقنيات الأساسية]\n- [مثال: TypeScript, React, Node.js]'} + +## اتفاقيات المشروع + +### نمط الكود +[اوصف تفضيلات نمط الكود، وقواعد التنسيق، واتفاقيات التسمية] + +### أنماط البنية +[وثّق قراراتك وأنماطك المعمارية] + +### استراتيجية الاختبار +[اشرح نهجك ومتطلباتك للاختبار] + +### سير عمل Git +[اوصف استراتيجية الفروع واتفاقيات الالتزام] + +## سياق المجال +[أضف المعرفة الخاصة بالمجال التي يحتاج مساعدو الذكاء الاصطناعي إلى فهمها] + +## القيود المهمة +[اذكر أي قيود تقنية أو تجارية أو تنظيمية] + +## التبعيات الخارجية +[وثّق الخدمات الخارجية أو واجهات برمجة التطبيقات أو الأنظمة الرئيسية] +`; + diff --git a/src/core/templates/i18n/ar-SA/slash-command-templates.ts b/src/core/templates/i18n/ar-SA/slash-command-templates.ts new file mode 100644 index 00000000..05f8b236 --- /dev/null +++ b/src/core/templates/i18n/ar-SA/slash-command-templates.ts @@ -0,0 +1,59 @@ +import { SlashCommandId } from '../../slash-command-templates.js'; + +const baseGuardrails = `**القيود** +- افضل التنفيذات البسيطة والدنيا أولاً وأضف التعقيد فقط عندما يُطلب أو يكون مطلوبًا بوضوح. +- حافظ على التغييرات محدودة النطاق بشكل وثيق على النتيجة المطلوبة. +- راجع \`openspec/AGENTS.md\` (موجود داخل دليل \`openspec/\`—قم بتشغيل \`ls openspec\` أو \`openspec update\` إذا لم تراه) إذا كنت بحاجة إلى اتفاقيات أو توضيحات OpenSpec إضافية.`; + +const proposalGuardrails = `${baseGuardrails}\n- حدد أي تفاصيل غامضة أو غير واضحة واطرح أسئلة المتابعة اللازمة قبل تحرير الملفات.`; + +const proposalSteps = `**الخطوات** +1. راجع \`openspec/project.md\`، وقم بتشغيل \`openspec list\` و \`openspec list --specs\`، وافحص الكود أو الوثائق ذات الصلة (مثلًا عبر \`rg\`/\`ls\`) لفهم السلوك الحالي؛ لاحظ أي فجوات تتطلب توضيحًا. +2. اختر \`change-id\` فريدًا بقيادة فعل وأنشئ \`proposal.md\` و \`tasks.md\` و \`design.md\` (عند الحاجة) تحت \`openspec/changes//\`. +3. اربط التغيير بقدرات أو متطلبات ملموسة، وقسم الجهود متعددة النطاق إلى دلتا مواصفات متميزة بعلاقات وترتيب واضح. +4. التقط المنطق المعماري في \`design.md\` عندما يمتد الحل عبر أنظمة متعددة أو يقدم أنماطًا جديدة أو يتطلب مناقشة مقايضة قبل الالتزام بالمواصفات. +5. اكتب دلتا المواصفات في \`changes//specs//spec.md\` (مجلد واحد لكل قدرة) باستخدام \`## ADDED|MODIFIED|REMOVED Requirements\` مع سيناريو واحد على الأقل \`#### Scenario:\` لكل متطلب ومرجع متقاطع للقدرات ذات الصلة عند الصلة. +6. اكتب \`tasks.md\` كقائمة مرتبة لعناصر عمل صغيرة قابلة للتحقق توفر تقدمًا مرئيًا للمستخدم، وتشمل التحقق (الاختبارات، الأدوات)، وتسليط الضوء على التبعيات أو العمل القابل للتوزيع. +7. تحقق باستخدام \`openspec validate --strict\` وحل جميع المشكلات قبل مشاركة المقترح.`; + +const proposalReferences = `**المرجع** +- استخدم \`openspec show --json --deltas-only\` أو \`openspec show --type spec\` لفحص التفاصيل عند فشل التحقق. +- ابحث عن المتطلبات الموجودة باستخدام \`rg -n "Requirement:|Scenario:" openspec/specs\` قبل كتابة متطلبات جديدة. +- استكشف قاعدة الكود باستخدام \`rg \` أو \`ls\` أو قراءات الملفات المباشرة حتى تتماشى المقترحات مع واقع التنفيذ الحالي.`; + +const applySteps = `**الخطوات** +تتبع هذه الخطوات كـ TODOs وأكملها واحدة تلو الأخرى. +1. اقرأ \`changes//proposal.md\` و \`design.md\` (إن وجد) و \`tasks.md\` لتأكيد النطاق ومعايير القبول. +2. اعمل على المهام بالتسلسل، مع الحفاظ على التعديلات بسيطة ومركزة على التغيير المطلوب. +3. أكد الإكمال قبل تحديث الحالات—تأكد من أن كل عنصر في \`tasks.md\` مكتمل. +4. حدّث قائمة التحقق بعد اكتمال كل العمل بحيث يتم تمييز كل مهمة بـ \`- [x]\` وتعكس الواقع. +5. راجع \`openspec list\` أو \`openspec show \` عند الحاجة إلى سياق إضافي.`; + +const applyReferences = `**المرجع** +- استخدم \`openspec show --json --deltas-only\` إذا كنت بحاجة إلى سياق إضافي من المقترح أثناء التنفيذ.`; + +const archiveSteps = `**الخطوات** +1. حدد معرف التغيير للأرشفة: + - إذا كانت هذه المطالبة تتضمن بالفعل معرف تغيير محدد (على سبيل المثال داخل كتلة \`\` مملوءة بوسائط أمر slash)، استخدم تلك القيمة بعد تقليم المسافات البيضاء. + - إذا كانت المحادثة تشير إلى تغيير بشكل فضفاض (على سبيل المثال بالعنوان أو الملخص)، قم بتشغيل \`openspec list\` لعرض معرفات محتملة، وشارك المرشحين ذوي الصلة، وأكد أي واحد ينوي المستخدم استخدامه. + - خلاف ذلك، راجع المحادثة، وقم بتشغيل \`openspec list\`، واسأل المستخدم عن التغيير الذي يجب أرشفته؛ انتظر معرف تغيير مؤكد قبل المتابعة. + - إذا لم تتمكن بعد من تحديد معرف تغيير واحد، توقف وأخبر المستخدم أنه لا يمكنك أرشفة أي شيء بعد. +2. تحقق من معرف التغيير عن طريق تشغيل \`openspec list\` (أو \`openspec show \`) وتوقف إذا كان التغيير مفقودًا أو مؤرشفًا بالفعل أو غير جاهز للأرشفة. +3. قم بتشغيل \`openspec archive --yes\` حتى ينقل CLI التغيير ويطبق تحديثات المواصفات دون مطالبات (استخدم \`--skip-specs\` فقط للعمل المتعلق بالأدوات فقط). +4. راجع إخراج الأمر لتأكيد تحديث المواصفات المستهدفة وهبوط التغيير في \`changes/archive/\`. +5. تحقق باستخدام \`openspec validate --strict\` وافحص باستخدام \`openspec show \` إذا بدا أي شيء غير صحيح.`; + +const archiveReferences = `**المرجع** +- استخدم \`openspec list\` لتأكيد معرفات التغيير قبل الأرشفة. +- افحص المواصفات المحدثة باستخدام \`openspec list --specs\` وتناول أي مشاكل تحقق قبل التسليم.`; + +export const slashCommandBodies: Record = { + proposal: [proposalGuardrails, proposalSteps, proposalReferences].join('\n\n'), + apply: [baseGuardrails, applySteps, applyReferences].join('\n\n'), + archive: [baseGuardrails, archiveSteps, archiveReferences].join('\n\n') +}; + +export function getSlashCommandBody(id: SlashCommandId): string { + return slashCommandBodies[id]; +} + diff --git a/src/core/templates/i18n/fr-FR/agents-root-stub.ts b/src/core/templates/i18n/fr-FR/agents-root-stub.ts new file mode 100644 index 00000000..b38660aa --- /dev/null +++ b/src/core/templates/i18n/fr-FR/agents-root-stub.ts @@ -0,0 +1,17 @@ +export const agentsRootStubTemplate = `# Instructions OpenSpec + +Ces instructions sont destinées aux assistants IA travaillant dans ce projet. + +Toujours ouvrir \`@/openspec/AGENTS.md\` lorsque la demande : +- Mentionne la planification ou les propositions (mots comme proposal, spec, change, plan) +- Introduit de nouvelles capacités, des changements majeurs, des changements d'architecture ou des travaux importants de performance/sécurité +- Semble ambiguë et vous avez besoin de la spécification faisant autorité avant de coder + +Utiliser \`@/openspec/AGENTS.md\` pour apprendre : +- Comment créer et appliquer des propositions de changement +- Le format et les conventions de spécification +- La structure du projet et les directives + +Conserver ce bloc géré pour que 'openspec update' puisse actualiser les instructions. +`; + diff --git a/src/core/templates/i18n/fr-FR/agents-template.ts b/src/core/templates/i18n/fr-FR/agents-template.ts new file mode 100644 index 00000000..6f92ab89 --- /dev/null +++ b/src/core/templates/i18n/fr-FR/agents-template.ts @@ -0,0 +1,457 @@ +export const agentsTemplate = `# Instructions OpenSpec + +Instructions pour les assistants de codage IA utilisant OpenSpec pour le développement piloté par spécifications. + +## Liste de contrôle rapide + +- Rechercher le travail existant : \`openspec spec list --long\`, \`openspec list\` (utiliser \`rg\` uniquement pour la recherche en texte intégral) +- Décider de la portée : nouvelle capacité vs modifier une capacité existante +- Choisir un \`change-id\` unique : kebab-case, mené par un verbe (\`add-\`, \`update-\`, \`remove-\`, \`refactor-\`) +- Échafaudage : \`proposal.md\`, \`tasks.md\`, \`design.md\` (seulement si nécessaire), et spécifications delta par capacité affectée +- Écrire les deltas : utiliser \`## ADDED|MODIFIED|REMOVED|RENAMED Requirements\` ; inclure au moins un \`#### Scenario:\` par exigence +- Valider : \`openspec validate [change-id] --strict\` et corriger les problèmes +- Demander l'approbation : Ne pas commencer l'implémentation tant que la proposition n'est pas approuvée + +## Workflow en trois étapes + +### Étape 1 : Création de changements +Créer une proposition lorsque vous devez : +- Ajouter des fonctionnalités ou des capacités +- Effectuer des changements majeurs (API, schéma) +- Changer l'architecture ou les modèles +- Optimiser les performances (change le comportement) +- Mettre à jour les modèles de sécurité + +Déclencheurs (exemples) : +- "Aidez-moi à créer une proposition de changement" +- "Aidez-moi à planifier un changement" +- "Je veux créer une proposition" +- "Je veux créer une proposition de spécification" +- "Je veux créer une spécification" + +Guide de correspondance approximative : +- Contient l'un des : \`proposal\`, \`change\`, \`spec\` +- Avec l'un des : \`create\`, \`plan\`, \`make\`, \`start\`, \`help\` + +Ignorer la proposition pour : +- Corrections de bugs (restaurer le comportement prévu) +- Fautes de frappe, formatage, commentaires +- Mises à jour de dépendances (non majeures) +- Changements de configuration +- Tests pour le comportement existant + +**Workflow** +1. Examiner \`openspec/project.md\`, \`openspec list\`, et \`openspec list --specs\` pour comprendre le contexte actuel. +2. Choisir un \`change-id\` unique mené par un verbe et créer \`proposal.md\`, \`tasks.md\`, \`design.md\` optionnel, et deltas de spécification sous \`openspec/changes//\`. +3. Rédiger des deltas de spécification en utilisant \`## ADDED|MODIFIED|REMOVED Requirements\` avec au moins un \`#### Scenario:\` par exigence. +4. Exécuter \`openspec validate --strict\` et résoudre tous les problèmes avant de partager la proposition. + +### Étape 2 : Implémentation des changements +Suivre ces étapes comme des TODOs et les compléter une par une. +1. **Lire proposal.md** - Comprendre ce qui est construit +2. **Lire design.md** (s'il existe) - Examiner les décisions techniques +3. **Lire tasks.md** - Obtenir la liste de contrôle d'implémentation +4. **Implémenter les tâches séquentiellement** - Compléter dans l'ordre +5. **Confirmer l'achèvement** - S'assurer que chaque élément de \`tasks.md\` est terminé avant de mettre à jour les statuts +6. **Mettre à jour la liste de contrôle** - Après que tout le travail est fait, définir chaque tâche à \`- [x]\` pour que la liste reflète la réalité +7. **Porte d'approbation** - Ne pas commencer l'implémentation tant que la proposition n'est pas examinée et approuvée + +### Étape 3 : Archivage des changements +Après le déploiement, créer une PR séparée pour : +- Déplacer \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\` +- Mettre à jour \`specs/\` si les capacités ont changé +- Utiliser \`openspec archive --skip-specs --yes\` pour les changements uniquement d'outillage (toujours passer l'ID de changement explicitement) +- Exécuter \`openspec validate --strict\` pour confirmer que le changement archivé passe les vérifications + +## Avant toute tâche + +**Liste de contrôle du contexte :** +- [ ] Lire les spécifications pertinentes dans \`specs/[capability]/spec.md\` +- [ ] Vérifier les changements en attente dans \`changes/\` pour les conflits +- [ ] Lire \`openspec/project.md\` pour les conventions +- [ ] Exécuter \`openspec list\` pour voir les changements actifs +- [ ] Exécuter \`openspec list --specs\` pour voir les capacités existantes + +**Avant de créer des spécifications :** +- Toujours vérifier si la capacité existe déjà +- Préférer modifier les spécifications existantes plutôt que de créer des doublons +- Utiliser \`openspec show [spec]\` pour examiner l'état actuel +- Si la demande est ambiguë, poser 1-2 questions de clarification avant l'échafaudage + +### Guide de recherche +- Énumérer les spécifications : \`openspec spec list --long\` (ou \`--json\` pour les scripts) +- Énumérer les changements : \`openspec list\` (ou \`openspec change list --json\` - déprécié mais disponible) +- Afficher les détails : + - Spécification : \`openspec show --type spec\` (utiliser \`--json\` pour les filtres) + - Changement : \`openspec show --json --deltas-only\` +- Recherche en texte intégral (utiliser ripgrep) : \`rg -n "Requirement:|Scenario:" openspec/specs\` + +## Démarrage rapide + +### Commandes CLI + +\`\`\`bash +# Commandes essentielles +openspec list # Lister les changements actifs +openspec list --specs # Lister les spécifications +openspec show [item] # Afficher le changement ou la spécification +openspec validate [item] # Valider les changements ou spécifications +openspec archive [--yes|-y] # Archiver après déploiement (ajouter --yes pour les exécutions non interactives) + +# Gestion de projet +openspec init [path] # Initialiser OpenSpec +openspec update [path] # Mettre à jour les fichiers d'instructions + +# Mode interactif +openspec show # Invite pour la sélection +openspec validate # Mode de validation en masse + +# Débogage +openspec show [change] --json --deltas-only +openspec validate [change] --strict +\`\`\` + +### Drapeaux de commande + +- \`--json\` - Sortie lisible par machine +- \`--type change|spec\` - Lever l'ambiguïté des éléments +- \`--strict\` - Validation complète +- \`--no-interactive\` - Désactiver les invites +- \`--skip-specs\` - Archiver sans mises à jour de spécification +- \`--yes\`/\`-y\` - Ignorer les invites de confirmation (archivage non interactif) + +## Structure de répertoire + +\`\`\` +openspec/ +├── project.md # Conventions du projet +├── specs/ # Vérité actuelle - ce qui EST construit +│ └── [capability]/ # Capacité unique et ciblée +│ ├── spec.md # Exigences et scénarios +│ └── design.md # Modèles techniques +├── changes/ # Propositions - ce qui DEVRAIT changer +│ ├── [change-name]/ +│ │ ├── proposal.md # Pourquoi, quoi, impact +│ │ ├── tasks.md # Liste de contrôle d'implémentation +│ │ ├── design.md # Décisions techniques (optionnel ; voir critères) +│ │ └── specs/ # Changements delta +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # Changements terminés +\`\`\` + +## Création de propositions de changement + +### Arbre de décision + +\`\`\` +Nouvelle demande ? +├─ Correction de bug restaurant le comportement de spécification ? → Corriger directement +├─ Faute de frappe/format/commentaire ? → Corriger directement +├─ Nouvelle fonctionnalité/capacité ? → Créer une proposition +├─ Changement majeur ? → Créer une proposition +├─ Changement d'architecture ? → Créer une proposition +└─ Pas clair ? → Créer une proposition (plus sûr) +\`\`\` + +### Structure de proposition + +1. **Créer le répertoire :** \`changes/[change-id]/\` (kebab-case, mené par un verbe, unique) + +2. **Écrire proposal.md :** +\`\`\`markdown +# Changement : [Brève description du changement] + +## Pourquoi +[1-2 phrases sur le problème/opportunité] + +## Quels changements +- [Liste à puces des changements] +- [Marquer les changements majeurs avec **BREAKING**] + +## Impact +- Spécifications affectées : [lister les capacités] +- Code affecté : [fichiers/systèmes clés] +\`\`\` + +3. **Créer des deltas de spécification :** \`specs/[capability]/spec.md\` +\`\`\`markdown +## ADDED Requirements +### Requirement: Nouvelle fonctionnalité +Le système DOIT fournir... + +#### Scenario: Cas de succès +- **WHEN** l'utilisateur effectue une action +- **THEN** résultat attendu + +## MODIFIED Requirements +### Requirement: Fonctionnalité existante +[Exigence modifiée complète] + +## REMOVED Requirements +### Requirement: Ancienne fonctionnalité +**Raison** : [Pourquoi supprimer] +**Migration** : [Comment gérer] +\`\`\` +Si plusieurs capacités sont affectées, créer plusieurs fichiers delta sous \`changes/[change-id]/specs//spec.md\`—un par capacité. + +4. **Créer tasks.md :** +\`\`\`markdown +## 1. Implémentation +- [ ] 1.1 Créer le schéma de base de données +- [ ] 1.2 Implémenter le point de terminaison API +- [ ] 1.3 Ajouter le composant frontend +- [ ] 1.4 Écrire les tests +\`\`\` + +5. **Créer design.md si nécessaire :** +Créer \`design.md\` si l'un des éléments suivants s'applique ; sinon l'omettre : +- Changement transversal (plusieurs services/modules) ou nouveau modèle architectural +- Nouvelle dépendance externe ou changements significatifs du modèle de données +- Complexité de sécurité, performance ou migration +- Ambiguïté qui bénéficie de décisions techniques avant le codage + +Squelette minimal de \`design.md\` : +\`\`\`markdown +## Contexte +[Contexte, contraintes, parties prenantes] + +## Objectifs / Non-objectifs +- Objectifs : [...] +- Non-objectifs : [...] + +## Décisions +- Décision : [Quoi et pourquoi] +- Alternatives considérées : [Options + justification] + +## Risques / Compromis +- [Risque] → Atténuation + +## Plan de migration +[Étapes, retour en arrière] + +## Questions ouvertes +- [...] +\`\`\` + +## Format de fichier de spécification + +### Critique : Formatage des scénarios + +**CORRECT** (utiliser les en-têtes ####) : +\`\`\`markdown +#### Scenario: Connexion utilisateur réussie +- **WHEN** des identifiants valides fournis +- **THEN** retourner le jeton JWT +\`\`\` + +**INCORRECT** (ne pas utiliser de puces ou de gras) : +\`\`\`markdown +- **Scenario: Connexion utilisateur** ❌ +**Scenario**: Connexion utilisateur ❌ +### Scenario: Connexion utilisateur ❌ +\`\`\` + +Chaque exigence DOIT avoir au moins un scénario. + +### Formulation des exigences +- Utiliser SHALL/MUST pour les exigences normatives (éviter should/may sauf si intentionnellement non normatif) + +### Opérations delta + +- \`## ADDED Requirements\` - Nouvelles capacités +- \`## MODIFIED Requirements\` - Comportement modifié +- \`## REMOVED Requirements\` - Fonctionnalités dépréciées +- \`## RENAMED Requirements\` - Changements de nom + +En-têtes correspondant à \`trim(header)\` - espaces ignorés. + +#### Quand utiliser ADDED vs MODIFIED +- ADDED : Introduit une nouvelle capacité ou sous-capacité qui peut exister seule comme exigence. Préférer ADDED lorsque le changement est orthogonal (par ex., ajouter "Configuration de commande slash") plutôt que de modifier la sémantique d'une exigence existante. +- MODIFIED : Modifie le comportement, la portée ou les critères d'acceptation d'une exigence existante. Toujours coller le contenu complet et mis à jour de l'exigence (en-tête + tous les scénarios). L'archiveur remplacera l'exigence entière par ce que vous fournissez ici ; les deltas partiels perdront les détails précédents. +- RENAMED : Utiliser lorsque seul le nom change. Si vous modifiez également le comportement, utiliser RENAMED (nom) plus MODIFIED (contenu) en référençant le nouveau nom. + +Piège courant : Utiliser MODIFIED pour ajouter une nouvelle préoccupation sans inclure le texte précédent. Cela entraîne une perte de détails au moment de l'archivage. Si vous ne modifiez pas explicitement l'exigence existante, ajoutez une nouvelle exigence sous ADDED à la place. + +Rédaction correcte d'une exigence MODIFIED : +1) Localiser l'exigence existante dans \`openspec/specs//spec.md\`. +2) Copier le bloc d'exigence entier (de \`### Requirement: ...\` à travers ses scénarios). +3) Le coller sous \`## MODIFIED Requirements\` et l'éditer pour refléter le nouveau comportement. +4) S'assurer que le texte de l'en-tête correspond exactement (insensible aux espaces) et conserver au moins un \`#### Scenario:\`. + +Exemple pour RENAMED : +\`\`\`markdown +## RENAMED Requirements +- FROM: \`### Requirement: Login\` +- TO: \`### Requirement: User Authentication\` +\`\`\` + +## Dépannage + +### Erreurs courantes + +**"Le changement doit avoir au moins un delta"** +- Vérifier que \`changes/[name]/specs/\` existe avec des fichiers .md +- Vérifier que les fichiers ont des préfixes d'opération (## ADDED Requirements) + +**"L'exigence doit avoir au moins un scénario"** +- Vérifier que les scénarios utilisent le format \`#### Scenario:\` (4 dièses) +- Ne pas utiliser de puces ou de gras pour les en-têtes de scénario + +**Échecs silencieux d'analyse de scénario** +- Format exact requis : \`#### Scenario: Name\` +- Déboguer avec : \`openspec show [change] --json --deltas-only\` + +### Conseils de validation + +\`\`\`bash +# Toujours utiliser le mode strict pour des vérifications complètes +openspec validate [change] --strict + +# Déboguer l'analyse delta +openspec show [change] --json | jq '.deltas' + +# Vérifier une exigence spécifique +openspec show [spec] --json -r 1 +\`\`\` + +## Script de chemin heureux + +\`\`\`bash +# 1) Explorer l'état actuel +openspec spec list --long +openspec list +# Recherche en texte intégral optionnelle : +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) Choisir l'id de changement et échafauder +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## Pourquoi\\n...\\n\\n## Quels changements\\n- ...\\n\\n## Impact\\n- ...\\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. Implémentation\\n- [ ] 1.1 ...\\n" > openspec/changes/$CHANGE/tasks.md + +# 3) Ajouter des deltas (exemple) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: Authentification à deux facteurs +Les utilisateurs DOIVENT fournir un second facteur lors de la connexion. + +#### Scenario: OTP requis +- **WHEN** des identifiants valides sont fournis +- **THEN** un défi OTP est requis +EOF + +# 4) Valider +openspec validate $CHANGE --strict +\`\`\` + +## Exemple multi-capacité + +\`\`\` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: Authentification à deux facteurs + └── notifications/ + └── spec.md # ADDED: Notification email OTP +\`\`\` + +auth/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: Authentification à deux facteurs +... +\`\`\` + +notifications/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: Notification email OTP +... +\`\`\` + +## Meilleures pratiques + +### Simplicité d'abord +- Par défaut <100 lignes de nouveau code +- Implémentations à fichier unique jusqu'à preuve d'insuffisance +- Éviter les frameworks sans justification claire +- Choisir des modèles ennuyeux et éprouvés + +### Déclencheurs de complexité +N'ajouter de la complexité qu'avec : +- Données de performance montrant que la solution actuelle est trop lente +- Exigences d'échelle concrètes (>1000 utilisateurs, >100MB de données) +- Plusieurs cas d'usage éprouvés nécessitant une abstraction + +### Références claires +- Utiliser le format \`file.ts:42\` pour les emplacements de code +- Référencer les spécifications comme \`specs/auth/spec.md\` +- Lier les changements et PR connexes + +### Nommage des capacités +- Utiliser verbe-nom : \`user-auth\`, \`payment-capture\` +- Objectif unique par capacité +- Règle de compréhensibilité en 10 minutes +- Diviser si la description nécessite "ET" + +### Nommage des ID de changement +- Utiliser kebab-case, court et descriptif : \`add-two-factor-auth\` +- Préférer les préfixes menés par un verbe : \`add-\`, \`update-\`, \`remove-\`, \`refactor-\` +- Assurer l'unicité ; si pris, ajouter \`-2\`, \`-3\`, etc. + +## Guide de sélection d'outils + +| Tâche | Outil | Pourquoi | +|------|------|-----| +| Trouver des fichiers par motif | Glob | Correspondance de motifs rapide | +| Rechercher le contenu du code | Grep | Recherche regex optimisée | +| Lire des fichiers spécifiques | Read | Accès direct aux fichiers | +| Explorer une portée inconnue | Task | Investigation en plusieurs étapes | + +## Récupération d'erreur + +### Conflits de changement +1. Exécuter \`openspec list\` pour voir les changements actifs +2. Vérifier les spécifications qui se chevauchent +3. Coordonner avec les propriétaires de changement +4. Considérer la combinaison de propositions + +### Échecs de validation +1. Exécuter avec le drapeau \`--strict\` +2. Vérifier la sortie JSON pour les détails +3. Vérifier le format du fichier de spécification +4. S'assurer que les scénarios sont correctement formatés + +### Contexte manquant +1. Lire project.md d'abord +2. Vérifier les spécifications connexes +3. Examiner les archives récentes +4. Demander des clarifications + +## Référence rapide + +### Indicateurs d'étape +- \`changes/\` - Proposé, pas encore construit +- \`specs/\` - Construit et déployé +- \`archive/\` - Changements terminés + +### Objectifs des fichiers +- \`proposal.md\` - Pourquoi et quoi +- \`tasks.md\` - Étapes d'implémentation +- \`design.md\` - Décisions techniques +- \`spec.md\` - Exigences et comportement + +### Essentiels CLI +\`\`\`bash +openspec list # Qu'est-ce qui est en cours ? +openspec show [item] # Voir les détails +openspec validate --strict # Est-ce correct ? +openspec archive [--yes|-y] # Marquer comme terminé (ajouter --yes pour l'automatisation) +\`\`\` + +Rappelez-vous : Les spécifications sont la vérité. Les changements sont des propositions. Gardez-les synchronisés. +`; diff --git a/src/core/templates/i18n/fr-FR/project-template.ts b/src/core/templates/i18n/fr-FR/project-template.ts new file mode 100644 index 00000000..292f5a06 --- /dev/null +++ b/src/core/templates/i18n/fr-FR/project-template.ts @@ -0,0 +1,34 @@ +import { ProjectContext } from '../../project-template.js'; + +export const projectTemplate = (context: ProjectContext = {}) => `# Contexte ${context.projectName || 'du Projet'} + +## Objectif +${context.description || '[Décrivez l\'objectif et les buts de votre projet]'} + +## Pile Technologique +${context.techStack?.length ? context.techStack.map(tech => `- ${tech}`).join('\n') : '- [Listez vos technologies principales]\n- [par ex. : TypeScript, React, Node.js]'} + +## Conventions du Projet + +### Style de Code +[Décrivez vos préférences de style de code, règles de formatage et conventions de nommage] + +### Modèles d'Architecture +[Documentez vos décisions et modèles architecturaux] + +### Stratégie de Test +[Expliquez votre approche et vos exigences de test] + +### Workflow Git +[Décrivez votre stratégie de branchement et vos conventions de commit] + +## Contexte du Domaine +[Ajoutez des connaissances spécifiques au domaine que les assistants IA doivent comprendre] + +## Contraintes Importantes +[Listez toutes les contraintes techniques, commerciales ou réglementaires] + +## Dépendances Externes +[Documentez les services externes, API ou systèmes clés] +`; + diff --git a/src/core/templates/i18n/fr-FR/slash-command-templates.ts b/src/core/templates/i18n/fr-FR/slash-command-templates.ts new file mode 100644 index 00000000..3e10587c --- /dev/null +++ b/src/core/templates/i18n/fr-FR/slash-command-templates.ts @@ -0,0 +1,59 @@ +import { SlashCommandId } from '../../slash-command-templates.js'; + +const baseGuardrails = `**Contraintes** +- Privilégier des implémentations simples et minimales d'abord, et n'ajouter de la complexité que lorsqu'elle est demandée ou clairement requise. +- Maintenir les changements étroitement ciblés sur le résultat demandé. +- Se référer à \`openspec/AGENTS.md\` (situé dans le répertoire \`openspec/\`—exécutez \`ls openspec\` ou \`openspec update\` si vous ne le voyez pas) si vous avez besoin de conventions ou clarifications OpenSpec supplémentaires.`; + +const proposalGuardrails = `${baseGuardrails}\n- Identifier tout détail vague ou ambigu et poser les questions de suivi nécessaires avant de modifier les fichiers.`; + +const proposalSteps = `**Étapes** +1. Examiner \`openspec/project.md\`, exécuter \`openspec list\` et \`openspec list --specs\`, et inspecter le code ou la documentation connexe (par ex. via \`rg\`/\`ls\`) pour comprendre le comportement actuel ; noter tout écart nécessitant une clarification. +2. Choisir un \`change-id\` unique mené par un verbe et créer \`proposal.md\`, \`tasks.md\` et \`design.md\` (si nécessaire) sous \`openspec/changes//\`. +3. Mapper le changement en capacités ou exigences concrètes, en décomposant les efforts multi-portée en deltas de spécification distincts avec des relations et un séquencement clairs. +4. Capturer le raisonnement architectural dans \`design.md\` lorsque la solution s'étend sur plusieurs systèmes, introduit de nouveaux modèles ou nécessite une discussion de compromis avant de s'engager dans les spécifications. +5. Rédiger des deltas de spécification dans \`changes//specs//spec.md\` (un dossier par capacité) en utilisant \`## ADDED|MODIFIED|REMOVED Requirements\` avec au moins un \`#### Scenario:\` par exigence et référencer les capacités connexes lorsque cela est pertinent. +6. Rédiger \`tasks.md\` comme une liste ordonnée de petits éléments de travail vérifiables qui offrent des progrès visibles par l'utilisateur, incluent la validation (tests, outils) et mettent en évidence les dépendances ou le travail parallélisable. +7. Valider avec \`openspec validate --strict\` et résoudre tous les problèmes avant de partager la proposition.`; + +const proposalReferences = `**Référence** +- Utiliser \`openspec show --json --deltas-only\` ou \`openspec show --type spec\` pour inspecter les détails lorsque la validation échoue. +- Rechercher les exigences existantes avec \`rg -n "Requirement:|Scenario:" openspec/specs\` avant d'en écrire de nouvelles. +- Explorer la base de code avec \`rg \`, \`ls\` ou des lectures de fichiers directes pour que les propositions s'alignent sur les réalités d'implémentation actuelles.`; + +const applySteps = `**Étapes** +Suivre ces étapes comme des TODOs et les compléter une par une. +1. Lire \`changes//proposal.md\`, \`design.md\` (s'il existe) et \`tasks.md\` pour confirmer la portée et les critères d'acceptation. +2. Travailler sur les tâches séquentiellement, en gardant les modifications minimales et ciblées sur le changement demandé. +3. Confirmer l'achèvement avant de mettre à jour les statuts—s'assurer que chaque élément de \`tasks.md\` est terminé. +4. Mettre à jour la liste de contrôle après que tout le travail est fait pour que chaque tâche soit marquée \`- [x]\` et reflète la réalité. +5. Se référer à \`openspec list\` ou \`openspec show \` lorsque un contexte supplémentaire est requis.`; + +const applyReferences = `**Référence** +- Utiliser \`openspec show --json --deltas-only\` si vous avez besoin d'un contexte supplémentaire de la proposition lors de l'implémentation.`; + +const archiveSteps = `**Étapes** +1. Déterminer l'ID de changement à archiver : + - Si cette invite inclut déjà un ID de changement spécifique (par exemple dans un bloc \`\` rempli par les arguments de commande slash), utiliser cette valeur après avoir supprimé les espaces. + - Si la conversation fait référence à un changement de manière vague (par exemple par titre ou résumé), exécuter \`openspec list\` pour afficher les ID probables, partager les candidats pertinents et confirmer celui que l'utilisateur a l'intention d'utiliser. + - Sinon, examiner la conversation, exécuter \`openspec list\`, et demander à l'utilisateur quel changement archiver ; attendre un ID de changement confirmé avant de continuer. + - Si vous ne pouvez toujours pas identifier un seul ID de changement, arrêter et dire à l'utilisateur que vous ne pouvez pas encore archiver quoi que ce soit. +2. Valider l'ID de changement en exécutant \`openspec list\` (ou \`openspec show \`) et s'arrêter si le changement est manquant, déjà archivé ou autrement pas prêt à être archivé. +3. Exécuter \`openspec archive --yes\` pour que le CLI déplace le changement et applique les mises à jour de spécification sans invites (utiliser \`--skip-specs\` uniquement pour le travail uniquement d'outillage). +4. Examiner la sortie de la commande pour confirmer que les spécifications cibles ont été mises à jour et que le changement a atterri dans \`changes/archive/\`. +5. Valider avec \`openspec validate --strict\` et inspecter avec \`openspec show \` si quelque chose semble incorrect.`; + +const archiveReferences = `**Référence** +- Utiliser \`openspec list\` pour confirmer les ID de changement avant l'archivage. +- Inspecter les spécifications actualisées avec \`openspec list --specs\` et résoudre tout problème de validation avant de remettre.`; + +export const slashCommandBodies: Record = { + proposal: [proposalGuardrails, proposalSteps, proposalReferences].join('\n\n'), + apply: [baseGuardrails, applySteps, applyReferences].join('\n\n'), + archive: [baseGuardrails, archiveSteps, archiveReferences].join('\n\n') +}; + +export function getSlashCommandBody(id: SlashCommandId): string { + return slashCommandBodies[id]; +} + diff --git a/src/core/templates/i18n/ja-JP/agents-root-stub.ts b/src/core/templates/i18n/ja-JP/agents-root-stub.ts new file mode 100644 index 00000000..9e0a1f3f --- /dev/null +++ b/src/core/templates/i18n/ja-JP/agents-root-stub.ts @@ -0,0 +1,17 @@ +export const agentsRootStubTemplate = `# OpenSpec 説明 + +このプロジェクトで作業する AI アシスタント向けの説明です。 + +以下の場合には、常に \`@/openspec/AGENTS.md\` を開いてください: +- 計画や提案に言及している場合(proposal、spec、change、plan などの単語) +- 新機能、破壊的変更、アーキテクチャの変更、または大きなパフォーマンス/セキュリティ作業を導入する場合 +- 曖昧に聞こえ、コーディング前に権威ある仕様が必要な場合 + +\`@/openspec/AGENTS.md\` を使用して以下を学習してください: +- 変更提案の作成と適用方法 +- 仕様の形式と規則 +- プロジェクト構造とガイドライン + +この管理ブロックを保持して、'openspec update' が説明を更新できるようにします。 +`; + diff --git a/src/core/templates/i18n/ja-JP/agents-template.ts b/src/core/templates/i18n/ja-JP/agents-template.ts new file mode 100644 index 00000000..03c82392 --- /dev/null +++ b/src/core/templates/i18n/ja-JP/agents-template.ts @@ -0,0 +1,457 @@ +export const agentsTemplate = `# OpenSpec 説明 + +仕様駆動開発に OpenSpec を使用する AI コーディングアシスタント向けの説明。 + +## クイックチェックリスト + +- 既存の作業を検索:\`openspec spec list --long\`、\`openspec list\`(全文検索の場合のみ \`rg\` を使用) +- スコープを決定:新機能 vs 既存機能の変更 +- 一意の \`change-id\` を選択:kebab-case、動詞主導(\`add-\`、\`update-\`、\`remove-\`、\`refactor-\`) +- スキャフォールド:\`proposal.md\`、\`tasks.md\`、\`design.md\`(必要な場合のみ)、および影響を受ける機能ごとのデルタ仕様 +- デルタを記述:\`## ADDED|MODIFIED|REMOVED|RENAMED Requirements\` を使用;要件ごとに少なくとも1つの \`#### Scenario:\` を含める +- 検証:\`openspec validate [change-id] --strict\` を実行して問題を修正 +- 承認をリクエスト:提案が承認されるまで実装を開始しない + +## 3段階のワークフロー + +### ステージ 1:変更の作成 +以下の場合に提案を作成: +- 機能や機能性を追加 +- 破壊的変更(API、スキーマ)を行う +- アーキテクチャやパターンを変更 +- パフォーマンスを最適化(動作を変更) +- セキュリティパターンを更新 + +トリガー(例): +- "変更提案を作成するのを手伝って" +- "変更を計画するのを手伝って" +- "提案を作成したい" +- "仕様提案を作成したい" +- "仕様を作成したい" + +緩いマッチングガイダンス: +- 次のいずれかを含む:\`proposal\`、\`change\`、\`spec\` +- 次のいずれかと組み合わせる:\`create\`、\`plan\`、\`make\`、\`start\`、\`help\` + +提案をスキップする場合: +- バグ修正(意図された動作を復元) +- タイプミス、フォーマット、コメント +- 依存関係の更新(非破壊的) +- 設定の変更 +- 既存の動作のテスト + +**ワークフロー** +1. \`openspec/project.md\`、\`openspec list\`、および \`openspec list --specs\` を確認して、現在のコンテキストを理解する。 +2. 一意の動詞主導の \`change-id\` を選択し、\`openspec/changes//\` の下に \`proposal.md\`、\`tasks.md\`、オプションの \`design.md\`、および仕様デルタを作成する。 +3. 要件ごとに少なくとも1つの \`#### Scenario:\` を含む \`## ADDED|MODIFIED|REMOVED Requirements\` を使用して仕様デルタを起草する。 +4. \`openspec validate --strict\` を実行し、提案を共有する前にすべての問題を解決する。 + +### ステージ 2:変更の実装 +これらのステップを TODO として追跡し、1つずつ完了する。 +1. **proposal.md を読む** - 構築される内容を理解する +2. **design.md を読む**(存在する場合) - 技術的決定を確認する +3. **tasks.md を読む** - 実装チェックリストを取得する +4. **タスクを順番に実装する** - 順番に完了する +5. **完了を確認する** - ステータスを更新する前に、\`tasks.md\` のすべての項目が完了していることを確認する +6. **チェックリストを更新する** - すべての作業が完了した後、各タスクを \`- [x]\` に設定して、リストが現実を反映するようにする +7. **承認ゲート** - 提案がレビューされ承認されるまで実装を開始しない + +### ステージ 3:変更のアーカイブ +デプロイ後、別の PR を作成して: +- \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\` に移動 +- 機能が変更された場合は \`specs/\` を更新 +- ツールのみの変更には \`openspec archive --skip-specs --yes\` を使用(常に変更 ID を明示的に渡す) +- \`openspec validate --strict\` を実行して、アーカイブされた変更がチェックを通過することを確認する + +## タスクの前 + +**コンテキストチェックリスト:** +- [ ] \`specs/[capability]/spec.md\` で関連する仕様を読む +- [ ] \`changes/\` の保留中の変更で競合を確認する +- [ ] 規則について \`openspec/project.md\` を読む +- [ ] \`openspec list\` を実行してアクティブな変更を表示する +- [ ] \`openspec list --specs\` を実行して既存の機能を表示する + +**仕様を作成する前:** +- 常に機能が既に存在するかどうかを確認する +- 重複を作成するよりも既存の仕様を変更することを優先する +- \`openspec show [spec]\` を使用して現在の状態を確認する +- リクエストが曖昧な場合、スキャフォールドの前に1〜2つの明確化の質問をする + +### 検索ガイダンス +- 仕様を列挙:\`openspec spec list --long\`(またはスクリプト用に \`--json\`) +- 変更を列挙:\`openspec list\`(または \`openspec change list --json\` - 非推奨だが利用可能) +- 詳細を表示: + - 仕様:\`openspec show --type spec\`(フィルターに \`--json\` を使用) + - 変更:\`openspec show --json --deltas-only\` +- 全文検索(ripgrep を使用):\`rg -n "Requirement:|Scenario:" openspec/specs\` + +## クイックスタート + +### CLI コマンド + +\`\`\`bash +# 基本コマンド +openspec list # アクティブな変更をリスト +openspec list --specs # 仕様をリスト +openspec show [item] # 変更または仕様を表示 +openspec validate [item] # 変更または仕様を検証 +openspec archive [--yes|-y] # デプロイ後にアーカイブ(非対話式実行に --yes を追加) + +# プロジェクト管理 +openspec init [path] # OpenSpec を初期化 +openspec update [path] # 説明ファイルを更新 + +# 対話モード +openspec show # 選択のプロンプト +openspec validate # 一括検証モード + +# デバッグ +openspec show [change] --json --deltas-only +openspec validate [change] --strict +\`\`\` + +### コマンドフラグ + +- \`--json\` - 機械可読出力 +- \`--type change|spec\` - 項目の曖昧さを解消 +- \`--strict\` - 包括的な検証 +- \`--no-interactive\` - プロンプトを無効化 +- \`--skip-specs\` - 仕様更新なしでアーカイブ +- \`--yes\`/\`-y\` - 確認プロンプトをスキップ(非対話式アーカイブ) + +## ディレクトリ構造 + +\`\`\` +openspec/ +├── project.md # プロジェクト規則 +├── specs/ # 現在の真実 - 構築されているもの +│ └── [capability]/ # 単一の焦点を絞った機能 +│ ├── spec.md # 要件とシナリオ +│ └── design.md # 技術パターン +├── changes/ # 提案 - 変更すべきもの +│ ├── [change-name]/ +│ │ ├── proposal.md # 理由、内容、影響 +│ │ ├── tasks.md # 実装チェックリスト +│ │ ├── design.md # 技術的決定(オプション;基準を参照) +│ │ └── specs/ # デルタ変更 +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # 完了した変更 +\`\`\` + +## 変更提案の作成 + +### 決定木 + +\`\`\` +新しいリクエスト? +├─ 仕様動作を復元するバグ修正? → 直接修正 +├─ タイプミス/フォーマット/コメント? → 直接修正 +├─ 新機能/能力? → 提案を作成 +├─ 破壊的変更? → 提案を作成 +├─ アーキテクチャ変更? → 提案を作成 +└─ 不明確? → 提案を作成(より安全) +\`\`\` + +### 提案構造 + +1. **ディレクトリを作成:** \`changes/[change-id]/\`(kebab-case、動詞主導、一意) + +2. **proposal.md を記述:** +\`\`\`markdown +# 変更:[変更の簡単な説明] + +## 理由 +[問題/機会について1〜2文] + +## 変更内容 +- [変更の箇条書きリスト] +- [破壊的変更を **BREAKING** でマーク] + +## 影響 +- 影響を受ける仕様:[機能をリスト] +- 影響を受けるコード:[キーファイル/システム] +\`\`\` + +3. **仕様デルタを作成:** \`specs/[capability]/spec.md\` +\`\`\`markdown +## ADDED Requirements +### Requirement: 新機能 +システムは...を提供する必要がある + +#### Scenario: 成功ケース +- **WHEN** ユーザーがアクションを実行 +- **THEN** 期待される結果 + +## MODIFIED Requirements +### Requirement: 既存機能 +[完全な変更後の要件] + +## REMOVED Requirements +### Requirement: 古い機能 +**理由**:[削除する理由] +**移行**:[処理方法] +\`\`\` +複数の機能が影響を受ける場合、\`changes/[change-id]/specs//spec.md\` の下に複数のデルタファイルを作成する—機能ごとに1つ。 + +4. **tasks.md を作成:** +\`\`\`markdown +## 1. 実装 +- [ ] 1.1 データベーススキーマを作成 +- [ ] 1.2 API エンドポイントを実装 +- [ ] 1.3 フロントエンドコンポーネントを追加 +- [ ] 1.4 テストを記述 +\`\`\` + +5. **必要に応じて design.md を作成:** +以下のいずれかが該当する場合は \`design.md\` を作成;それ以外の場合は省略: +- 横断的変更(複数のサービス/モジュール)または新しいアーキテクチャパターン +- 新しい外部依存関係または重要なデータモデルの変更 +- セキュリティ、パフォーマンス、または移行の複雑さ +- コーディング前に技術的決定から利益を得る曖昧さ + +最小限の \`design.md\` スケルトン: +\`\`\`markdown +## コンテキスト +[背景、制約、ステークホルダー] + +## 目標 / 非目標 +- 目標:[...] +- 非目標:[...] + +## 決定 +- 決定:[何と理由] +- 検討された代替案:[オプション + 理由] + +## リスク / トレードオフ +- [リスク] → 緩和策 + +## 移行計画 +[ステップ、ロールバック] + +## 未解決の問題 +- [...] +\`\`\` + +## 仕様ファイル形式 + +### 重要:シナリオのフォーマット + +**正しい**(#### ヘッダーを使用): +\`\`\`markdown +#### Scenario: ユーザーログイン成功 +- **WHEN** 有効な認証情報が提供される +- **THEN** JWT トークンを返す +\`\`\` + +**間違い**(箇条書きや太字を使用しない): +\`\`\`markdown +- **Scenario: ユーザーログイン** ❌ +**Scenario**: ユーザーログイン ❌ +### Scenario: ユーザーログイン ❌ +\`\`\` + +各要件には少なくとも1つのシナリオが必要です。 + +### 要件の表現 +- 規範的要件には SHALL/MUST を使用(意図的に非規範的でない限り should/may を避ける) + +### デルタ操作 + +- \`## ADDED Requirements\` - 新機能 +- \`## MODIFIED Requirements\` - 変更された動作 +- \`## REMOVED Requirements\` - 非推奨機能 +- \`## RENAMED Requirements\` - 名前の変更 + +\`trim(header)\` と一致するヘッダー - 空白は無視される。 + +#### ADDED と MODIFIED を使用するタイミング +- ADDED:独立した要件として存在できる新しい機能またはサブ機能を導入する。変更が直交的(例:「スラッシュコマンド設定」の追加)で、既存の要件のセマンティクスを変更しない場合、ADDED を優先する。 +- MODIFIED:既存の要件の動作、スコープ、または受け入れ基準を変更する。常に完全な、更新された要件内容(ヘッダー + すべてのシナリオ)を貼り付ける。アーカイバーは、ここで提供する内容で要件全体を置き換える;部分的なデルタは以前の詳細を失う。 +- RENAMED:名前のみが変更される場合に使用する。動作も変更する場合は、RENAMED(名前)と MODIFIED(内容)を新しい名前を参照して使用する。 + +よくある落とし穴:以前のテキストを含めずに MODIFIED を使用して新しい懸念事項を追加する。これにより、アーカイブ時に詳細が失われる。既存の要件を明示的に変更していない場合は、代わりに ADDED の下に新しい要件を追加する。 + +MODIFIED 要件を正しく作成する: +1) \`openspec/specs//spec.md\` で既存の要件を見つける。 +2) 要件ブロック全体(\`### Requirement: ...\` からそのシナリオまで)をコピーする。 +3) \`## MODIFIED Requirements\` の下に貼り付けて、新しい動作を反映するように編集する。 +4) ヘッダーテキストが正確に一致することを確認(空白に依存しない)し、少なくとも1つの \`#### Scenario:\` を保持する。 + +RENAMED の例: +\`\`\`markdown +## RENAMED Requirements +- FROM: \`### Requirement: Login\` +- TO: \`### Requirement: User Authentication\` +\`\`\` + +## トラブルシューティング + +### 一般的なエラー + +**"変更には少なくとも1つのデルタが必要です"** +- \`changes/[name]/specs/\` が .md ファイルで存在することを確認 +- ファイルに操作プレフィックス(## ADDED Requirements)があることを確認 + +**"要件には少なくとも1つのシナリオが必要です"** +- シナリオが \`#### Scenario:\` 形式(4つのハッシュ)を使用していることを確認 +- シナリオヘッダーに箇条書きや太字を使用しない + +**サイレントシナリオ解析の失敗** +- 正確な形式が必要:\`#### Scenario: Name\` +- 以下でデバッグ:\`openspec show [change] --json --deltas-only\` + +### 検証のヒント + +\`\`\`bash +# 包括的なチェックには常に厳密モードを使用 +openspec validate [change] --strict + +# デルタ解析をデバッグ +openspec show [change] --json | jq '.deltas' + +# 特定の要件を確認 +openspec show [spec] --json -r 1 +\`\`\` + +## ハッピーパススクリプト + +\`\`\`bash +# 1) 現在の状態を探索 +openspec spec list --long +openspec list +# オプションの全文検索: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) 変更 id を選択してスキャフォールド +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## 理由\\n...\\n\\n## 変更内容\\n- ...\\n\\n## 影響\\n- ...\\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. 実装\\n- [ ] 1.1 ...\\n" > openspec/changes/$CHANGE/tasks.md + +# 3) デルタを追加(例) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: 二要素認証 +ユーザーはログイン時に2番目の要素を提供する必要がある。 + +#### Scenario: OTP が必要 +- **WHEN** 有効な認証情報が提供される +- **THEN** OTP チャレンジが必要 +EOF + +# 4) 検証 +openspec validate $CHANGE --strict +\`\`\` + +## マルチ機能の例 + +\`\`\` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: 二要素認証 + └── notifications/ + └── spec.md # ADDED: OTP メール通知 +\`\`\` + +auth/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: 二要素認証 +... +\`\`\` + +notifications/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: OTP メール通知 +... +\`\`\` + +## ベストプラクティス + +### シンプルさを優先 +- デフォルトで <100 行の新しいコード +- 不十分であることが証明されるまで単一ファイルの実装 +- 明確な正当化なしにフレームワークを避ける +- 退屈で実証済みのパターンを選択 + +### 複雑さのトリガー +以下の場合にのみ複雑さを追加: +- 現在のソリューションが遅すぎることを示すパフォーマンスデータ +- 具体的なスケール要件(>1000 ユーザー、>100MB データ) +- 抽象化を必要とする複数の実証済みのユースケース + +### 明確な参照 +- コードの場所に \`file.ts:42\` 形式を使用 +- 仕様を \`specs/auth/spec.md\` として参照 +- 関連する変更と PR をリンク + +### 機能の命名 +- 動詞-名詞を使用:\`user-auth\`、\`payment-capture\` +- 機能ごとに単一の目的 +- 10分の理解可能性ルール +- 説明に「AND」が必要な場合は分割 + +### 変更 ID の命名 +- kebab-case を使用、短く説明的:\`add-two-factor-auth\` +- 動詞主導のプレフィックスを優先:\`add-\`、\`update-\`、\`remove-\`、\`refactor-\` +- 一意性を確保;使用されている場合は \`-2\`、\`-3\` などを追加 + +## ツール選択ガイド + +| タスク | ツール | 理由 | +|------|------|-----| +| パターンでファイルを検索 | Glob | 高速なパターンマッチング | +| コードコンテンツを検索 | Grep | 最適化された正規表現検索 | +| 特定のファイルを読み取る | Read | 直接ファイルアクセス | +| 未知のスコープを探索 | Task | 多段階の調査 | + +## エラー回復 + +### 変更の競合 +1. \`openspec list\` を実行してアクティブな変更を表示 +2. 重複する仕様を確認 +3. 変更の所有者と調整 +4. 提案の結合を検討 + +### 検証の失敗 +1. \`--strict\` フラグで実行 +2. 詳細については JSON 出力を確認 +3. 仕様ファイル形式を確認 +4. シナリオが正しくフォーマットされていることを確認 + +### コンテキストの欠如 +1. 最初に project.md を読む +2. 関連する仕様を確認 +3. 最近のアーカイブを確認 +4. 明確化を求める + +## クイックリファレンス + +### ステージインジケーター +- \`changes/\` - 提案済み、まだ構築されていない +- \`specs/\` - 構築されデプロイ済み +- \`archive/\` - 完了した変更 + +### ファイルの目的 +- \`proposal.md\` - 理由と内容 +- \`tasks.md\` - 実装ステップ +- \`design.md\` - 技術的決定 +- \`spec.md\` - 要件と動作 + +### CLI の要点 +\`\`\`bash +openspec list # 何が進行中ですか? +openspec show [item] # 詳細を表示 +openspec validate --strict # 正しいですか? +openspec archive [--yes|-y] # 完了としてマーク(自動化に --yes を追加) +\`\`\` + +覚えておいてください:仕様は真実です。変更は提案です。それらを同期させてください。 +`; diff --git a/src/core/templates/i18n/ja-JP/project-template.ts b/src/core/templates/i18n/ja-JP/project-template.ts new file mode 100644 index 00000000..5d25b4c2 --- /dev/null +++ b/src/core/templates/i18n/ja-JP/project-template.ts @@ -0,0 +1,34 @@ +import { ProjectContext } from '../../project-template.js'; + +export const projectTemplate = (context: ProjectContext = {}) => `# ${context.projectName || 'プロジェクト'} コンテキスト + +## 目的 +${context.description || '[プロジェクトの目的と目標を説明してください]'} + +## 技術スタック +${context.techStack?.length ? context.techStack.map(tech => `- ${tech}`).join('\n') : '- [主要な技術をリストしてください]\n- [例:TypeScript, React, Node.js]'} + +## プロジェクト規則 + +### コードスタイル +[コードスタイルの好み、フォーマット規則、命名規則を説明してください] + +### アーキテクチャパターン +[アーキテクチャの決定とパターンを文書化してください] + +### テスト戦略 +[テストアプローチと要件を説明してください] + +### Git ワークフロー +[ブランチ戦略とコミット規則を説明してください] + +## ドメインコンテキスト +[AI アシスタントが理解する必要があるドメイン固有の知識を追加してください] + +## 重要な制約 +[技術的、ビジネス的、または規制上の制約をリストしてください] + +## 外部依存関係 +[主要な外部サービス、API、またはシステムを文書化してください] +`; + diff --git a/src/core/templates/i18n/ja-JP/slash-command-templates.ts b/src/core/templates/i18n/ja-JP/slash-command-templates.ts new file mode 100644 index 00000000..0e80ee8d --- /dev/null +++ b/src/core/templates/i18n/ja-JP/slash-command-templates.ts @@ -0,0 +1,59 @@ +import { SlashCommandId } from '../../slash-command-templates.js'; + +const baseGuardrails = `**ガードレール** +- まずはシンプルで最小限の実装を優先し、要求された場合または明確に必要な場合にのみ複雑さを追加します。 +- 変更を要求された結果に厳密にスコープを絞ってください。 +- 追加の OpenSpec 規則や説明が必要な場合は、\`openspec/AGENTS.md\`(\`openspec/\` ディレクトリ内にあります—表示されない場合は \`ls openspec\` または \`openspec update\` を実行)を参照してください。`; + +const proposalGuardrails = `${baseGuardrails}\n- あいまいまたは不明確な詳細を特定し、ファイルを編集する前に必要なフォローアップの質問をしてください。`; + +const proposalSteps = `**ステップ** +1. \`openspec/project.md\` を確認し、\`openspec list\` と \`openspec list --specs\` を実行し、関連するコードやドキュメント(例:\`rg\`/\`ls\` 経由)を検査して、現在の動作を理解します。明確化が必要なギャップに注意してください。 +2. 一意の動詞主導の \`change-id\` を選択し、\`openspec/changes//\` の下に \`proposal.md\`、\`tasks.md\`、および \`design.md\`(必要な場合)を作成します。 +3. 変更を具体的な機能または要件にマッピングし、マルチスコープの取り組みを明確な関係と順序付けを持つ個別の仕様デルタに分解します。 +4. ソリューションが複数のシステムにまたがる場合、新しいパターンを導入する場合、または仕様にコミットする前にトレードオフの議論が必要な場合、\`design.md\` にアーキテクチャの推論を記録します。 +5. \`changes//specs//spec.md\`(機能ごとに1つのフォルダ)で仕様デルタを起草し、要件ごとに少なくとも1つの \`#### Scenario:\` を含む \`## ADDED|MODIFIED|REMOVED Requirements\` を使用し、関連する場合は関連機能を相互参照します。 +6. \`tasks.md\` を、ユーザーに見える進捗を提供する小さな検証可能な作業項目の順序付きリストとして起草し、検証(テスト、ツール)を含め、依存関係または並列化可能な作業を強調します。 +7. \`openspec validate --strict\` で検証し、提案を共有する前にすべての問題を解決します。`; + +const proposalReferences = `**参照** +- 検証が失敗した場合、\`openspec show --json --deltas-only\` または \`openspec show --type spec\` を使用して詳細を検査します。 +- 新しい要件を書く前に、\`rg -n "Requirement:|Scenario:" openspec/specs\` で既存の要件を検索します。 +- 提案が現在の実装の現実と一致するように、\`rg \`、\`ls\`、または直接ファイル読み取りでコードベースを探索します。`; + +const applySteps = `**ステップ** +これらのステップを TODO として追跡し、1つずつ完了します。 +1. \`changes//proposal.md\`、\`design.md\`(存在する場合)、および \`tasks.md\` を読んで、スコープと受け入れ基準を確認します。 +2. タスクを順番に作業し、編集を最小限に抑え、要求された変更に焦点を当てます。 +3. ステータスを更新する前に完了を確認します—\`tasks.md\` のすべての項目が完了していることを確認します。 +4. すべての作業が完了したらチェックリストを更新し、各タスクが \`- [x]\` とマークされ、現実を反映するようにします。 +5. 追加のコンテキストが必要な場合は、\`openspec list\` または \`openspec show \` を参照します。`; + +const applyReferences = `**参照** +- 実装中に提案から追加のコンテキストが必要な場合は、\`openspec show --json --deltas-only\` を使用してください。`; + +const archiveSteps = `**ステップ** +1. アーカイブする変更 ID を決定します: + - このプロンプトに既に特定の変更 ID が含まれている場合(例:スラッシュコマンド引数で入力された \`\` ブロック内)、空白を削除した後にその値を使用します。 + - 会話が変更を大まかに参照している場合(例:タイトルや要約で)、\`openspec list\` を実行して可能性のある ID を表示し、関連する候補を共有し、ユーザーが意図するものを確認します。 + - それ以外の場合は、会話を確認し、\`openspec list\` を実行し、ユーザーにどの変更をアーカイブするか尋ねます。続行する前に確認された変更 ID を待ちます。 + - それでも単一の変更 ID を識別できない場合は、停止し、ユーザーにまだ何もアーカイブできないことを伝えます。 +2. \`openspec list\`(または \`openspec show \`)を実行して変更 ID を検証し、変更が欠落している、既にアーカイブされている、またはアーカイブの準備ができていない場合は停止します。 +3. \`openspec archive --yes\` を実行して、CLI が変更を移動し、プロンプトなしで仕様更新を適用するようにします(ツールのみの作業には \`--skip-specs\` のみを使用)。 +4. コマンド出力を確認して、ターゲット仕様が更新され、変更が \`changes/archive/\` に配置されたことを確認します。 +5. \`openspec validate --strict\` で検証し、問題があるように見える場合は \`openspec show \` で検査します。`; + +const archiveReferences = `**参照** +- アーカイブする前に \`openspec list\` を使用して変更 ID を確認します。 +- \`openspec list --specs\` で更新された仕様を検査し、引き継ぐ前に検証の問題に対処します。`; + +export const slashCommandBodies: Record = { + proposal: [proposalGuardrails, proposalSteps, proposalReferences].join('\n\n'), + apply: [baseGuardrails, applySteps, applyReferences].join('\n\n'), + archive: [baseGuardrails, archiveSteps, archiveReferences].join('\n\n') +}; + +export function getSlashCommandBody(id: SlashCommandId): string { + return slashCommandBodies[id]; +} + diff --git a/src/core/templates/i18n/zh-CN/agents-root-stub.ts b/src/core/templates/i18n/zh-CN/agents-root-stub.ts new file mode 100644 index 00000000..92afd9ba --- /dev/null +++ b/src/core/templates/i18n/zh-CN/agents-root-stub.ts @@ -0,0 +1,17 @@ +export const agentsRootStubTemplate = `# OpenSpec 说明 + +这些说明适用于在此项目中工作的 AI 助手。 + +当请求涉及以下情况时,请始终打开 \`@/openspec/AGENTS.md\`: +- 提及规划或提案(如 proposal、spec、change、plan 等词) +- 引入新功能、破坏性变更、架构调整或重大的性能/安全工作 +- 听起来模糊不清,需要在编码前查看权威规范 + +使用 \`@/openspec/AGENTS.md\` 了解: +- 如何创建和应用变更提案 +- 规范格式和约定 +- 项目结构和指南 + +保留此托管块,以便 'openspec update' 可以刷新说明。 +`; + diff --git a/src/core/templates/i18n/zh-CN/agents-template.ts b/src/core/templates/i18n/zh-CN/agents-template.ts new file mode 100644 index 00000000..723bb5d2 --- /dev/null +++ b/src/core/templates/i18n/zh-CN/agents-template.ts @@ -0,0 +1,458 @@ +export const agentsTemplate = `# OpenSpec 说明 + +适用于使用 OpenSpec 进行规范驱动开发的 AI 编码助手的说明。 + +## 快速检查清单 + +- 搜索现有工作:\`openspec spec list --long\`、\`openspec list\`(仅在全文搜索时使用 \`rg\`) +- 确定范围:新功能 vs 修改现有功能 +- 选择唯一的 \`change-id\`:kebab-case,动词开头(\`add-\`、\`update-\`、\`remove-\`、\`refactor-\`) +- 搭建:\`proposal.md\`、\`tasks.md\`、\`design.md\`(仅在需要时),以及每个受影响功能的增量规范 +- 编写增量:使用 \`## ADDED|MODIFIED|REMOVED|RENAMED Requirements\`;每个需求至少包含一个 \`#### Scenario:\` +- 验证:\`openspec validate [change-id] --strict\` 并修复问题 +- 请求批准:在提案获得批准之前不要开始实施 + +## 三阶段工作流 + +### 阶段 1:创建变更 +在以下情况下创建提案: +- 添加功能或功能特性 +- 进行破坏性更改(API、架构) +- 更改架构或模式 +- 优化性能(改变行为) +- 更新安全模式 + +触发词(示例): +- "帮我创建一个变更提案" +- "帮我规划一个变更" +- "我想创建一个提案" +- "我想创建一个规范提案" +- "我想创建一个规范" + +宽松匹配指导: +- 包含以下之一:\`proposal\`、\`change\`、\`spec\` +- 与以下之一组合:\`create\`、\`plan\`、\`make\`、\`start\`、\`help\` + +跳过提案的情况: +- Bug 修复(恢复预期行为) +- 拼写错误、格式、注释 +- 依赖更新(非破坏性) +- 配置更改 +- 现有行为的测试 + +**工作流** +1. 查看 \`openspec/project.md\`、\`openspec list\` 和 \`openspec list --specs\` 以了解当前上下文。 +2. 选择一个唯一的动词开头的 \`change-id\`,并在 \`openspec/changes//\` 下搭建 \`proposal.md\`、\`tasks.md\`、可选的 \`design.md\` 和规范增量。 +3. 使用 \`## ADDED|MODIFIED|REMOVED Requirements\` 起草规范增量,每个需求至少包含一个 \`#### Scenario:\`。 +4. 运行 \`openspec validate --strict\` 并在分享提案之前解决所有问题。 + +### 阶段 2:实施变更 +将这些步骤作为待办事项跟踪,逐一完成。 +1. **阅读 proposal.md** - 了解要构建的内容 +2. **阅读 design.md**(如果存在) - 查看技术决策 +3. **阅读 tasks.md** - 获取实施清单 +4. **按顺序实施任务** - 按顺序完成 +5. **确认完成** - 在更新状态之前,确保 \`tasks.md\` 中的每个项目都已完成 +6. **更新清单** - 所有工作完成后,将每个任务设置为 \`- [x]\`,使列表反映实际情况 +7. **批准门控** - 在提案经过审查和批准之前不要开始实施 + +### 阶段 3:归档变更 +部署后,创建单独的 PR: +- 将 \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\` +- 如果功能已更改,更新 \`specs/\` +- 对于仅工具更改,使用 \`openspec archive --skip-specs --yes\`(始终显式传递变更 ID) +- 运行 \`openspec validate --strict\` 以确认归档的变更通过检查 + +## 任何任务之前 + +**上下文检查清单:** +- [ ] 在 \`specs/[capability]/spec.md\` 中阅读相关规范 +- [ ] 检查 \`changes/\` 中的待处理变更是否存在冲突 +- [ ] 阅读 \`openspec/project.md\` 了解约定 +- [ ] 运行 \`openspec list\` 查看活动变更 +- [ ] 运行 \`openspec list --specs\` 查看现有功能 + +**创建规范之前:** +- 始终检查功能是否已存在 +- 优先修改现有规范而不是创建重复项 +- 使用 \`openspec show [spec]\` 查看当前状态 +- 如果请求不明确,在搭建之前提出 1-2 个澄清问题 + +### 搜索指导 +- 枚举规范:\`openspec spec list --long\`(或脚本使用 \`--json\`) +- 枚举变更:\`openspec list\`(或 \`openspec change list --json\` - 已弃用但可用) +- 显示详细信息: + - 规范:\`openspec show --type spec\`(使用 \`--json\` 进行过滤) + - 变更:\`openspec show --json --deltas-only\` +- 全文搜索(使用 ripgrep):\`rg -n "Requirement:|Scenario:" openspec/specs\` + +## 快速开始 + +### CLI 命令 + +\`\`\`bash +# 基本命令 +openspec list # 列出活动变更 +openspec list --specs # 列出规范 +openspec show [item] # 显示变更或规范 +openspec validate [item] # 验证变更或规范 +openspec archive [--yes|-y] # 部署后归档(非交互式运行添加 --yes) + +# 项目管理 +openspec init [path] # 初始化 OpenSpec +openspec update [path] # 更新说明文件 + +# 交互模式 +openspec show # 提示选择 +openspec validate # 批量验证模式 + +# 调试 +openspec show [change] --json --deltas-only +openspec validate [change] --strict +\`\`\` + +### 命令标志 + +- \`--json\` - 机器可读输出 +- \`--type change|spec\` - 消除项目歧义 +- \`--strict\` - 全面验证 +- \`--no-interactive\` - 禁用提示 +- \`--skip-specs\` - 归档时不更新规范 +- \`--yes\`/\`-y\` - 跳过确认提示(非交互式归档) + +## 目录结构 + +\`\`\` +openspec/ +├── project.md # 项目约定 +├── specs/ # 当前真相 - 已构建的内容 +│ └── [capability]/ # 单一专注的功能 +│ ├── spec.md # 需求和场景 +│ └── design.md # 技术模式 +├── changes/ # 提案 - 应该更改的内容 +│ ├── [change-name]/ +│ │ ├── proposal.md # 原因、内容、影响 +│ │ ├── tasks.md # 实施清单 +│ │ ├── design.md # 技术决策(可选;参见标准) +│ │ └── specs/ # 增量变更 +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # 已完成的变更 +\`\`\` + +## 创建变更提案 + +### 决策树 + +\`\`\` +新请求? +├─ 修复规范行为的 Bug? → 直接修复 +├─ 拼写错误/格式/注释? → 直接修复 +├─ 新功能/能力? → 创建提案 +├─ 破坏性更改? → 创建提案 +├─ 架构更改? → 创建提案 +└─ 不明确? → 创建提案(更安全) +\`\`\` + +### 提案结构 + +1. **创建目录:** \`changes/[change-id]/\`(kebab-case,动词开头,唯一) + +2. **编写 proposal.md:** +\`\`\`markdown +# 变更:[变更的简要描述] + +## 原因 +[关于问题/机会的 1-2 句话] + +## 变更内容 +- [变更的要点列表] +- [用 **BREAKING** 标记破坏性更改] + +## 影响 +- 受影响的规范:[列出功能] +- 受影响的代码:[关键文件/系统] +\`\`\` + +3. **创建规范增量:** \`specs/[capability]/spec.md\` +\`\`\`markdown +## ADDED Requirements +### Requirement: 新功能 +系统应提供... + +#### Scenario: 成功情况 +- **WHEN** 用户执行操作 +- **THEN** 预期结果 + +## MODIFIED Requirements +### Requirement: 现有功能 +[完整的修改后需求] + +## REMOVED Requirements +### Requirement: 旧功能 +**原因**:[为什么移除] +**迁移**:[如何处理] +\`\`\` +如果多个功能受到影响,在 \`changes/[change-id]/specs//spec.md\` 下创建多个增量文件——每个功能一个。 + +4. **创建 tasks.md:** +\`\`\`markdown +## 1. 实施 +- [ ] 1.1 创建数据库架构 +- [ ] 1.2 实现 API 端点 +- [ ] 1.3 添加前端组件 +- [ ] 1.4 编写测试 +\`\`\` + +5. **在需要时创建 design.md:** +如果以下任何情况适用,则创建 \`design.md\`;否则省略: +- 跨领域更改(多个服务/模块)或新的架构模式 +- 新的外部依赖或重大的数据模型更改 +- 安全、性能或迁移复杂性 +- 在编码之前从技术决策中受益的模糊性 + +最小 \`design.md\` 骨架: +\`\`\`markdown +## 上下文 +[背景、约束、利益相关者] + +## 目标 / 非目标 +- 目标:[...] +- 非目标:[...] + +## 决策 +- 决策:[内容和原因] +- 考虑的替代方案:[选项 + 理由] + +## 风险 / 权衡 +- [风险] → 缓解措施 + +## 迁移计划 +[步骤、回滚] + +## 开放问题 +- [...] +\`\`\` + +## 规范文件格式 + +### 关键:场景格式 + +**正确**(使用 #### 标题): +\`\`\`markdown +#### Scenario: 用户登录成功 +- **WHEN** 提供有效凭据 +- **THEN** 返回 JWT 令牌 +\`\`\` + +**错误**(不要使用项目符号或粗体): +\`\`\`markdown +- **Scenario: 用户登录** ❌ +**Scenario**: 用户登录 ❌ +### Scenario: 用户登录 ❌ +\`\`\` + +每个需求必须至少有一个场景。 + +### 需求措辞 +- 对规范性需求使用 SHALL/MUST(除非有意非规范性,否则避免 should/may) + +### 增量操作 + +- \`## ADDED Requirements\` - 新功能 +- \`## MODIFIED Requirements\` - 更改的行为 +- \`## REMOVED Requirements\` - 已弃用的功能 +- \`## RENAMED Requirements\` - 名称更改 + +标题与 \`trim(header)\` 匹配 - 忽略空白。 + +#### 何时使用 ADDED vs MODIFIED +- ADDED:引入可以独立作为需求的新功能或子功能。当更改是正交的(例如,添加"斜杠命令配置")而不是改变现有需求的语义时,优先使用 ADDED。 +- MODIFIED:更改现有行为、范围或验收标准。始终粘贴完整的、更新的需求内容(标题 + 所有场景)。归档器将用您在此处提供的内容替换整个需求;部分增量将丢失先前的详细信息。 +- RENAMED:仅在名称更改时使用。如果您还更改行为,请使用 RENAMED(名称)加上 MODIFIED(内容),引用新名称。 + +常见陷阱:使用 MODIFIED 添加新关注点而不包含先前的文本。这会在归档时导致详细信息丢失。如果您没有明确更改现有需求,请在 ADDED 下添加新需求。 + +正确编写 MODIFIED 需求: +1) 在 \`openspec/specs//spec.md\` 中找到现有需求。 +2) 复制整个需求块(从 \`### Requirement: ...\` 到其场景)。 +3) 将其粘贴到 \`## MODIFIED Requirements\` 下并编辑以反映新行为。 +4) 确保标题文本完全匹配(忽略空白)并至少保留一个 \`#### Scenario:\`。 + +RENAMED 示例: +\`\`\`markdown +## RENAMED Requirements +- FROM: \`### Requirement: Login\` +- TO: \`### Requirement: User Authentication\` +\`\`\` + +## 故障排除 + +### 常见错误 + +**"变更必须至少有一个增量"** +- 检查 \`changes/[name]/specs/\` 是否存在 .md 文件 +- 验证文件具有操作前缀(## ADDED Requirements) + +**"需求必须至少有一个场景"** +- 检查场景是否使用 \`#### Scenario:\` 格式(4 个井号) +- 不要对场景标题使用项目符号或粗体 + +**静默场景解析失败** +- 需要精确格式:\`#### Scenario: Name\` +- 使用以下命令调试:\`openspec show [change] --json --deltas-only\` + +### 验证提示 + +\`\`\`bash +# 始终使用严格模式进行全面检查 +openspec validate [change] --strict + +# 调试增量解析 +openspec show [change] --json | jq '.deltas' + +# 检查特定需求 +openspec show [spec] --json -r 1 +\`\`\` + +## 成功路径脚本 + +\`\`\`bash +# 1) 探索当前状态 +openspec spec list --long +openspec list +# 可选全文搜索: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) 选择变更 id 并搭建 +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## 原因\\n...\\n\\n## 变更内容\\n- ...\\n\\n## 影响\\n- ...\\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. 实施\\n- [ ] 1.1 ...\\n" > openspec/changes/$CHANGE/tasks.md + +# 3) 添加增量(示例) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: 双因素认证 +用户必须在登录时提供第二个因素。 + +#### Scenario: 需要 OTP +- **WHEN** 提供有效凭据 +- **THEN** 需要 OTP 挑战 +EOF + +# 4) 验证 +openspec validate $CHANGE --strict +\`\`\` + +## 多功能示例 + +\`\`\` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: 双因素认证 + └── notifications/ + └── spec.md # ADDED: OTP 电子邮件通知 +\`\`\` + +auth/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: 双因素认证 +... +\`\`\` + +notifications/spec.md +\`\`\`markdown +## ADDED Requirements +### Requirement: OTP 电子邮件通知 +... +\`\`\` + +## 最佳实践 + +### 简单优先 +- 默认 <100 行新代码 +- 单文件实现,直到证明不足 +- 没有明确理由时避免框架 +- 选择无聊、经过验证的模式 + +### 复杂性触发器 +仅在以下情况下添加复杂性: +- 性能数据表明当前解决方案太慢 +- 具体的规模要求(>1000 用户,>100MB 数据) +- 需要抽象的多个经过验证的用例 + +### 清晰的引用 +- 使用 \`file.ts:42\` 格式表示代码位置 +- 将规范引用为 \`specs/auth/spec.md\` +- 链接相关变更和 PR + +### 功能命名 +- 使用动词-名词:\`user-auth\`、\`payment-capture\` +- 每个功能单一目的 +- 10 分钟可理解性规则 +- 如果描述需要"AND",则拆分 + +### 变更 ID 命名 +- 使用 kebab-case,简短且描述性:\`add-two-factor-auth\` +- 优先使用动词开头的前缀:\`add-\`、\`update-\`、\`remove-\`、\`refactor-\` +- 确保唯一性;如果已使用,追加 \`-2\`、\`-3\` 等 + +## 工具选择指南 + +| 任务 | 工具 | 原因 | +|------|------|-----| +| 按模式查找文件 | Glob | 快速模式匹配 | +| 搜索代码内容 | Grep | 优化的正则表达式搜索 | +| 读取特定文件 | Read | 直接文件访问 | +| 探索未知范围 | Task | 多步骤调查 | + +## 错误恢复 + +### 变更冲突 +1. 运行 \`openspec list\` 查看活动变更 +2. 检查重叠的规范 +3. 与变更所有者协调 +4. 考虑合并提案 + +### 验证失败 +1. 使用 \`--strict\` 标志运行 +2. 检查 JSON 输出以获取详细信息 +3. 验证规范文件格式 +4. 确保场景格式正确 + +### 缺少上下文 +1. 首先阅读 project.md +2. 检查相关规范 +3. 查看最近的归档 +4. 请求澄清 + +## 快速参考 + +### 阶段指示器 +- \`changes/\` - 已提议,尚未构建 +- \`specs/\` - 已构建并部署 +- \`archive/\` - 已完成的变更 + +### 文件用途 +- \`proposal.md\` - 原因和内容 +- \`tasks.md\` - 实施步骤 +- \`design.md\` - 技术决策 +- \`spec.md\` - 需求和行为 + +### CLI 要点 +\`\`\`bash +openspec list # 正在进行什么? +openspec show [item] # 查看详细信息 +openspec validate --strict # 是否正确? +openspec archive [--yes|-y] # 标记完成(自动化添加 --yes) +\`\`\` + +记住:规范是真相。变更是提案。保持它们同步。 +`; + diff --git a/src/core/templates/i18n/zh-CN/project-template.ts b/src/core/templates/i18n/zh-CN/project-template.ts new file mode 100644 index 00000000..94003a14 --- /dev/null +++ b/src/core/templates/i18n/zh-CN/project-template.ts @@ -0,0 +1,34 @@ +import { ProjectContext } from '../../project-template.js'; + +export const projectTemplate = (context: ProjectContext = {}) => `# ${context.projectName || '项目'} 上下文 + +## 目的 +${context.description || '[描述您项目的目标和目的]'} + +## 技术栈 +${context.techStack?.length ? context.techStack.map(tech => `- ${tech}`).join('\n') : '- [列出您的主要技术]\n- [例如:TypeScript, React, Node.js]'} + +## 项目约定 + +### 代码风格 +[描述您的代码风格偏好、格式化规则和命名约定] + +### 架构模式 +[记录您的架构决策和模式] + +### 测试策略 +[说明您的测试方法和要求] + +### Git 工作流 +[描述您的分支策略和提交约定] + +## 领域上下文 +[添加 AI 助手需要理解的领域特定知识] + +## 重要约束 +[列出任何技术、业务或监管约束] + +## 外部依赖 +[记录关键的外部服务、API 或系统] +`; + diff --git a/src/core/templates/i18n/zh-CN/slash-command-templates.ts b/src/core/templates/i18n/zh-CN/slash-command-templates.ts new file mode 100644 index 00000000..67ddedbf --- /dev/null +++ b/src/core/templates/i18n/zh-CN/slash-command-templates.ts @@ -0,0 +1,59 @@ +import { SlashCommandId } from '../../slash-command-templates.js'; + +const baseGuardrails = `**约束条件** +- 优先采用简单、最小化的实现,只有在明确要求或明显需要时才添加复杂性。 +- 保持更改紧密围绕请求的结果。 +- 如需更多 OpenSpec 约定或说明,请参考 \`openspec/AGENTS.md\`(位于 \`openspec/\` 目录中——如果看不到,请运行 \`ls openspec\` 或 \`openspec update\`)。`; + +const proposalGuardrails = `${baseGuardrails}\n- 识别任何模糊或含糊的细节,在编辑文件之前提出必要的后续问题。`; + +const proposalSteps = `**步骤** +1. 查看 \`openspec/project.md\`,运行 \`openspec list\` 和 \`openspec list --specs\`,并检查相关代码或文档(例如通过 \`rg\`/\`ls\`)以了解当前行为;注意任何需要澄清的空白。 +2. 选择一个唯一的动词引导的 \`change-id\`,并在 \`openspec/changes//\` 下搭建 \`proposal.md\`、\`tasks.md\` 和 \`design.md\`(需要时)。 +3. 将变更映射到具体的能力或需求,将多范围的工作分解为具有明确关系和顺序的不同规范增量。 +4. 当解决方案跨越多个系统、引入新模式或需要在提交规范之前进行权衡讨论时,在 \`design.md\` 中捕获架构推理。 +5. 在 \`changes//specs//spec.md\` 中起草规范增量(每个能力一个文件夹),使用 \`## ADDED|MODIFIED|REMOVED Requirements\`,每个需求至少包含一个 \`#### Scenario:\`,并在相关时交叉引用相关能力。 +6. 将 \`tasks.md\` 起草为有序的小型、可验证工作项列表,这些工作项提供用户可见的进度,包括验证(测试、工具),并突出依赖关系或可并行化的工作。 +7. 使用 \`openspec validate --strict\` 进行验证,并在分享提案之前解决所有问题。`; + +const proposalReferences = `**参考** +- 当验证失败时,使用 \`openspec show --json --deltas-only\` 或 \`openspec show --type spec\` 检查详细信息。 +- 在编写新需求之前,使用 \`rg -n "Requirement:|Scenario:" openspec/specs\` 搜索现有需求。 +- 使用 \`rg \`、\`ls\` 或直接文件读取来探索代码库,以便提案与当前实现现实保持一致。`; + +const applySteps = `**步骤** +将这些步骤作为待办事项跟踪,逐一完成。 +1. 阅读 \`changes//proposal.md\`、\`design.md\`(如果存在)和 \`tasks.md\` 以确认范围和验收标准。 +2. 按顺序完成任务,保持编辑最小化并专注于请求的更改。 +3. 在更新状态之前确认完成——确保 \`tasks.md\` 中的每个项目都已完成。 +4. 在所有工作完成后更新清单,以便每个任务都标记为 \`- [x]\` 并反映实际情况。 +5. 需要额外上下文时,参考 \`openspec list\` 或 \`openspec show \`。`; + +const applyReferences = `**参考** +- 如果在实施过程中需要提案的额外上下文,请使用 \`openspec show --json --deltas-only\`。`; + +const archiveSteps = `**步骤** +1. 确定要归档的变更 ID: + - 如果此提示已包含特定的变更 ID(例如在由斜杠命令参数填充的 \`\` 块内),请在修剪空白后使用该值。 + - 如果对话中松散地引用了变更(例如通过标题或摘要),运行 \`openspec list\` 以显示可能的 ID,分享相关候选,并确认用户打算使用哪一个。 + - 否则,查看对话,运行 \`openspec list\`,并询问用户要归档哪个变更;在继续之前等待确认的变更 ID。 + - 如果您仍然无法识别单个变更 ID,请停止并告诉用户您还无法归档任何内容。 +2. 通过运行 \`openspec list\`(或 \`openspec show \`)验证变更 ID,如果变更缺失、已归档或尚未准备好归档,则停止。 +3. 运行 \`openspec archive --yes\`,以便 CLI 移动变更并应用规范更新而不提示(仅对仅工具工作使用 \`--skip-specs\`)。 +4. 查看命令输出以确认目标规范已更新,并且变更已进入 \`changes/archive/\`。 +5. 使用 \`openspec validate --strict\` 进行验证,如果看起来有问题,请使用 \`openspec show \` 进行检查。`; + +const archiveReferences = `**参考** +- 在归档之前使用 \`openspec list\` 确认变更 ID。 +- 使用 \`openspec list --specs\` 检查刷新的规范,并在移交之前解决任何验证问题。`; + +export const slashCommandBodies: Record = { + proposal: [proposalGuardrails, proposalSteps, proposalReferences].join('\n\n'), + apply: [baseGuardrails, applySteps, applyReferences].join('\n\n'), + archive: [baseGuardrails, archiveSteps, archiveReferences].join('\n\n') +}; + +export function getSlashCommandBody(id: SlashCommandId): string { + return slashCommandBodies[id]; +} + diff --git a/src/core/templates/index.ts b/src/core/templates/index.ts index 8dab4b5f..15673a1b 100644 --- a/src/core/templates/index.ts +++ b/src/core/templates/index.ts @@ -6,13 +6,92 @@ import { costrictTemplate } from './costrict-template.js'; import { agentsRootStubTemplate } from './agents-root-stub.js'; import { getSlashCommandBody, SlashCommandId } from './slash-command-templates.js'; +// Import Chinese translations +import { agentsRootStubTemplate as zhCN_agentsRootStubTemplate } from './i18n/zh-CN/agents-root-stub.js'; +import { agentsTemplate as zhCN_agentsTemplate } from './i18n/zh-CN/agents-template.js'; +import { projectTemplate as zhCN_projectTemplate } from './i18n/zh-CN/project-template.js'; +import { getSlashCommandBody as zhCN_getSlashCommandBody } from './i18n/zh-CN/slash-command-templates.js'; + +// Import French translations +import { agentsRootStubTemplate as frFR_agentsRootStubTemplate } from './i18n/fr-FR/agents-root-stub.js'; +import { agentsTemplate as frFR_agentsTemplate } from './i18n/fr-FR/agents-template.js'; +import { projectTemplate as frFR_projectTemplate } from './i18n/fr-FR/project-template.js'; +import { getSlashCommandBody as frFR_getSlashCommandBody } from './i18n/fr-FR/slash-command-templates.js'; + +// Import Japanese translations +import { agentsRootStubTemplate as jaJP_agentsRootStubTemplate } from './i18n/ja-JP/agents-root-stub.js'; +import { agentsTemplate as jaJP_agentsTemplate } from './i18n/ja-JP/agents-template.js'; +import { projectTemplate as jaJP_projectTemplate } from './i18n/ja-JP/project-template.js'; +import { getSlashCommandBody as jaJP_getSlashCommandBody } from './i18n/ja-JP/slash-command-templates.js'; + +// Import Arabic translations +import { agentsRootStubTemplate as arSA_agentsRootStubTemplate } from './i18n/ar-SA/agents-root-stub.js'; +import { agentsTemplate as arSA_agentsTemplate } from './i18n/ar-SA/agents-template.js'; +import { projectTemplate as arSA_projectTemplate } from './i18n/ar-SA/project-template.js'; +import { getSlashCommandBody as arSA_getSlashCommandBody } from './i18n/ar-SA/slash-command-templates.js'; + export interface Template { path: string; content: string | ((context: ProjectContext) => string); } export class TemplateManager { - static getTemplates(context: ProjectContext = {}): Template[] { + static getTemplates(context: ProjectContext = {}, language: string = 'en-US'): Template[] { + const normalizedLang = language.toLowerCase(); + + if (normalizedLang === 'zh-cn') { + return [ + { + path: 'AGENTS.md', + content: zhCN_agentsTemplate + }, + { + path: 'project.md', + content: zhCN_projectTemplate(context) + } + ]; + } + + if (normalizedLang === 'fr-fr') { + return [ + { + path: 'AGENTS.md', + content: frFR_agentsTemplate + }, + { + path: 'project.md', + content: frFR_projectTemplate(context) + } + ]; + } + + if (normalizedLang === 'ja-jp') { + return [ + { + path: 'AGENTS.md', + content: jaJP_agentsTemplate + }, + { + path: 'project.md', + content: jaJP_projectTemplate(context) + } + ]; + } + + if (normalizedLang === 'ar-sa') { + return [ + { + path: 'AGENTS.md', + content: arSA_agentsTemplate + }, + { + path: 'project.md', + content: arSA_projectTemplate(context) + } + ]; + } + + // Default to English return [ { path: 'AGENTS.md', @@ -25,23 +104,58 @@ export class TemplateManager { ]; } - static getClaudeTemplate(): string { + static getClaudeTemplate(language: string = 'en-US'): string { + const normalizedLang = language.toLowerCase(); + + if (normalizedLang === 'zh-cn') return zhCN_agentsRootStubTemplate; + if (normalizedLang === 'fr-fr') return frFR_agentsRootStubTemplate; + if (normalizedLang === 'ja-jp') return jaJP_agentsRootStubTemplate; + if (normalizedLang === 'ar-sa') return arSA_agentsRootStubTemplate; + return claudeTemplate; } - static getClineTemplate(): string { + static getClineTemplate(language: string = 'en-US'): string { + const normalizedLang = language.toLowerCase(); + + if (normalizedLang === 'zh-cn') return zhCN_agentsRootStubTemplate; + if (normalizedLang === 'fr-fr') return frFR_agentsRootStubTemplate; + if (normalizedLang === 'ja-jp') return jaJP_agentsRootStubTemplate; + if (normalizedLang === 'ar-sa') return arSA_agentsRootStubTemplate; + return clineTemplate; } - static getCostrictTemplate(): string { + static getCostrictTemplate(language: string = 'en-US'): string { + const normalizedLang = language.toLowerCase(); + + if (normalizedLang === 'zh-cn') return zhCN_agentsRootStubTemplate; + if (normalizedLang === 'fr-fr') return frFR_agentsRootStubTemplate; + if (normalizedLang === 'ja-jp') return jaJP_agentsRootStubTemplate; + if (normalizedLang === 'ar-sa') return arSA_agentsRootStubTemplate; + return costrictTemplate; } - static getAgentsStandardTemplate(): string { + static getAgentsStandardTemplate(language: string = 'en-US'): string { + const normalizedLang = language.toLowerCase(); + + if (normalizedLang === 'zh-cn') return zhCN_agentsRootStubTemplate; + if (normalizedLang === 'fr-fr') return frFR_agentsRootStubTemplate; + if (normalizedLang === 'ja-jp') return jaJP_agentsRootStubTemplate; + if (normalizedLang === 'ar-sa') return arSA_agentsRootStubTemplate; + return agentsRootStubTemplate; } - static getSlashCommandBody(id: SlashCommandId): string { + static getSlashCommandBody(id: SlashCommandId, language: string = 'en-US'): string { + const normalizedLang = language.toLowerCase(); + + if (normalizedLang === 'zh-cn') return zhCN_getSlashCommandBody(id); + if (normalizedLang === 'fr-fr') return frFR_getSlashCommandBody(id); + if (normalizedLang === 'ja-jp') return jaJP_getSlashCommandBody(id); + if (normalizedLang === 'ar-sa') return arSA_getSlashCommandBody(id); + return getSlashCommandBody(id); } } diff --git a/src/core/update.ts b/src/core/update.ts index 6d75898e..021dfeba 100644 --- a/src/core/update.ts +++ b/src/core/update.ts @@ -1,9 +1,9 @@ import path from 'path'; import { FileSystemUtils } from '../utils/file-system.js'; -import { OPENSPEC_DIR_NAME } from './config.js'; +import { OPENSPEC_DIR_NAME, CONFIG_FILE_NAME, DEFAULT_LANGUAGE } from './config.js'; import { ToolRegistry } from './configurators/registry.js'; import { SlashCommandRegistry } from './configurators/slash/registry.js'; -import { agentsTemplate } from './templates/agents-template.js'; +import { TemplateManager } from './templates/index.js'; export class UpdateCommand { async execute(projectPath: string): Promise { @@ -16,10 +16,21 @@ export class UpdateCommand { throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`); } - // 2. Update AGENTS.md (full replacement) - const agentsPath = path.join(openspecPath, 'AGENTS.md'); + // 2. Read language configuration + const configPath = path.join(openspecPath, CONFIG_FILE_NAME); + const config = await FileSystemUtils.readJsonFile<{ language?: string }>(configPath); + const language = config?.language || DEFAULT_LANGUAGE; - await FileSystemUtils.writeFile(agentsPath, agentsTemplate); + // 3. Update AGENTS.md (full replacement) + const agentsPath = path.join(openspecPath, 'AGENTS.md'); + const templates = TemplateManager.getTemplates({}, language); + const agentsTemplate = templates.find(t => t.path === 'AGENTS.md'); + if (agentsTemplate) { + const content = typeof agentsTemplate.content === 'function' + ? agentsTemplate.content({}) + : agentsTemplate.content; + await FileSystemUtils.writeFile(agentsPath, content); + } // 3. Update existing AI tool configuration files only const configurators = ToolRegistry.getAll(); @@ -50,7 +61,7 @@ export class UpdateCommand { ); } - await configurator.configure(resolvedProjectPath, openspecPath); + await configurator.configure(resolvedProjectPath, openspecPath, language); updatedFiles.push(configurator.configFileName); if (!fileExists) { @@ -74,7 +85,8 @@ export class UpdateCommand { try { const updated = await slashConfigurator.updateExisting( resolvedProjectPath, - openspecPath + openspecPath, + language ); updatedSlashFiles.push(...updated); } catch (error) { diff --git a/src/utils/file-system.ts b/src/utils/file-system.ts index fc892a11..a3f4d42a 100644 --- a/src/utils/file-system.ts +++ b/src/utils/file-system.ts @@ -184,4 +184,22 @@ export class FileSystemUtils { return false; } } + + static async readJsonFile(filePath: string): Promise { + try { + if (!await this.fileExists(filePath)) { + return null; + } + const content = await this.readFile(filePath); + return JSON.parse(content) as T; + } catch (error: any) { + console.debug(`Unable to read JSON file at ${filePath}: ${error.message}`); + return null; + } + } + + static async writeJsonFile(filePath: string, data: any): Promise { + const content = JSON.stringify(data, null, 2) + '\n'; + await this.writeFile(filePath, content); + } } diff --git a/test/core/init.test.ts b/test/core/init.test.ts index dad27726..b5c5a233 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -4,6 +4,16 @@ import path from 'path'; import os from 'os'; import { InitCommand } from '../../src/core/init.js'; +// Mock @inquirer/prompts for language selection +const mockSelect = vi.fn(); +vi.mock('@inquirer/prompts', async () => { + const actual = await vi.importActual('@inquirer/prompts'); + return { + ...actual, + select: mockSelect, + }; +}); + const DONE = '__done__'; type SelectionQueue = string[][]; @@ -43,6 +53,7 @@ describe('InitCommand', () => { await fs.mkdir(testDir, { recursive: true }); selectionQueue = []; mockPrompt.mockReset(); + mockSelect.mockReset(); initCommand = new InitCommand({ prompt: mockPrompt }); // Route Codex global directory into the test sandbox @@ -51,6 +62,11 @@ describe('InitCommand', () => { // Mock console.log to suppress output during tests vi.spyOn(console, 'log').mockImplementation(() => {}); + + // Default language selection mock - return default language unless overridden + mockSelect.mockImplementation(async (options: any) => { + return options.default || 'en-US'; + }); }); afterEach(async () => { @@ -1248,6 +1264,147 @@ describe('InitCommand', () => { }); describe('non-interactive mode', () => { + it('should create config.json with language setting when --language is provided', async () => { + const command = new InitCommand({ tools: 'none', language: 'zh-CN' }); + await command.execute(testDir); + + const configPath = path.join(testDir, 'openspec', 'config.json'); + expect(await fileExists(configPath)).toBe(true); + + const configContent = await fs.readFile(configPath, 'utf-8'); + const config = JSON.parse(configContent); + expect(config.language).toBe('zh-CN'); + }); + + it('should use Chinese templates when language is zh-CN', async () => { + const command = new InitCommand({ tools: 'none', language: 'zh-CN' }); + await command.execute(testDir); + + const projectPath = path.join(testDir, 'openspec', 'project.md'); + expect(await fileExists(projectPath)).toBe(true); + + const projectContent = await fs.readFile(projectPath, 'utf-8'); + expect(projectContent).toContain('项目'); + expect(projectContent).toContain('技术栈'); + + // Verify AGENTS.md is also in Chinese + const agentsPath = path.join(testDir, 'openspec', 'AGENTS.md'); + expect(await fileExists(agentsPath)).toBe(true); + const agentsContent = await fs.readFile(agentsPath, 'utf-8'); + expect(agentsContent).toContain('OpenSpec 说明'); + expect(agentsContent).toContain('快速检查清单'); + expect(agentsContent).toContain('三阶段工作流'); + }); + + it('should default to en-US when no language is specified', async () => { + const command = new InitCommand({ tools: 'none' }); + await command.execute(testDir); + + const configPath = path.join(testDir, 'openspec', 'config.json'); + if (await fileExists(configPath)) { + const configContent = await fs.readFile(configPath, 'utf-8'); + const config = JSON.parse(configContent); + expect(config.language).toBe('en-US'); + } + + const projectPath = path.join(testDir, 'openspec', 'project.md'); + const projectContent = await fs.readFile(projectPath, 'utf-8'); + expect(projectContent).toContain('Project Context'); + }); + + it('should read existing language config in extend mode and allow changing', async () => { + // First init with Chinese + const firstCommand = new InitCommand({ tools: 'none', language: 'zh-CN' }); + await firstCommand.execute(testDir); + + // Second init in extend mode - should prompt with Chinese as default, but allow changing + // Mock language selection to return French (fr-FR) + mockSelect.mockResolvedValueOnce('fr-FR'); + queueSelections(DONE); + await initCommand.execute(testDir); + + // Verify language was changed to French + const configPath = path.join(testDir, 'openspec', 'config.json'); + const configContent = await fs.readFile(configPath, 'utf-8'); + const config = JSON.parse(configContent); + expect(config.language).toBe('fr-FR'); + + // Verify French templates were used + const projectPath = path.join(testDir, 'openspec', 'project.md'); + const projectContent = await fs.readFile(projectPath, 'utf-8'); + expect(projectContent).toContain('Contexte'); + + // Verify language prompt was called with Chinese as default + expect(mockSelect).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Select your preferred language:', + default: 'zh-CN', + }) + ); + }); + + it('should use existing language as default in extend mode when user confirms', async () => { + // First init with Japanese + const firstCommand = new InitCommand({ tools: 'none', language: 'ja-JP' }); + await firstCommand.execute(testDir); + + // Second init in extend mode - user confirms default (Japanese) + // Mock language selection to return Japanese (default) + mockSelect.mockResolvedValueOnce('ja-JP'); + queueSelections(DONE); + await initCommand.execute(testDir); + + // Verify language remains Japanese + const configPath = path.join(testDir, 'openspec', 'config.json'); + const configContent = await fs.readFile(configPath, 'utf-8'); + const config = JSON.parse(configContent); + expect(config.language).toBe('ja-JP'); + + // Verify language prompt was called with Japanese as default + expect(mockSelect).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Select your preferred language:', + default: 'ja-JP', + }) + ); + }); + + it('should use French templates when language is fr-FR', async () => { + const command = new InitCommand({ tools: 'none', language: 'fr-FR' }); + await command.execute(testDir); + + const projectPath = path.join(testDir, 'openspec', 'project.md'); + expect(await fileExists(projectPath)).toBe(true); + + const projectContent = await fs.readFile(projectPath, 'utf-8'); + expect(projectContent).toContain('Contexte'); + expect(projectContent).toContain('Pile Technologique'); + }); + + it('should use Japanese templates when language is ja-JP', async () => { + const command = new InitCommand({ tools: 'none', language: 'ja-JP' }); + await command.execute(testDir); + + const projectPath = path.join(testDir, 'openspec', 'project.md'); + expect(await fileExists(projectPath)).toBe(true); + + const projectContent = await fs.readFile(projectPath, 'utf-8'); + expect(projectContent).toContain('コンテキスト'); + expect(projectContent).toContain('技術スタック'); + }); + + it('should use Arabic templates when language is ar-SA', async () => { + const command = new InitCommand({ tools: 'none', language: 'ar-SA' }); + await command.execute(testDir); + + const projectPath = path.join(testDir, 'openspec', 'project.md'); + expect(await fileExists(projectPath)).toBe(true); + + const projectContent = await fs.readFile(projectPath, 'utf-8'); + expect(projectContent).toContain('سياق'); + expect(projectContent).toContain('المكدس التقني'); + }); + it('should select all available tools with --tools all option', async () => { const nonInteractiveCommand = new InitCommand({ tools: 'all' });