openspec-cn 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +153 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +480 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +257 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +198 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -0
- package/dist/commands/workflow/index.d.ts +17 -0
- package/dist/commands/workflow/index.js +12 -0
- package/dist/commands/workflow/instructions.d.ts +29 -0
- package/dist/commands/workflow/instructions.js +381 -0
- package/dist/commands/workflow/new-change.d.ts +11 -0
- package/dist/commands/workflow/new-change.js +44 -0
- package/dist/commands/workflow/schemas.d.ts +10 -0
- package/dist/commands/workflow/schemas.js +34 -0
- package/dist/commands/workflow/shared.d.ts +52 -0
- package/dist/commands/workflow/shared.js +111 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +58 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +68 -0
- package/dist/core/archive.d.ts +11 -0
- package/dist/core/archive.js +280 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +7 -0
- package/dist/core/artifact-graph/index.js +13 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
- package/dist/core/artifact-graph/instruction-loader.js +214 -0
- package/dist/core/artifact-graph/resolver.d.ts +81 -0
- package/dist/core/artifact-graph/resolver.js +257 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +54 -0
- package/dist/core/artifact-graph/types.d.ts +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
- package/dist/core/command-generation/adapters/amazon-q.js +26 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +26 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +27 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +50 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +28 -0
- package/dist/core/command-generation/adapters/codex.d.ts +13 -0
- package/dist/core/command-generation/adapters/codex.js +27 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +28 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +27 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +44 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +27 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +26 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +26 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +27 -0
- package/dist/core/command-generation/adapters/index.js +27 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +26 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
- package/dist/core/command-generation/adapters/qwen.js +26 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
- package/dist/core/command-generation/adapters/windsurf.js +51 -0
- package/dist/core/command-generation/generator.d.ts +21 -0
- package/dist/core/command-generation/generator.js +27 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +88 -0
- package/dist/core/command-generation/types.d.ts +55 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +456 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -0
- package/dist/core/completions/factory.d.ts +64 -0
- package/dist/core/completions/factory.js +75 -0
- package/dist/core/completions/generators/bash-generator.d.ts +32 -0
- package/dist/core/completions/generators/bash-generator.js +174 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +157 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
- package/dist/core/completions/generators/powershell-generator.js +207 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
- package/dist/core/completions/generators/zsh-generator.js +250 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +318 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
- package/dist/core/completions/installers/powershell-installer.js +327 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
- package/dist/core/completions/installers/zsh-installer.js +449 -0
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +24 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +39 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +25 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +36 -0
- package/dist/core/completions/types.d.ts +79 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +34 -0
- package/dist/core/config-schema.d.ts +76 -0
- package/dist/core/config-schema.js +200 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +30 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +39 -0
- package/dist/core/global-config.js +115 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +32 -0
- package/dist/core/init.js +433 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +501 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +193 -0
- package/dist/core/parsers/markdown-parser.d.ts +22 -0
- package/dist/core/parsers/markdown-parser.js +187 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/project-config.d.ts +64 -0
- package/dist/core/project-config.js +223 -0
- package/dist/core/schemas/base.schema.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/shared/index.d.ts +8 -0
- package/dist/core/shared/index.js +8 -0
- package/dist/core/shared/skill-generation.d.ts +41 -0
- package/dist/core/shared/skill-generation.js +74 -0
- package/dist/core/shared/tool-detection.d.ts +66 -0
- package/dist/core/shared/tool-detection.js +140 -0
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +384 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/index.d.ts +8 -0
- package/dist/core/templates/index.js +9 -0
- package/dist/core/templates/skill-templates.d.ts +112 -0
- package/dist/core/templates/skill-templates.js +2893 -0
- package/dist/core/update.d.ts +42 -0
- package/dist/core/update.js +306 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +33 -0
- package/dist/core/validation/validator.js +409 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +27 -0
- package/dist/prompts/searchable-multi-select.js +149 -0
- package/dist/telemetry/config.d.ts +32 -0
- package/dist/telemetry/config.js +68 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +145 -0
- package/dist/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +10 -0
- package/dist/ui/welcome-screen.js +146 -0
- package/dist/utils/change-metadata.d.ts +51 -0
- package/dist/utils/change-metadata.js +147 -0
- package/dist/utils/change-utils.d.ts +62 -0
- package/dist/utils/change-utils.js +121 -0
- package/dist/utils/file-system.d.ts +36 -0
- package/dist/utils/file-system.js +281 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +84 -0
- package/schemas/spec-driven/schema.yaml +148 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +23 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +9 -0
- package/schemas/tdd/schema.yaml +213 -0
- package/schemas/tdd/templates/docs.md +15 -0
- package/schemas/tdd/templates/implementation.md +11 -0
- package/schemas/tdd/templates/spec.md +11 -0
- package/schemas/tdd/templates/test.md +11 -0
- package/scripts/postinstall.js +147 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Command
|
|
3
|
+
*
|
|
4
|
+
* Refreshes OpenSpec skills and commands for configured tools.
|
|
5
|
+
* Supports smart update detection to skip updates when already current.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Options for the update command.
|
|
9
|
+
*/
|
|
10
|
+
export interface UpdateCommandOptions {
|
|
11
|
+
/** Force update even when tools are up to date */
|
|
12
|
+
force?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class UpdateCommand {
|
|
15
|
+
private readonly force;
|
|
16
|
+
constructor(options?: UpdateCommandOptions);
|
|
17
|
+
execute(projectPath: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Display message when all tools are up to date.
|
|
20
|
+
*/
|
|
21
|
+
private displayUpToDateMessage;
|
|
22
|
+
/**
|
|
23
|
+
* Display the update plan showing which tools need updating.
|
|
24
|
+
*/
|
|
25
|
+
private displayUpdatePlan;
|
|
26
|
+
/**
|
|
27
|
+
* Detect and handle legacy OpenSpec artifacts.
|
|
28
|
+
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
|
29
|
+
* Returns array of tool IDs that were newly configured during legacy upgrade.
|
|
30
|
+
*/
|
|
31
|
+
private handleLegacyCleanup;
|
|
32
|
+
/**
|
|
33
|
+
* Perform cleanup of legacy artifacts.
|
|
34
|
+
*/
|
|
35
|
+
private performLegacyCleanup;
|
|
36
|
+
/**
|
|
37
|
+
* Upgrade legacy tools to new skills system.
|
|
38
|
+
* Returns array of tool IDs that were newly configured.
|
|
39
|
+
*/
|
|
40
|
+
private upgradeLegacyTools;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Command
|
|
3
|
+
*
|
|
4
|
+
* Refreshes OpenSpec skills and commands for configured tools.
|
|
5
|
+
* Supports smart update detection to skip updates when already current.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { createRequire } from 'module';
|
|
11
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
12
|
+
import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
|
|
13
|
+
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
|
|
14
|
+
import { getConfiguredTools, getAllToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, } from './shared/index.js';
|
|
15
|
+
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, getToolsFromLegacyArtifacts, } from './legacy-cleanup.js';
|
|
16
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const { version: OPENSPEC_VERSION } = require('../../package.json');
|
|
19
|
+
export class UpdateCommand {
|
|
20
|
+
force;
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.force = options.force ?? false;
|
|
23
|
+
}
|
|
24
|
+
async execute(projectPath) {
|
|
25
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
26
|
+
const openspecPath = path.join(resolvedProjectPath, OPENSPEC_DIR_NAME);
|
|
27
|
+
// 1. Check openspec directory exists
|
|
28
|
+
if (!await FileSystemUtils.directoryExists(openspecPath)) {
|
|
29
|
+
throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`);
|
|
30
|
+
}
|
|
31
|
+
// 2. Detect and handle legacy artifacts + upgrade legacy tools to new skills
|
|
32
|
+
const newlyConfiguredTools = await this.handleLegacyCleanup(resolvedProjectPath);
|
|
33
|
+
// 3. Find configured tools
|
|
34
|
+
const configuredTools = getConfiguredTools(resolvedProjectPath);
|
|
35
|
+
if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) {
|
|
36
|
+
console.log(chalk.yellow('No configured tools found.'));
|
|
37
|
+
console.log(chalk.dim('Run "openspec init" to set up tools.'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// 4. Check version status for all configured tools
|
|
41
|
+
const toolStatuses = getAllToolVersionStatus(resolvedProjectPath, OPENSPEC_VERSION);
|
|
42
|
+
// 5. Smart update detection
|
|
43
|
+
const toolsNeedingUpdate = toolStatuses.filter((s) => s.needsUpdate);
|
|
44
|
+
const toolsUpToDate = toolStatuses.filter((s) => !s.needsUpdate);
|
|
45
|
+
if (!this.force && toolsNeedingUpdate.length === 0) {
|
|
46
|
+
// All tools are up to date
|
|
47
|
+
this.displayUpToDateMessage(toolStatuses);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// 6. Display update plan
|
|
51
|
+
if (this.force) {
|
|
52
|
+
console.log(`Force updating ${configuredTools.length} tool(s): ${configuredTools.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.displayUpdatePlan(toolsNeedingUpdate, toolsUpToDate);
|
|
56
|
+
}
|
|
57
|
+
console.log();
|
|
58
|
+
// 7. Prepare templates
|
|
59
|
+
const skillTemplates = getSkillTemplates();
|
|
60
|
+
const commandContents = getCommandContents();
|
|
61
|
+
// 8. Update tools (all if force, otherwise only those needing update)
|
|
62
|
+
const toolsToUpdate = this.force ? configuredTools : toolsNeedingUpdate.map((s) => s.toolId);
|
|
63
|
+
const updatedTools = [];
|
|
64
|
+
const failedTools = [];
|
|
65
|
+
for (const toolId of toolsToUpdate) {
|
|
66
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
67
|
+
if (!tool?.skillsDir)
|
|
68
|
+
continue;
|
|
69
|
+
const spinner = ora(`Updating ${tool.name}...`).start();
|
|
70
|
+
try {
|
|
71
|
+
const skillsDir = path.join(resolvedProjectPath, tool.skillsDir, 'skills');
|
|
72
|
+
// Update skill files
|
|
73
|
+
for (const { template, dirName } of skillTemplates) {
|
|
74
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
75
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
76
|
+
const skillContent = generateSkillContent(template, OPENSPEC_VERSION);
|
|
77
|
+
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
78
|
+
}
|
|
79
|
+
// Update commands
|
|
80
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
81
|
+
if (adapter) {
|
|
82
|
+
const generatedCommands = generateCommands(commandContents, adapter);
|
|
83
|
+
for (const cmd of generatedCommands) {
|
|
84
|
+
const commandFile = path.join(resolvedProjectPath, cmd.path);
|
|
85
|
+
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
spinner.succeed(`Updated ${tool.name}`);
|
|
89
|
+
updatedTools.push(tool.name);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
spinner.fail(`Failed to update ${tool.name}`);
|
|
93
|
+
failedTools.push({
|
|
94
|
+
name: tool.name,
|
|
95
|
+
error: error instanceof Error ? error.message : String(error)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 9. Summary
|
|
100
|
+
console.log();
|
|
101
|
+
if (updatedTools.length > 0) {
|
|
102
|
+
console.log(chalk.green(`✓ Updated: ${updatedTools.join(', ')} (v${OPENSPEC_VERSION})`));
|
|
103
|
+
}
|
|
104
|
+
if (failedTools.length > 0) {
|
|
105
|
+
console.log(chalk.red(`✗ Failed: ${failedTools.map(f => `${f.name} (${f.error})`).join(', ')}`));
|
|
106
|
+
}
|
|
107
|
+
// 10. Show onboarding message for newly configured tools from legacy upgrade
|
|
108
|
+
if (newlyConfiguredTools.length > 0) {
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(chalk.bold('Getting started:'));
|
|
111
|
+
console.log(' /opsx:new Start a new change');
|
|
112
|
+
console.log(' /opsx:continue Create the next artifact');
|
|
113
|
+
console.log(' /opsx:apply Implement tasks');
|
|
114
|
+
console.log();
|
|
115
|
+
console.log(`Learn more: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
|
|
116
|
+
}
|
|
117
|
+
console.log();
|
|
118
|
+
console.log(chalk.dim('Restart your IDE for changes to take effect.'));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Display message when all tools are up to date.
|
|
122
|
+
*/
|
|
123
|
+
displayUpToDateMessage(toolStatuses) {
|
|
124
|
+
const toolNames = toolStatuses.map((s) => s.toolId);
|
|
125
|
+
console.log(chalk.green(`✓ All ${toolStatuses.length} tool(s) up to date (v${OPENSPEC_VERSION})`));
|
|
126
|
+
console.log(chalk.dim(` Tools: ${toolNames.join(', ')}`));
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(chalk.dim('Use --force to refresh skills anyway.'));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Display the update plan showing which tools need updating.
|
|
132
|
+
*/
|
|
133
|
+
displayUpdatePlan(needingUpdate, upToDate) {
|
|
134
|
+
const updates = needingUpdate.map((s) => {
|
|
135
|
+
const fromVersion = s.generatedByVersion ?? 'unknown';
|
|
136
|
+
return `${s.toolId} (${fromVersion} → ${OPENSPEC_VERSION})`;
|
|
137
|
+
});
|
|
138
|
+
console.log(`Updating ${needingUpdate.length} tool(s): ${updates.join(', ')}`);
|
|
139
|
+
if (upToDate.length > 0) {
|
|
140
|
+
const upToDateNames = upToDate.map((s) => s.toolId);
|
|
141
|
+
console.log(chalk.dim(`Already up to date: ${upToDateNames.join(', ')}`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Detect and handle legacy OpenSpec artifacts.
|
|
146
|
+
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
|
147
|
+
* Returns array of tool IDs that were newly configured during legacy upgrade.
|
|
148
|
+
*/
|
|
149
|
+
async handleLegacyCleanup(projectPath) {
|
|
150
|
+
// Detect legacy artifacts
|
|
151
|
+
const detection = await detectLegacyArtifacts(projectPath);
|
|
152
|
+
if (!detection.hasLegacyArtifacts) {
|
|
153
|
+
return []; // No legacy artifacts found
|
|
154
|
+
}
|
|
155
|
+
// Show what was detected
|
|
156
|
+
console.log();
|
|
157
|
+
console.log(formatDetectionSummary(detection));
|
|
158
|
+
console.log();
|
|
159
|
+
const canPrompt = isInteractive();
|
|
160
|
+
if (this.force) {
|
|
161
|
+
// --force flag: proceed with cleanup automatically
|
|
162
|
+
await this.performLegacyCleanup(projectPath, detection);
|
|
163
|
+
// Then upgrade legacy tools to new skills
|
|
164
|
+
return this.upgradeLegacyTools(projectPath, detection, canPrompt);
|
|
165
|
+
}
|
|
166
|
+
if (!canPrompt) {
|
|
167
|
+
// Non-interactive mode without --force: warn and continue
|
|
168
|
+
// (Unlike init, update doesn't abort - user may just want to update skills)
|
|
169
|
+
console.log(chalk.yellow('⚠ Run with --force to auto-cleanup legacy files, or run interactively.'));
|
|
170
|
+
console.log();
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
// Interactive mode: prompt for confirmation
|
|
174
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
175
|
+
const shouldCleanup = await confirm({
|
|
176
|
+
message: 'Upgrade and clean up legacy files?',
|
|
177
|
+
default: true,
|
|
178
|
+
});
|
|
179
|
+
if (shouldCleanup) {
|
|
180
|
+
await this.performLegacyCleanup(projectPath, detection);
|
|
181
|
+
// Then upgrade legacy tools to new skills
|
|
182
|
+
return this.upgradeLegacyTools(projectPath, detection, canPrompt);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(chalk.dim('Skipping legacy cleanup. Continuing with skill update...'));
|
|
186
|
+
console.log();
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Perform cleanup of legacy artifacts.
|
|
192
|
+
*/
|
|
193
|
+
async performLegacyCleanup(projectPath, detection) {
|
|
194
|
+
const spinner = ora('Cleaning up legacy files...').start();
|
|
195
|
+
const result = await cleanupLegacyArtifacts(projectPath, detection);
|
|
196
|
+
spinner.succeed('Legacy files cleaned up');
|
|
197
|
+
const summary = formatCleanupSummary(result);
|
|
198
|
+
if (summary) {
|
|
199
|
+
console.log();
|
|
200
|
+
console.log(summary);
|
|
201
|
+
}
|
|
202
|
+
console.log();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Upgrade legacy tools to new skills system.
|
|
206
|
+
* Returns array of tool IDs that were newly configured.
|
|
207
|
+
*/
|
|
208
|
+
async upgradeLegacyTools(projectPath, detection, canPrompt) {
|
|
209
|
+
// Get tools that had legacy artifacts
|
|
210
|
+
const legacyTools = getToolsFromLegacyArtifacts(detection);
|
|
211
|
+
if (legacyTools.length === 0) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
// Get currently configured tools
|
|
215
|
+
const configuredTools = getConfiguredTools(projectPath);
|
|
216
|
+
const configuredSet = new Set(configuredTools);
|
|
217
|
+
// Filter to tools that aren't already configured
|
|
218
|
+
const unconfiguredLegacyTools = legacyTools.filter((t) => !configuredSet.has(t));
|
|
219
|
+
if (unconfiguredLegacyTools.length === 0) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
// Get valid tools (those with skillsDir)
|
|
223
|
+
const validToolIds = new Set(getToolsWithSkillsDir());
|
|
224
|
+
const validUnconfiguredTools = unconfiguredLegacyTools.filter((t) => validToolIds.has(t));
|
|
225
|
+
if (validUnconfiguredTools.length === 0) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
// Show what tools were detected from legacy artifacts
|
|
229
|
+
console.log(chalk.bold('Tools detected from legacy artifacts:'));
|
|
230
|
+
for (const toolId of validUnconfiguredTools) {
|
|
231
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
232
|
+
console.log(` • ${tool?.name || toolId}`);
|
|
233
|
+
}
|
|
234
|
+
console.log();
|
|
235
|
+
let selectedTools;
|
|
236
|
+
if (this.force || !canPrompt) {
|
|
237
|
+
// Non-interactive with --force: auto-select detected tools
|
|
238
|
+
selectedTools = validUnconfiguredTools;
|
|
239
|
+
console.log(`Setting up skills for: ${selectedTools.join(', ')}`);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Interactive mode: prompt for tool selection with detected tools pre-selected
|
|
243
|
+
const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
|
|
244
|
+
const sortedChoices = validUnconfiguredTools.map((toolId) => {
|
|
245
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
246
|
+
return {
|
|
247
|
+
name: tool?.name || toolId,
|
|
248
|
+
value: toolId,
|
|
249
|
+
configured: false,
|
|
250
|
+
preSelected: true, // Pre-select all detected legacy tools
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
selectedTools = await searchableMultiSelect({
|
|
254
|
+
message: 'Select tools to set up with the new skill system:',
|
|
255
|
+
pageSize: 15,
|
|
256
|
+
choices: sortedChoices,
|
|
257
|
+
validate: (_selected) => true, // Allow empty selection (user can skip)
|
|
258
|
+
});
|
|
259
|
+
if (selectedTools.length === 0) {
|
|
260
|
+
console.log(chalk.dim('Skipping tool setup.'));
|
|
261
|
+
console.log();
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Create skills for selected tools
|
|
266
|
+
const newlyConfigured = [];
|
|
267
|
+
const skillTemplates = getSkillTemplates();
|
|
268
|
+
const commandContents = getCommandContents();
|
|
269
|
+
for (const toolId of selectedTools) {
|
|
270
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
271
|
+
if (!tool?.skillsDir)
|
|
272
|
+
continue;
|
|
273
|
+
const spinner = ora(`Setting up ${tool.name}...`).start();
|
|
274
|
+
try {
|
|
275
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
276
|
+
// Create skill files
|
|
277
|
+
for (const { template, dirName } of skillTemplates) {
|
|
278
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
279
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
280
|
+
const skillContent = generateSkillContent(template, OPENSPEC_VERSION);
|
|
281
|
+
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
282
|
+
}
|
|
283
|
+
// Create commands
|
|
284
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
285
|
+
if (adapter) {
|
|
286
|
+
const generatedCommands = generateCommands(commandContents, adapter);
|
|
287
|
+
for (const cmd of generatedCommands) {
|
|
288
|
+
const commandFile = path.join(projectPath, cmd.path);
|
|
289
|
+
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
spinner.succeed(`Setup complete for ${tool.name}`);
|
|
293
|
+
newlyConfigured.push(toolId);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
spinner.fail(`Failed to set up ${tool.name}`);
|
|
297
|
+
console.log(chalk.red(` ${error instanceof Error ? error.message : String(error)}`));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (newlyConfigured.length > 0) {
|
|
301
|
+
console.log();
|
|
302
|
+
}
|
|
303
|
+
return newlyConfigured;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation threshold constants
|
|
3
|
+
*/
|
|
4
|
+
export declare const MIN_WHY_SECTION_LENGTH = 50;
|
|
5
|
+
export declare const MIN_PURPOSE_LENGTH = 50;
|
|
6
|
+
export declare const MAX_WHY_SECTION_LENGTH = 1000;
|
|
7
|
+
export declare const MAX_REQUIREMENT_TEXT_LENGTH = 500;
|
|
8
|
+
export declare const MAX_DELTAS_PER_CHANGE = 10;
|
|
9
|
+
export declare const VALIDATION_MESSAGES: {
|
|
10
|
+
readonly SCENARIO_EMPTY: "Scenario text cannot be empty";
|
|
11
|
+
readonly REQUIREMENT_EMPTY: "Requirement text cannot be empty";
|
|
12
|
+
readonly REQUIREMENT_NO_SHALL: "Requirement must contain SHALL or MUST keyword";
|
|
13
|
+
readonly REQUIREMENT_NO_SCENARIOS: "Requirement must have at least one scenario";
|
|
14
|
+
readonly SPEC_NAME_EMPTY: "Spec name cannot be empty";
|
|
15
|
+
readonly SPEC_PURPOSE_EMPTY: "Purpose section cannot be empty";
|
|
16
|
+
readonly SPEC_NO_REQUIREMENTS: "Spec must have at least one requirement";
|
|
17
|
+
readonly CHANGE_NAME_EMPTY: "Change name cannot be empty";
|
|
18
|
+
readonly CHANGE_WHY_TOO_SHORT: "Why section must be at least 50 characters";
|
|
19
|
+
readonly CHANGE_WHY_TOO_LONG: "Why section should not exceed 1000 characters";
|
|
20
|
+
readonly CHANGE_WHAT_EMPTY: "What Changes section cannot be empty";
|
|
21
|
+
readonly CHANGE_NO_DELTAS: "Change must have at least one delta";
|
|
22
|
+
readonly CHANGE_TOO_MANY_DELTAS: "Consider splitting changes with more than 10 deltas";
|
|
23
|
+
readonly DELTA_SPEC_EMPTY: "Spec name cannot be empty";
|
|
24
|
+
readonly DELTA_DESCRIPTION_EMPTY: "Delta description cannot be empty";
|
|
25
|
+
readonly PURPOSE_TOO_BRIEF: "Purpose section is too brief (less than 50 characters)";
|
|
26
|
+
readonly REQUIREMENT_TOO_LONG: "Requirement text is very long (>500 characters). Consider breaking it down.";
|
|
27
|
+
readonly DELTA_DESCRIPTION_TOO_BRIEF: "Delta description is too brief";
|
|
28
|
+
readonly DELTA_MISSING_REQUIREMENTS: "Delta should include requirements";
|
|
29
|
+
readonly GUIDE_NO_DELTAS: "No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one \"#### Scenario:\" block. Tip: run \"openspec change show <change-id> --json --deltas-only\" to inspect parsed deltas.";
|
|
30
|
+
readonly GUIDE_MISSING_SPEC_SECTIONS: "Missing required sections. Expected headers: \"## Purpose\" and \"## Requirements\". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...";
|
|
31
|
+
readonly GUIDE_MISSING_CHANGE_SECTIONS: "Missing required sections. Expected headers: \"## Why\" and \"## What Changes\". Ensure deltas are documented in specs/ using delta headers.";
|
|
32
|
+
readonly GUIDE_SCENARIO_FORMAT: "Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...";
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation threshold constants
|
|
3
|
+
*/
|
|
4
|
+
// Minimum character lengths
|
|
5
|
+
export const MIN_WHY_SECTION_LENGTH = 50;
|
|
6
|
+
export const MIN_PURPOSE_LENGTH = 50;
|
|
7
|
+
// Maximum character/item limits
|
|
8
|
+
export const MAX_WHY_SECTION_LENGTH = 1000;
|
|
9
|
+
export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
|
|
10
|
+
export const MAX_DELTAS_PER_CHANGE = 10;
|
|
11
|
+
// Validation messages
|
|
12
|
+
export const VALIDATION_MESSAGES = {
|
|
13
|
+
// Required content
|
|
14
|
+
SCENARIO_EMPTY: 'Scenario text cannot be empty',
|
|
15
|
+
REQUIREMENT_EMPTY: 'Requirement text cannot be empty',
|
|
16
|
+
REQUIREMENT_NO_SHALL: 'Requirement must contain SHALL or MUST keyword',
|
|
17
|
+
REQUIREMENT_NO_SCENARIOS: 'Requirement must have at least one scenario',
|
|
18
|
+
SPEC_NAME_EMPTY: 'Spec name cannot be empty',
|
|
19
|
+
SPEC_PURPOSE_EMPTY: 'Purpose section cannot be empty',
|
|
20
|
+
SPEC_NO_REQUIREMENTS: 'Spec must have at least one requirement',
|
|
21
|
+
CHANGE_NAME_EMPTY: 'Change name cannot be empty',
|
|
22
|
+
CHANGE_WHY_TOO_SHORT: `Why section must be at least ${MIN_WHY_SECTION_LENGTH} characters`,
|
|
23
|
+
CHANGE_WHY_TOO_LONG: `Why section should not exceed ${MAX_WHY_SECTION_LENGTH} characters`,
|
|
24
|
+
CHANGE_WHAT_EMPTY: 'What Changes section cannot be empty',
|
|
25
|
+
CHANGE_NO_DELTAS: 'Change must have at least one delta',
|
|
26
|
+
CHANGE_TOO_MANY_DELTAS: `Consider splitting changes with more than ${MAX_DELTAS_PER_CHANGE} deltas`,
|
|
27
|
+
DELTA_SPEC_EMPTY: 'Spec name cannot be empty',
|
|
28
|
+
DELTA_DESCRIPTION_EMPTY: 'Delta description cannot be empty',
|
|
29
|
+
// Warnings
|
|
30
|
+
PURPOSE_TOO_BRIEF: `Purpose section is too brief (less than ${MIN_PURPOSE_LENGTH} characters)`,
|
|
31
|
+
REQUIREMENT_TOO_LONG: `Requirement text is very long (>${MAX_REQUIREMENT_TEXT_LENGTH} characters). Consider breaking it down.`,
|
|
32
|
+
DELTA_DESCRIPTION_TOO_BRIEF: 'Delta description is too brief',
|
|
33
|
+
DELTA_MISSING_REQUIREMENTS: 'Delta should include requirements',
|
|
34
|
+
// Guidance snippets (appended to primary messages for remediation)
|
|
35
|
+
GUIDE_NO_DELTAS: 'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "openspec change show <change-id> --json --deltas-only" to inspect parsed deltas.',
|
|
36
|
+
GUIDE_MISSING_SPEC_SECTIONS: 'Missing required sections. Expected headers: "## Purpose" and "## Requirements". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...',
|
|
37
|
+
GUIDE_MISSING_CHANGE_SECTIONS: 'Missing required sections. Expected headers: "## Why" and "## What Changes". Ensure deltas are documented in specs/ using delta headers.',
|
|
38
|
+
GUIDE_SCENARIO_FORMAT: 'Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ValidationLevel = 'ERROR' | 'WARNING' | 'INFO';
|
|
2
|
+
export interface ValidationIssue {
|
|
3
|
+
level: ValidationLevel;
|
|
4
|
+
path: string;
|
|
5
|
+
message: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
column?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ValidationReport {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
issues: ValidationIssue[];
|
|
12
|
+
summary: {
|
|
13
|
+
errors: number;
|
|
14
|
+
warnings: number;
|
|
15
|
+
info: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ValidationReport } from './types.js';
|
|
2
|
+
export declare class Validator {
|
|
3
|
+
private strictMode;
|
|
4
|
+
constructor(strictMode?: boolean);
|
|
5
|
+
validateSpec(filePath: string): Promise<ValidationReport>;
|
|
6
|
+
/**
|
|
7
|
+
* Validate spec content from a string (used for pre-write validation of rebuilt specs)
|
|
8
|
+
*/
|
|
9
|
+
validateSpecContent(specName: string, content: string): Promise<ValidationReport>;
|
|
10
|
+
validateChange(filePath: string): Promise<ValidationReport>;
|
|
11
|
+
/**
|
|
12
|
+
* Validate delta-formatted spec files under a change directory.
|
|
13
|
+
* Enforces:
|
|
14
|
+
* - At least one delta across all files
|
|
15
|
+
* - ADDED/MODIFIED: each requirement has SHALL/MUST and at least one scenario
|
|
16
|
+
* - REMOVED: names only; no scenario/description required
|
|
17
|
+
* - RENAMED: pairs well-formed
|
|
18
|
+
* - No duplicates within sections; no cross-section conflicts per spec
|
|
19
|
+
*/
|
|
20
|
+
validateChangeDeltaSpecs(changeDir: string): Promise<ValidationReport>;
|
|
21
|
+
private convertZodErrors;
|
|
22
|
+
private applySpecRules;
|
|
23
|
+
private applyChangeRules;
|
|
24
|
+
private enrichTopLevelError;
|
|
25
|
+
private extractNameFromPath;
|
|
26
|
+
private createReport;
|
|
27
|
+
isValid(report: ValidationReport): boolean;
|
|
28
|
+
private extractRequirementText;
|
|
29
|
+
private containsShallOrMust;
|
|
30
|
+
private countScenarios;
|
|
31
|
+
private formatSectionList;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=validator.d.ts.map
|