mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-04 22:47:37 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31c78a5e38 |
@@ -0,0 +1,217 @@
|
||||
# Multi-Language Output Guide
|
||||
|
||||
Configure OpenSpec to generate artifacts in languages other than English.
|
||||
|
||||
## Overview
|
||||
|
||||
OpenSpec can output proposals, specs, designs, and tasks in any language by adding language instructions to your project's `context` configuration. This approach uses the existing config system without requiring any code changes.
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### If you already have a config file
|
||||
|
||||
Add a language instruction to your existing `openspec/config.yaml`:
|
||||
|
||||
```yaml
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
Language: Portuguese (pt-BR)
|
||||
All artifacts must be written in Brazilian Portuguese.
|
||||
Use Portuguese technical terminology where appropriate.
|
||||
|
||||
# Your other project context below...
|
||||
Tech stack: TypeScript, React, Node.js
|
||||
```
|
||||
|
||||
That's it. All generated artifacts will now be in Portuguese.
|
||||
|
||||
### If you don't have a config file yet
|
||||
|
||||
You can create one interactively or manually:
|
||||
|
||||
**Option 1: Interactive setup**
|
||||
|
||||
```bash
|
||||
openspec artifact-experimental-setup
|
||||
```
|
||||
|
||||
This will guide you through creating `openspec/config.yaml` with schema selection, context, and rules.
|
||||
|
||||
**Option 2: Manual creation**
|
||||
|
||||
Create the file `openspec/config.yaml` in your project root:
|
||||
|
||||
```bash
|
||||
mkdir -p openspec
|
||||
cat > openspec/config.yaml << 'EOF'
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
Language: Portuguese (pt-BR)
|
||||
All artifacts must be written in Brazilian Portuguese.
|
||||
EOF
|
||||
```
|
||||
|
||||
## Language Examples
|
||||
|
||||
### Portuguese (Brazil)
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
Language: Portuguese (pt-BR)
|
||||
All artifacts (proposals, specs, designs, tasks) must be written in Brazilian Portuguese.
|
||||
Use Brazilian Portuguese conventions for technical terminology.
|
||||
```
|
||||
|
||||
### Spanish
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
Idioma: Español
|
||||
Todos los artefactos deben escribirse en español.
|
||||
Utilizar terminología técnica en español cuando sea posible.
|
||||
```
|
||||
|
||||
### Chinese (Simplified)
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
语言:中文(简体)
|
||||
所有产出物(提案、规格、设计、任务)必须用简体中文撰写。
|
||||
技术术语可以保留英文原文,但说明应使用中文。
|
||||
```
|
||||
|
||||
### Japanese
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
言語:日本語
|
||||
すべての成果物は日本語で作成してください。
|
||||
技術用語は必要に応じて英語を併記可能です。
|
||||
```
|
||||
|
||||
### French
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
Langue : Français
|
||||
Tous les artefacts doivent être rédigés en français.
|
||||
Utiliser la terminologie technique française lorsque possible.
|
||||
```
|
||||
|
||||
### German
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
Sprache: Deutsch
|
||||
Alle Artefakte müssen auf Deutsch verfasst werden.
|
||||
Technische Fachbegriffe können auf Englisch beibehalten werden.
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The `context` field in `openspec/config.yaml` is injected into every artifact's instructions as an XML block:
|
||||
|
||||
```xml
|
||||
<context>
|
||||
Language: Portuguese (pt-BR)
|
||||
All artifacts must be written in Brazilian Portuguese.
|
||||
...
|
||||
</context>
|
||||
|
||||
<template>
|
||||
[Schema's template content]
|
||||
</template>
|
||||
```
|
||||
|
||||
The AI assistant sees this context when generating any artifact and follows the language instruction.
|
||||
|
||||
## Tips
|
||||
|
||||
### Be Explicit About Scope
|
||||
|
||||
Specify which parts should be in the target language:
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
Language: Spanish
|
||||
Write all prose, descriptions, and explanations in Spanish.
|
||||
Code comments may remain in English for consistency with the codebase.
|
||||
Variable names and code identifiers should stay in English.
|
||||
```
|
||||
|
||||
### Handle Technical Terms
|
||||
|
||||
Decide how to handle technical terminology:
|
||||
|
||||
```yaml
|
||||
context: |
|
||||
Language: Japanese
|
||||
Write in Japanese, but:
|
||||
- Keep technical terms like "API", "REST", "GraphQL" in English
|
||||
- Provide Japanese explanations in parentheses for complex terms on first use
|
||||
- Code examples and file paths remain in English
|
||||
```
|
||||
|
||||
### Combine with Other Context
|
||||
|
||||
Language settings work alongside your other project context:
|
||||
|
||||
```yaml
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
Language: Portuguese (pt-BR)
|
||||
All artifacts must be written in Brazilian Portuguese.
|
||||
|
||||
Tech stack: TypeScript, React 18, Node.js 20
|
||||
Database: PostgreSQL with Prisma ORM
|
||||
Testing: Vitest + React Testing Library
|
||||
|
||||
Conventions:
|
||||
- Use functional components with hooks
|
||||
- Follow existing patterns in src/components/
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
To verify your language config is working:
|
||||
|
||||
```bash
|
||||
# Create a test change
|
||||
openspec new change test-language
|
||||
|
||||
# Check the instructions - should show your language context
|
||||
openspec instructions proposal --change test-language
|
||||
|
||||
# Output will include:
|
||||
# <context>
|
||||
# Language: Portuguese (pt-BR)
|
||||
# All artifacts must be written in Brazilian Portuguese.
|
||||
# ...
|
||||
# </context>
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Language instructions apply to **all** artifacts. You cannot set different languages for different artifact types.
|
||||
- The effectiveness depends on the AI model's proficiency in the target language.
|
||||
- Technical diagrams, code samples, and file paths typically remain in English regardless of language setting.
|
||||
|
||||
## Future Considerations
|
||||
|
||||
We're considering adding a dedicated `language` field to the config schema in a future release:
|
||||
|
||||
```yaml
|
||||
# Potential future syntax (not yet implemented)
|
||||
schema: spec-driven
|
||||
language: pt-BR
|
||||
```
|
||||
|
||||
For now, the `context` approach described above is the recommended method. It provides full flexibility and works with all current versions of OpenSpec.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Project Config Demo](./project-config-demo.md) - Overview of project configuration
|
||||
- [Experimental Workflow Guide](./experimental-workflow.md) - Full workflow documentation
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-20
|
||||
@@ -1,50 +0,0 @@
|
||||
## Why
|
||||
|
||||
When users start a new change with `/opsx:new`, their initial description gets lost. The system creates an empty folder and shows a template, but the user's intent—the thing they said they wanted to build—isn't captured anywhere. They have to describe it again when creating the proposal.
|
||||
|
||||
This adds friction and breaks the natural flow of: idea → capture → expand → implement.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Introduce "napkin" as a lightweight precursor to a full change
|
||||
- Napkins are simple markdown files: `<name>.napkin.md` in `openspec/changes/`
|
||||
- When promoted via `/opsx:continue`, napkin content seeds the proposal and the napkin file is deleted
|
||||
- Full changes remain as folders with artifacts (no change to existing behavior)
|
||||
|
||||
**File structure:**
|
||||
```
|
||||
openspec/changes/
|
||||
├── dark-mode.napkin.md ← napkin (lightweight, just an idea)
|
||||
├── auth-refactor/ ← full change (folder with artifacts)
|
||||
│ ├── .openspec.yaml
|
||||
│ ├── proposal.md
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
**Lifecycle:**
|
||||
1. `/opsx:new "add dark mode"` → creates `dark-mode.napkin.md` with user's description
|
||||
2. User can edit, add notes, refine thinking
|
||||
3. `/opsx:continue` → promotes napkin to full change folder, creates `proposal.md` using napkin content
|
||||
4. `dark-mode.napkin.md` is deleted after promotion
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `napkin-changes`: Support for lightweight napkin files (`.napkin.md`) as precursors to full changes, including creation, listing, and promotion to full change folders
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `change-creation`: Add support for creating napkin files instead of folders
|
||||
- `cli-change`: Update `openspec new change` to support napkin creation
|
||||
- `cli-list`: Show napkins alongside full changes in `openspec list` output
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/utils/change-utils.ts` - Add napkin creation/detection utilities
|
||||
- `src/commands/change.ts` - Support `--napkin` flag or make napkin the default
|
||||
- `src/commands/list.ts` - Include napkins in change listing
|
||||
- `.claude/skills/openspec-new-change/` - Update to create napkins by default
|
||||
- `.claude/skills/openspec-continue-change/` - Handle napkin → full change promotion
|
||||
- `.claude/commands/opsx/new.md` - Update skill instructions
|
||||
- `.claude/commands/opsx/continue.md` - Update to handle napkin promotion
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-20
|
||||
@@ -1,28 +0,0 @@
|
||||
## Why
|
||||
|
||||
We want to rename `spec-driven` to `openspec-default` to better reflect that it's the standard/default workflow. However, renaming directly would break existing projects that have `schema: spec-driven` in their `openspec/config.yaml`. Adding alias support allows both names to work interchangeably, enabling a smooth transition with no breaking changes.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add schema alias resolution in the schema resolver
|
||||
- `openspec-default` and `spec-driven` will both resolve to the same schema
|
||||
- The physical directory remains `schemas/spec-driven/` (or could be renamed to `schemas/openspec-default/` with `spec-driven` as the alias)
|
||||
- All CLI commands and config files accept either name
|
||||
- No changes required to existing user configs
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `schema-aliases`: Support for schema name aliases so multiple names can resolve to the same schema directory
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
<!-- No existing spec-level behavior is changing - this is purely additive -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `src/core/artifact-graph/resolver.ts` - Add alias resolution logic
|
||||
- `schemas/` directory - Potentially rename `spec-driven` to `openspec-default`
|
||||
- Documentation - Update to prefer `openspec-default` while noting `spec-driven` still works
|
||||
- Default schema constants - Update `DEFAULT_SCHEMA` to `openspec-default`
|
||||
@@ -28,9 +28,9 @@ import {
|
||||
type SchemaInfo,
|
||||
} from '../core/artifact-graph/index.js';
|
||||
import { createChange, validateChangeName } from '../utils/change-utils.js';
|
||||
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
|
||||
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
|
||||
import { FileSystemUtils } from '../utils/file-system.js';
|
||||
import { serializeConfig } from '../core/config-prompts.js';
|
||||
import { promptForConfig, serializeConfig, isExitPromptError } from '../core/config-prompts.js';
|
||||
import { readProjectConfig } from '../core/project-config.js';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -818,7 +818,6 @@ async function artifactExperimentalSetupCommand(): Promise<void> {
|
||||
const ffChangeSkill = getFfChangeSkillTemplate();
|
||||
const syncSpecsSkill = getSyncSpecsSkillTemplate();
|
||||
const archiveChangeSkill = getArchiveChangeSkillTemplate();
|
||||
const bulkArchiveChangeSkill = getBulkArchiveChangeSkillTemplate();
|
||||
const verifyChangeSkill = getVerifyChangeSkillTemplate();
|
||||
|
||||
// Get command templates
|
||||
@@ -829,7 +828,6 @@ async function artifactExperimentalSetupCommand(): Promise<void> {
|
||||
const ffCommand = getOpsxFfCommandTemplate();
|
||||
const syncCommand = getOpsxSyncCommandTemplate();
|
||||
const archiveCommand = getOpsxArchiveCommandTemplate();
|
||||
const bulkArchiveCommand = getOpsxBulkArchiveCommandTemplate();
|
||||
const verifyCommand = getOpsxVerifyCommandTemplate();
|
||||
|
||||
// Create skill directories and SKILL.md files
|
||||
@@ -841,7 +839,6 @@ async function artifactExperimentalSetupCommand(): Promise<void> {
|
||||
{ template: ffChangeSkill, dirName: 'openspec-ff-change' },
|
||||
{ template: syncSpecsSkill, dirName: 'openspec-sync-specs' },
|
||||
{ template: archiveChangeSkill, dirName: 'openspec-archive-change' },
|
||||
{ template: bulkArchiveChangeSkill, dirName: 'openspec-bulk-archive-change' },
|
||||
{ template: verifyChangeSkill, dirName: 'openspec-verify-change' },
|
||||
];
|
||||
|
||||
@@ -874,7 +871,6 @@ ${template.instructions}
|
||||
{ template: ffCommand, fileName: 'ff.md' },
|
||||
{ template: syncCommand, fileName: 'sync.md' },
|
||||
{ template: archiveCommand, fileName: 'archive.md' },
|
||||
{ template: bulkArchiveCommand, fileName: 'bulk-archive.md' },
|
||||
{ template: verifyCommand, fileName: 'verify.md' },
|
||||
];
|
||||
|
||||
@@ -945,37 +941,89 @@ ${template.content}
|
||||
console.log(chalk.dim(' schema: spec-driven'));
|
||||
console.log();
|
||||
} else {
|
||||
// Create config with default schema
|
||||
const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
|
||||
|
||||
// Prompt for config creation
|
||||
try {
|
||||
await FileSystemUtils.writeFile(configPath, yamlContent);
|
||||
const configResult = await promptForConfig(projectRoot);
|
||||
|
||||
console.log();
|
||||
console.log(chalk.green('✓ Created openspec/config.yaml'));
|
||||
console.log();
|
||||
console.log(` Default schema: ${chalk.cyan(DEFAULT_SCHEMA)}`);
|
||||
console.log();
|
||||
console.log(chalk.dim(' Edit the file to add project context and per-artifact rules.'));
|
||||
console.log();
|
||||
if (configResult.createConfig && configResult.schema) {
|
||||
// Build config object
|
||||
const config = {
|
||||
schema: configResult.schema,
|
||||
context: configResult.context,
|
||||
rules: configResult.rules,
|
||||
};
|
||||
|
||||
// Git commit suggestion
|
||||
console.log(chalk.bold('To share with team:'));
|
||||
console.log(chalk.dim(' git add openspec/config.yaml .claude/'));
|
||||
console.log(chalk.dim(' git commit -m "Setup OpenSpec experimental workflow"'));
|
||||
console.log();
|
||||
} catch (writeError) {
|
||||
// Handle file write errors
|
||||
console.error();
|
||||
console.error(chalk.red('✗ Failed to write openspec/config.yaml'));
|
||||
console.error(chalk.dim(` ${(writeError as Error).message}`));
|
||||
console.error();
|
||||
console.error('Fallback: Create config manually:');
|
||||
console.error(chalk.dim(' 1. Create openspec/config.yaml'));
|
||||
console.error(chalk.dim(' 2. Copy the following content:'));
|
||||
console.error();
|
||||
console.error(chalk.dim(yamlContent));
|
||||
console.error();
|
||||
// Serialize to YAML
|
||||
const yamlContent = serializeConfig(config);
|
||||
|
||||
// Write config file
|
||||
try {
|
||||
await FileSystemUtils.writeFile(configPath, yamlContent);
|
||||
|
||||
console.log();
|
||||
console.log(chalk.green('✓ Created openspec/config.yaml'));
|
||||
console.log();
|
||||
console.log('━'.repeat(70));
|
||||
console.log();
|
||||
console.log(chalk.bold('📖 Config created at: openspec/config.yaml'));
|
||||
|
||||
// Display summary
|
||||
const contextLines = config.context ? config.context.split('\n').length : 0;
|
||||
const rulesCount = config.rules ? Object.keys(config.rules).length : 0;
|
||||
|
||||
console.log(` • Default schema: ${chalk.cyan(config.schema)}`);
|
||||
if (contextLines > 0) {
|
||||
console.log(` • Project context: ${chalk.cyan(`Added (${contextLines} lines)`)}`);
|
||||
}
|
||||
if (rulesCount > 0) {
|
||||
console.log(` • Rules: ${chalk.cyan(`${rulesCount} artifact${rulesCount > 1 ? 's' : ''} configured`)}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Usage examples
|
||||
console.log(chalk.bold('Usage:'));
|
||||
console.log(' • New changes automatically use this schema');
|
||||
console.log(' • Context injected into all artifact instructions');
|
||||
console.log(' • Rules applied to matching artifacts');
|
||||
console.log();
|
||||
|
||||
// Git commit suggestion
|
||||
console.log(chalk.bold('To share with team:'));
|
||||
console.log(chalk.dim(' git add openspec/config.yaml .claude/'));
|
||||
console.log(chalk.dim(' git commit -m "Setup OpenSpec experimental workflow with project config"'));
|
||||
console.log();
|
||||
} catch (writeError) {
|
||||
// Handle file write errors
|
||||
console.error();
|
||||
console.error(chalk.red('✗ Failed to write openspec/config.yaml'));
|
||||
console.error(chalk.dim(` ${(writeError as Error).message}`));
|
||||
console.error();
|
||||
console.error('Fallback: Create config manually:');
|
||||
console.error(chalk.dim(' 1. Create openspec/config.yaml'));
|
||||
console.error(chalk.dim(' 2. Copy the following content:'));
|
||||
console.error();
|
||||
console.error(chalk.dim(yamlContent));
|
||||
console.error();
|
||||
}
|
||||
} else {
|
||||
// User chose not to create config
|
||||
console.log();
|
||||
console.log(chalk.blue('ℹ️ Skipped config creation.'));
|
||||
console.log(' You can create openspec/config.yaml manually later.');
|
||||
console.log();
|
||||
}
|
||||
} catch (promptError) {
|
||||
if (isExitPromptError(promptError)) {
|
||||
// User cancelled (Ctrl+C)
|
||||
console.log();
|
||||
console.log(chalk.blue('ℹ️ Config creation cancelled'));
|
||||
console.log(' Skills and commands already created');
|
||||
console.log(' Run setup again to create config later');
|
||||
console.log();
|
||||
} else {
|
||||
// Unexpected error
|
||||
throw promptError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+187
-27
@@ -1,39 +1,199 @@
|
||||
import { stringify as stringifyYaml } from 'yaml';
|
||||
import { listSchemasWithInfo, resolveSchema } from './artifact-graph/resolver.js';
|
||||
import type { ProjectConfig } from './project-config.js';
|
||||
|
||||
/**
|
||||
* Serialize config to YAML string with helpful comments.
|
||||
* Check if an error is an ExitPromptError (user cancelled with Ctrl+C).
|
||||
* Used instead of instanceof check since @inquirer modules use dynamic imports.
|
||||
*/
|
||||
export function isExitPromptError(error: unknown): boolean {
|
||||
return (
|
||||
error !== null &&
|
||||
typeof error === 'object' &&
|
||||
'name' in error &&
|
||||
(error as { name: string }).name === 'ExitPromptError'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of interactive config creation prompts.
|
||||
*/
|
||||
export interface ConfigPromptResult {
|
||||
/** Whether to create config file */
|
||||
createConfig: boolean;
|
||||
/** Selected schema name */
|
||||
schema?: string;
|
||||
/** Project context (optional) */
|
||||
context?: string;
|
||||
/** Per-artifact rules (optional) */
|
||||
rules?: Record<string, string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user to create project config interactively.
|
||||
* Used by experimental setup command.
|
||||
*
|
||||
* @param projectRoot - Optional project root for project-local schema resolution
|
||||
* @returns Config prompt result
|
||||
* @throws ExitPromptError if user cancels (Ctrl+C)
|
||||
*/
|
||||
export async function promptForConfig(
|
||||
projectRoot?: string
|
||||
): Promise<ConfigPromptResult> {
|
||||
// Dynamic imports to prevent pre-commit hook hangs (see #367)
|
||||
const { confirm, select, editor, checkbox } = await import('@inquirer/prompts');
|
||||
|
||||
// Ask if user wants to create config
|
||||
const shouldCreate = await confirm({
|
||||
message: 'Create openspec/config.yaml?',
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (!shouldCreate) {
|
||||
return { createConfig: false };
|
||||
}
|
||||
|
||||
// Get available schemas
|
||||
const schemas = listSchemasWithInfo(projectRoot);
|
||||
|
||||
if (schemas.length === 0) {
|
||||
throw new Error('No schemas found. Cannot create config.');
|
||||
}
|
||||
|
||||
// Prompt for schema selection
|
||||
const selectedSchema = await select({
|
||||
message: 'Default schema for new changes?',
|
||||
choices: schemas.map((s) => ({
|
||||
name: `${s.name} (${s.artifacts.join(' → ')})`,
|
||||
value: s.name,
|
||||
description: s.description || undefined,
|
||||
})),
|
||||
});
|
||||
|
||||
// Prompt for project context
|
||||
console.log('\nAdd project context? (optional)');
|
||||
console.log('Context is shown to AI when creating artifacts.');
|
||||
console.log('Examples: tech stack, conventions, style guides, domain knowledge\n');
|
||||
|
||||
const contextInput = await editor({
|
||||
message: 'Press Enter to skip, or edit context:',
|
||||
default: '',
|
||||
waitForUseInput: false,
|
||||
});
|
||||
|
||||
const context = contextInput.trim() || undefined;
|
||||
|
||||
// Prompt for per-artifact rules
|
||||
const addRules = await confirm({
|
||||
message: 'Add per-artifact rules? (optional)',
|
||||
default: false,
|
||||
});
|
||||
|
||||
let rules: Record<string, string[]> | undefined;
|
||||
|
||||
if (addRules) {
|
||||
// Load the selected schema to get artifact list
|
||||
const schema = resolveSchema(selectedSchema, projectRoot);
|
||||
const artifactIds = schema.artifacts.map((a) => a.id);
|
||||
|
||||
// Let user select which artifacts to add rules for
|
||||
const selectedArtifacts = await checkbox({
|
||||
message: 'Which artifacts should have custom rules?',
|
||||
choices: artifactIds.map((id) => ({
|
||||
name: id,
|
||||
value: id,
|
||||
})),
|
||||
});
|
||||
|
||||
if (selectedArtifacts.length > 0) {
|
||||
rules = {};
|
||||
|
||||
// For each selected artifact, collect rules line by line
|
||||
for (const artifactId of selectedArtifacts) {
|
||||
const artifactRules = await promptForArtifactRules(artifactId);
|
||||
if (artifactRules.length > 0) {
|
||||
rules[artifactId] = artifactRules;
|
||||
}
|
||||
}
|
||||
|
||||
// If no rules were actually added, set to undefined
|
||||
if (Object.keys(rules).length === 0) {
|
||||
rules = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
createConfig: true,
|
||||
schema: selectedSchema,
|
||||
context,
|
||||
rules,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for rules for a specific artifact.
|
||||
* Collects rules one per line until user enters empty line.
|
||||
*
|
||||
* @param artifactId - The artifact ID to collect rules for
|
||||
* @returns Array of rules
|
||||
*/
|
||||
async function promptForArtifactRules(artifactId: string): Promise<string[]> {
|
||||
// Dynamic import to prevent pre-commit hook hangs (see #367)
|
||||
const { input } = await import('@inquirer/prompts');
|
||||
|
||||
const rules: string[] = [];
|
||||
|
||||
console.log(`\nRules for ${artifactId} artifact:`);
|
||||
console.log('Enter rules one per line, press Enter on empty line to finish:\n');
|
||||
|
||||
while (true) {
|
||||
const rule = await input({
|
||||
message: '│',
|
||||
validate: () => {
|
||||
// Empty string is valid (signals end of input)
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
const trimmed = rule.trim();
|
||||
|
||||
// Empty line signals end of input
|
||||
if (!trimmed) {
|
||||
break;
|
||||
}
|
||||
|
||||
rules.push(trimmed);
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize config to YAML string with proper multi-line formatting.
|
||||
*
|
||||
* @param config - Partial config object (schema required, context/rules optional)
|
||||
* @returns YAML string ready to write to file
|
||||
*/
|
||||
export function serializeConfig(config: Partial<ProjectConfig>): string {
|
||||
const lines: string[] = [];
|
||||
// Build clean config object (only include defined fields)
|
||||
const cleanConfig: Record<string, unknown> = {
|
||||
schema: config.schema,
|
||||
};
|
||||
|
||||
// Schema (required)
|
||||
lines.push(`schema: ${config.schema}`);
|
||||
lines.push('');
|
||||
if (config.context) {
|
||||
cleanConfig.context = config.context;
|
||||
}
|
||||
|
||||
// Context section with comments
|
||||
lines.push('# Project context (optional)');
|
||||
lines.push('# This is shown to AI when creating artifacts.');
|
||||
lines.push('# Add your tech stack, conventions, style guides, domain knowledge, etc.');
|
||||
lines.push('# Example:');
|
||||
lines.push('# context: |');
|
||||
lines.push('# Tech stack: TypeScript, React, Node.js');
|
||||
lines.push('# We use conventional commits');
|
||||
lines.push('# Domain: e-commerce platform');
|
||||
lines.push('');
|
||||
if (config.rules && Object.keys(config.rules).length > 0) {
|
||||
cleanConfig.rules = config.rules;
|
||||
}
|
||||
|
||||
// Rules section with comments
|
||||
lines.push('# Per-artifact rules (optional)');
|
||||
lines.push('# Add custom rules for specific artifacts.');
|
||||
lines.push('# Example:');
|
||||
lines.push('# rules:');
|
||||
lines.push('# proposal:');
|
||||
lines.push('# - Keep proposals under 500 words');
|
||||
lines.push('# - Always include a "Non-goals" section');
|
||||
lines.push('# tasks:');
|
||||
lines.push('# - Break tasks into chunks of max 2 hours');
|
||||
|
||||
return lines.join('\n') + '\n';
|
||||
// Serialize to YAML with proper formatting
|
||||
return stringifyYaml(cleanConfig, {
|
||||
indent: 2,
|
||||
lineWidth: 0, // Don't wrap long lines
|
||||
defaultStringType: 'PLAIN',
|
||||
defaultKeyType: 'PLAIN',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1634,252 +1634,6 @@ All artifacts complete. All tasks complete.
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for openspec-bulk-archive-change skill
|
||||
* For archiving multiple completed changes at once
|
||||
*/
|
||||
export function getBulkArchiveChangeSkillTemplate(): SkillTemplate {
|
||||
return {
|
||||
name: 'openspec-bulk-archive-change',
|
||||
description: 'Archive multiple completed changes at once. Use when archiving several parallel changes.',
|
||||
instructions: `Archive multiple completed changes in a single operation.
|
||||
|
||||
This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
|
||||
|
||||
**Input**: None required (prompts for selection)
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **Get active changes**
|
||||
|
||||
Run \`openspec list --json\` to get all active changes.
|
||||
|
||||
If no active changes exist, inform user and stop.
|
||||
|
||||
2. **Prompt for change selection**
|
||||
|
||||
Use **AskUserQuestion tool** with multi-select to let user choose changes:
|
||||
- Show each change with its schema
|
||||
- Include an option for "All changes"
|
||||
- Allow any number of selections (1+ works, 2+ is the typical use case)
|
||||
|
||||
**IMPORTANT**: Do NOT auto-select. Always let the user choose.
|
||||
|
||||
3. **Batch validation - gather status for all selected changes**
|
||||
|
||||
For each selected change, collect:
|
||||
|
||||
a. **Artifact status** - Run \`openspec status --change "<name>" --json\`
|
||||
- Parse \`schemaName\` and \`artifacts\` list
|
||||
- Note which artifacts are \`done\` vs other states
|
||||
|
||||
b. **Task completion** - Read \`openspec/changes/<name>/tasks.md\`
|
||||
- Count \`- [ ]\` (incomplete) vs \`- [x]\` (complete)
|
||||
- If no tasks file exists, note as "No tasks"
|
||||
|
||||
c. **Delta specs** - Check \`openspec/changes/<name>/specs/\` directory
|
||||
- List which capability specs exist
|
||||
- For each, extract requirement names (lines matching \`### Requirement: <name>\`)
|
||||
|
||||
4. **Detect spec conflicts**
|
||||
|
||||
Build a map of \`capability -> [changes that touch it]\`:
|
||||
|
||||
\`\`\`
|
||||
auth -> [change-a, change-b] <- CONFLICT (2+ changes)
|
||||
api -> [change-c] <- OK (only 1 change)
|
||||
\`\`\`
|
||||
|
||||
A conflict exists when 2+ selected changes have delta specs for the same capability.
|
||||
|
||||
5. **Resolve conflicts agentically**
|
||||
|
||||
**For each conflict**, investigate the codebase:
|
||||
|
||||
a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
|
||||
|
||||
b. **Search the codebase** for implementation evidence:
|
||||
- Look for code implementing requirements from each delta spec
|
||||
- Check for related files, functions, or tests
|
||||
|
||||
c. **Determine resolution**:
|
||||
- If only one change is actually implemented -> sync that one's specs
|
||||
- If both implemented -> apply in chronological order (older first, newer overwrites)
|
||||
- If neither implemented -> skip spec sync, warn user
|
||||
|
||||
d. **Record resolution** for each conflict:
|
||||
- Which change's specs to apply
|
||||
- In what order (if both)
|
||||
- Rationale (what was found in codebase)
|
||||
|
||||
6. **Show consolidated status table**
|
||||
|
||||
Display a table summarizing all changes:
|
||||
|
||||
\`\`\`
|
||||
| Change | Artifacts | Tasks | Specs | Conflicts | Status |
|
||||
|---------------------|-----------|-------|---------|-----------|--------|
|
||||
| schema-management | Done | 5/5 | 2 delta | None | Ready |
|
||||
| project-config | Done | 3/3 | 1 delta | None | Ready |
|
||||
| add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |
|
||||
| add-verify-skill | 1 left | 2/5 | None | None | Warn |
|
||||
\`\`\`
|
||||
|
||||
For conflicts, show the resolution:
|
||||
\`\`\`
|
||||
* Conflict resolution:
|
||||
- auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
|
||||
\`\`\`
|
||||
|
||||
For incomplete changes, show warnings:
|
||||
\`\`\`
|
||||
Warnings:
|
||||
- add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
|
||||
\`\`\`
|
||||
|
||||
7. **Confirm batch operation**
|
||||
|
||||
Use **AskUserQuestion tool** with a single confirmation:
|
||||
|
||||
- "Archive N changes?" with options based on status
|
||||
- Options might include:
|
||||
- "Archive all N changes"
|
||||
- "Archive only N ready changes (skip incomplete)"
|
||||
- "Cancel"
|
||||
|
||||
If there are incomplete changes, make clear they'll be archived with warnings.
|
||||
|
||||
8. **Execute archive for each confirmed change**
|
||||
|
||||
Process changes in the determined order (respecting conflict resolution):
|
||||
|
||||
a. **Sync specs** if delta specs exist:
|
||||
- Use the openspec-sync-specs approach (agent-driven intelligent merge)
|
||||
- For conflicts, apply in resolved order
|
||||
- Track if sync was done
|
||||
|
||||
b. **Perform the archive**:
|
||||
\`\`\`bash
|
||||
mkdir -p openspec/changes/archive
|
||||
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||
\`\`\`
|
||||
|
||||
c. **Track outcome** for each change:
|
||||
- Success: archived successfully
|
||||
- Failed: error during archive (record error)
|
||||
- Skipped: user chose not to archive (if applicable)
|
||||
|
||||
9. **Display summary**
|
||||
|
||||
Show final results:
|
||||
|
||||
\`\`\`
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived 3 changes:
|
||||
- schema-management-cli -> archive/2026-01-19-schema-management-cli/
|
||||
- project-config -> archive/2026-01-19-project-config/
|
||||
- add-oauth -> archive/2026-01-19-add-oauth/
|
||||
|
||||
Skipped 1 change:
|
||||
- add-verify-skill (user chose not to archive incomplete)
|
||||
|
||||
Spec sync summary:
|
||||
- 4 delta specs synced to main specs
|
||||
- 1 conflict resolved (auth: applied both in chronological order)
|
||||
\`\`\`
|
||||
|
||||
If any failures:
|
||||
\`\`\`
|
||||
Failed 1 change:
|
||||
- some-change: Archive directory already exists
|
||||
\`\`\`
|
||||
|
||||
**Conflict Resolution Examples**
|
||||
|
||||
Example 1: Only one implemented
|
||||
\`\`\`
|
||||
Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
|
||||
|
||||
Checking add-oauth:
|
||||
- Delta adds "OAuth Provider Integration" requirement
|
||||
- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
|
||||
|
||||
Checking add-jwt:
|
||||
- Delta adds "JWT Token Handling" requirement
|
||||
- Searching codebase... no JWT implementation found
|
||||
|
||||
Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
|
||||
\`\`\`
|
||||
|
||||
Example 2: Both implemented
|
||||
\`\`\`
|
||||
Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
|
||||
|
||||
Checking add-rest-api (created 2026-01-10):
|
||||
- Delta adds "REST Endpoints" requirement
|
||||
- Searching codebase... found src/api/rest.ts
|
||||
|
||||
Checking add-graphql (created 2026-01-15):
|
||||
- Delta adds "GraphQL Schema" requirement
|
||||
- Searching codebase... found src/api/graphql.ts
|
||||
|
||||
Resolution: Both implemented. Will apply add-rest-api specs first,
|
||||
then add-graphql specs (chronological order, newer takes precedence).
|
||||
\`\`\`
|
||||
|
||||
**Output On Success**
|
||||
|
||||
\`\`\`
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
- <change-2> -> archive/YYYY-MM-DD-<change-2>/
|
||||
|
||||
Spec sync summary:
|
||||
- N delta specs synced to main specs
|
||||
- No conflicts (or: M conflicts resolved)
|
||||
\`\`\`
|
||||
|
||||
**Output On Partial Success**
|
||||
|
||||
\`\`\`
|
||||
## Bulk Archive Complete (partial)
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
|
||||
Skipped M changes:
|
||||
- <change-2> (user chose not to archive incomplete)
|
||||
|
||||
Failed K changes:
|
||||
- <change-3>: Archive directory already exists
|
||||
\`\`\`
|
||||
|
||||
**Output When No Changes**
|
||||
|
||||
\`\`\`
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use \`/opsx:new\` to create a new change.
|
||||
\`\`\`
|
||||
|
||||
**Guardrails**
|
||||
- Allow any number of changes (1+ is fine, 2+ is the typical use case)
|
||||
- Always prompt for selection, never auto-select
|
||||
- Detect spec conflicts early and resolve by checking codebase
|
||||
- When both changes are implemented, apply specs in chronological order
|
||||
- Skip spec sync only when implementation is missing (warn user)
|
||||
- Show clear per-change status before confirming
|
||||
- Use single confirmation for entire batch
|
||||
- Track and report all outcomes (success/skip/fail)
|
||||
- Preserve .openspec.yaml when moving to archive
|
||||
- Archive directory target uses current date: YYYY-MM-DD-<name>
|
||||
- If archive target exists, fail that change but continue with others`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for /opsx:sync slash command
|
||||
*/
|
||||
@@ -2349,253 +2103,6 @@ Target archive directory already exists.
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for /opsx:bulk-archive slash command
|
||||
*/
|
||||
export function getOpsxBulkArchiveCommandTemplate(): CommandTemplate {
|
||||
return {
|
||||
name: 'OPSX: Bulk Archive',
|
||||
description: 'Archive multiple completed changes at once',
|
||||
category: 'Workflow',
|
||||
tags: ['workflow', 'archive', 'experimental', 'bulk'],
|
||||
content: `Archive multiple completed changes in a single operation.
|
||||
|
||||
This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
|
||||
|
||||
**Input**: None required (prompts for selection)
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **Get active changes**
|
||||
|
||||
Run \`openspec list --json\` to get all active changes.
|
||||
|
||||
If no active changes exist, inform user and stop.
|
||||
|
||||
2. **Prompt for change selection**
|
||||
|
||||
Use **AskUserQuestion tool** with multi-select to let user choose changes:
|
||||
- Show each change with its schema
|
||||
- Include an option for "All changes"
|
||||
- Allow any number of selections (1+ works, 2+ is the typical use case)
|
||||
|
||||
**IMPORTANT**: Do NOT auto-select. Always let the user choose.
|
||||
|
||||
3. **Batch validation - gather status for all selected changes**
|
||||
|
||||
For each selected change, collect:
|
||||
|
||||
a. **Artifact status** - Run \`openspec status --change "<name>" --json\`
|
||||
- Parse \`schemaName\` and \`artifacts\` list
|
||||
- Note which artifacts are \`done\` vs other states
|
||||
|
||||
b. **Task completion** - Read \`openspec/changes/<name>/tasks.md\`
|
||||
- Count \`- [ ]\` (incomplete) vs \`- [x]\` (complete)
|
||||
- If no tasks file exists, note as "No tasks"
|
||||
|
||||
c. **Delta specs** - Check \`openspec/changes/<name>/specs/\` directory
|
||||
- List which capability specs exist
|
||||
- For each, extract requirement names (lines matching \`### Requirement: <name>\`)
|
||||
|
||||
4. **Detect spec conflicts**
|
||||
|
||||
Build a map of \`capability -> [changes that touch it]\`:
|
||||
|
||||
\`\`\`
|
||||
auth -> [change-a, change-b] <- CONFLICT (2+ changes)
|
||||
api -> [change-c] <- OK (only 1 change)
|
||||
\`\`\`
|
||||
|
||||
A conflict exists when 2+ selected changes have delta specs for the same capability.
|
||||
|
||||
5. **Resolve conflicts agentically**
|
||||
|
||||
**For each conflict**, investigate the codebase:
|
||||
|
||||
a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
|
||||
|
||||
b. **Search the codebase** for implementation evidence:
|
||||
- Look for code implementing requirements from each delta spec
|
||||
- Check for related files, functions, or tests
|
||||
|
||||
c. **Determine resolution**:
|
||||
- If only one change is actually implemented -> sync that one's specs
|
||||
- If both implemented -> apply in chronological order (older first, newer overwrites)
|
||||
- If neither implemented -> skip spec sync, warn user
|
||||
|
||||
d. **Record resolution** for each conflict:
|
||||
- Which change's specs to apply
|
||||
- In what order (if both)
|
||||
- Rationale (what was found in codebase)
|
||||
|
||||
6. **Show consolidated status table**
|
||||
|
||||
Display a table summarizing all changes:
|
||||
|
||||
\`\`\`
|
||||
| Change | Artifacts | Tasks | Specs | Conflicts | Status |
|
||||
|---------------------|-----------|-------|---------|-----------|--------|
|
||||
| schema-management | Done | 5/5 | 2 delta | None | Ready |
|
||||
| project-config | Done | 3/3 | 1 delta | None | Ready |
|
||||
| add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |
|
||||
| add-verify-skill | 1 left | 2/5 | None | None | Warn |
|
||||
\`\`\`
|
||||
|
||||
For conflicts, show the resolution:
|
||||
\`\`\`
|
||||
* Conflict resolution:
|
||||
- auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
|
||||
\`\`\`
|
||||
|
||||
For incomplete changes, show warnings:
|
||||
\`\`\`
|
||||
Warnings:
|
||||
- add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
|
||||
\`\`\`
|
||||
|
||||
7. **Confirm batch operation**
|
||||
|
||||
Use **AskUserQuestion tool** with a single confirmation:
|
||||
|
||||
- "Archive N changes?" with options based on status
|
||||
- Options might include:
|
||||
- "Archive all N changes"
|
||||
- "Archive only N ready changes (skip incomplete)"
|
||||
- "Cancel"
|
||||
|
||||
If there are incomplete changes, make clear they'll be archived with warnings.
|
||||
|
||||
8. **Execute archive for each confirmed change**
|
||||
|
||||
Process changes in the determined order (respecting conflict resolution):
|
||||
|
||||
a. **Sync specs** if delta specs exist:
|
||||
- Use the openspec-sync-specs approach (agent-driven intelligent merge)
|
||||
- For conflicts, apply in resolved order
|
||||
- Track if sync was done
|
||||
|
||||
b. **Perform the archive**:
|
||||
\`\`\`bash
|
||||
mkdir -p openspec/changes/archive
|
||||
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||
\`\`\`
|
||||
|
||||
c. **Track outcome** for each change:
|
||||
- Success: archived successfully
|
||||
- Failed: error during archive (record error)
|
||||
- Skipped: user chose not to archive (if applicable)
|
||||
|
||||
9. **Display summary**
|
||||
|
||||
Show final results:
|
||||
|
||||
\`\`\`
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived 3 changes:
|
||||
- schema-management-cli -> archive/2026-01-19-schema-management-cli/
|
||||
- project-config -> archive/2026-01-19-project-config/
|
||||
- add-oauth -> archive/2026-01-19-add-oauth/
|
||||
|
||||
Skipped 1 change:
|
||||
- add-verify-skill (user chose not to archive incomplete)
|
||||
|
||||
Spec sync summary:
|
||||
- 4 delta specs synced to main specs
|
||||
- 1 conflict resolved (auth: applied both in chronological order)
|
||||
\`\`\`
|
||||
|
||||
If any failures:
|
||||
\`\`\`
|
||||
Failed 1 change:
|
||||
- some-change: Archive directory already exists
|
||||
\`\`\`
|
||||
|
||||
**Conflict Resolution Examples**
|
||||
|
||||
Example 1: Only one implemented
|
||||
\`\`\`
|
||||
Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
|
||||
|
||||
Checking add-oauth:
|
||||
- Delta adds "OAuth Provider Integration" requirement
|
||||
- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
|
||||
|
||||
Checking add-jwt:
|
||||
- Delta adds "JWT Token Handling" requirement
|
||||
- Searching codebase... no JWT implementation found
|
||||
|
||||
Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
|
||||
\`\`\`
|
||||
|
||||
Example 2: Both implemented
|
||||
\`\`\`
|
||||
Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
|
||||
|
||||
Checking add-rest-api (created 2026-01-10):
|
||||
- Delta adds "REST Endpoints" requirement
|
||||
- Searching codebase... found src/api/rest.ts
|
||||
|
||||
Checking add-graphql (created 2026-01-15):
|
||||
- Delta adds "GraphQL Schema" requirement
|
||||
- Searching codebase... found src/api/graphql.ts
|
||||
|
||||
Resolution: Both implemented. Will apply add-rest-api specs first,
|
||||
then add-graphql specs (chronological order, newer takes precedence).
|
||||
\`\`\`
|
||||
|
||||
**Output On Success**
|
||||
|
||||
\`\`\`
|
||||
## Bulk Archive Complete
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
- <change-2> -> archive/YYYY-MM-DD-<change-2>/
|
||||
|
||||
Spec sync summary:
|
||||
- N delta specs synced to main specs
|
||||
- No conflicts (or: M conflicts resolved)
|
||||
\`\`\`
|
||||
|
||||
**Output On Partial Success**
|
||||
|
||||
\`\`\`
|
||||
## Bulk Archive Complete (partial)
|
||||
|
||||
Archived N changes:
|
||||
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
|
||||
|
||||
Skipped M changes:
|
||||
- <change-2> (user chose not to archive incomplete)
|
||||
|
||||
Failed K changes:
|
||||
- <change-3>: Archive directory already exists
|
||||
\`\`\`
|
||||
|
||||
**Output When No Changes**
|
||||
|
||||
\`\`\`
|
||||
## No Changes to Archive
|
||||
|
||||
No active changes found. Use \`/opsx:new\` to create a new change.
|
||||
\`\`\`
|
||||
|
||||
**Guardrails**
|
||||
- Allow any number of changes (1+ is fine, 2+ is the typical use case)
|
||||
- Always prompt for selection, never auto-select
|
||||
- Detect spec conflicts early and resolve by checking codebase
|
||||
- When both changes are implemented, apply specs in chronological order
|
||||
- Skip spec sync only when implementation is missing (warn user)
|
||||
- Show clear per-change status before confirming
|
||||
- Use single confirmation for entire batch
|
||||
- Track and report all outcomes (success/skip/fail)
|
||||
- Preserve .openspec.yaml when moving to archive
|
||||
- Archive directory target uses current date: YYYY-MM-DD-<name>
|
||||
- If archive target exists, fail that change but continue with others`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for /opsx:verify slash command
|
||||
*/
|
||||
|
||||
+2
-5
@@ -7,9 +7,6 @@ export async function setup() {
|
||||
|
||||
// Global teardown to ensure clean exit
|
||||
export async function teardown() {
|
||||
// Force exit after a short grace period if the process hasn't exited cleanly.
|
||||
// This handles cases where child processes or open handles keep the worker alive.
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 1000).unref();
|
||||
// Clear any remaining timers
|
||||
// This helps prevent hanging handles from keeping the process alive
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user