@fission-ai/openspec 1.1.0 → 1.2.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/dist/cli/index.js +2 -0
- package/dist/commands/config.d.ts +28 -0
- package/dist/commands/config.js +359 -5
- package/dist/core/available-tools.d.ts +16 -0
- package/dist/core/available-tools.js +30 -0
- package/dist/core/command-generation/adapters/index.d.ts +2 -0
- package/dist/core/command-generation/adapters/index.js +2 -0
- package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
- package/dist/core/command-generation/adapters/kiro.js +26 -0
- package/dist/core/command-generation/adapters/opencode.js +4 -1
- package/dist/core/command-generation/adapters/pi.d.ts +14 -0
- package/dist/core/command-generation/adapters/pi.js +41 -0
- package/dist/core/command-generation/registry.js +4 -0
- package/dist/core/completions/command-registry.js +5 -0
- package/dist/core/config-schema.d.ts +10 -0
- package/dist/core/config-schema.js +14 -1
- package/dist/core/config.js +2 -0
- package/dist/core/global-config.d.ts +5 -0
- package/dist/core/global-config.js +12 -2
- package/dist/core/init.d.ts +5 -0
- package/dist/core/init.js +206 -42
- package/dist/core/legacy-cleanup.js +1 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +108 -0
- package/dist/core/profile-sync-drift.d.ts +38 -0
- package/dist/core/profile-sync-drift.js +200 -0
- package/dist/core/profiles.d.ts +26 -0
- package/dist/core/profiles.js +40 -0
- package/dist/core/shared/index.d.ts +1 -1
- package/dist/core/shared/index.js +1 -1
- package/dist/core/shared/skill-generation.d.ts +16 -8
- package/dist/core/shared/skill-generation.js +42 -22
- package/dist/core/shared/tool-detection.d.ts +8 -3
- package/dist/core/shared/tool-detection.js +18 -0
- package/dist/core/templates/index.d.ts +1 -1
- package/dist/core/templates/index.js +2 -2
- package/dist/core/templates/skill-templates.d.ts +15 -118
- package/dist/core/templates/skill-templates.js +14 -3424
- package/dist/core/templates/types.d.ts +19 -0
- package/dist/core/templates/types.js +5 -0
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +308 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +271 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +488 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +232 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +461 -0
- package/dist/core/templates/workflows/feedback.d.ts +9 -0
- package/dist/core/templates/workflows/feedback.js +108 -0
- package/dist/core/templates/workflows/ff-change.d.ts +10 -0
- package/dist/core/templates/workflows/ff-change.js +198 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +143 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +565 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +216 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +272 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +332 -0
- package/dist/core/update.d.ts +36 -1
- package/dist/core/update.js +292 -61
- package/dist/prompts/searchable-multi-select.d.ts +3 -2
- package/dist/prompts/searchable-multi-select.js +22 -12
- package/dist/utils/command-references.d.ts +18 -0
- package/dist/utils/command-references.js +20 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration Utilities
|
|
3
|
+
*
|
|
4
|
+
* One-time migration logic for existing projects when profile system is introduced.
|
|
5
|
+
* Called by both init and update commands before profile resolution.
|
|
6
|
+
*/
|
|
7
|
+
import { getGlobalConfig, getGlobalConfigPath, saveGlobalConfig } from './global-config.js';
|
|
8
|
+
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
9
|
+
import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js';
|
|
10
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
function scanInstalledWorkflowArtifacts(projectPath, tools) {
|
|
14
|
+
const installed = new Set();
|
|
15
|
+
let hasSkills = false;
|
|
16
|
+
let hasCommands = false;
|
|
17
|
+
for (const tool of tools) {
|
|
18
|
+
if (!tool.skillsDir)
|
|
19
|
+
continue;
|
|
20
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
21
|
+
for (const workflowId of ALL_WORKFLOWS) {
|
|
22
|
+
const skillDirName = WORKFLOW_TO_SKILL_DIR[workflowId];
|
|
23
|
+
const skillFile = path.join(skillsDir, skillDirName, 'SKILL.md');
|
|
24
|
+
if (fs.existsSync(skillFile)) {
|
|
25
|
+
installed.add(workflowId);
|
|
26
|
+
hasSkills = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
30
|
+
if (!adapter)
|
|
31
|
+
continue;
|
|
32
|
+
for (const workflowId of ALL_WORKFLOWS) {
|
|
33
|
+
const commandPath = adapter.getFilePath(workflowId);
|
|
34
|
+
const fullPath = path.isAbsolute(commandPath)
|
|
35
|
+
? commandPath
|
|
36
|
+
: path.join(projectPath, commandPath);
|
|
37
|
+
if (fs.existsSync(fullPath)) {
|
|
38
|
+
installed.add(workflowId);
|
|
39
|
+
hasCommands = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
workflows: ALL_WORKFLOWS.filter((workflowId) => installed.has(workflowId)),
|
|
45
|
+
hasSkills,
|
|
46
|
+
hasCommands,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Scans installed workflow files across all detected tools and returns
|
|
51
|
+
* the union of installed workflow IDs.
|
|
52
|
+
*/
|
|
53
|
+
export function scanInstalledWorkflows(projectPath, tools) {
|
|
54
|
+
return scanInstalledWorkflowArtifacts(projectPath, tools).workflows;
|
|
55
|
+
}
|
|
56
|
+
function inferDelivery(artifacts) {
|
|
57
|
+
if (artifacts.hasSkills && artifacts.hasCommands) {
|
|
58
|
+
return 'both';
|
|
59
|
+
}
|
|
60
|
+
if (artifacts.hasCommands) {
|
|
61
|
+
return 'commands';
|
|
62
|
+
}
|
|
63
|
+
return 'skills';
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Performs one-time migration if the global config does not yet have a profile field.
|
|
67
|
+
* Called by both init and update before profile resolution.
|
|
68
|
+
*
|
|
69
|
+
* - If no profile field exists and workflows are installed: sets profile to 'custom'
|
|
70
|
+
* with the detected workflows, preserving the user's existing setup.
|
|
71
|
+
* - If no profile field exists and no workflows are installed: no-op (defaults apply).
|
|
72
|
+
* - If profile field already exists: no-op.
|
|
73
|
+
*/
|
|
74
|
+
export function migrateIfNeeded(projectPath, tools) {
|
|
75
|
+
const config = getGlobalConfig();
|
|
76
|
+
// Check raw config file for profile field presence
|
|
77
|
+
const configPath = getGlobalConfigPath();
|
|
78
|
+
let rawConfig = {};
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(configPath)) {
|
|
81
|
+
rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return; // Can't read config, skip migration
|
|
86
|
+
}
|
|
87
|
+
// If profile is already explicitly set, no migration needed
|
|
88
|
+
if (rawConfig.profile !== undefined) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Scan for installed workflows
|
|
92
|
+
const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools);
|
|
93
|
+
const installedWorkflows = artifacts.workflows;
|
|
94
|
+
if (installedWorkflows.length === 0) {
|
|
95
|
+
// No workflows installed, new user — defaults will apply
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Migrate: set profile to custom with detected workflows
|
|
99
|
+
config.profile = 'custom';
|
|
100
|
+
config.workflows = installedWorkflows;
|
|
101
|
+
if (rawConfig.delivery === undefined) {
|
|
102
|
+
config.delivery = inferDelivery(artifacts);
|
|
103
|
+
}
|
|
104
|
+
saveGlobalConfig(config);
|
|
105
|
+
console.log(`Migrated: custom profile with ${installedWorkflows.length} workflows`);
|
|
106
|
+
console.log("New in this version: /opsx:propose. Try 'openspec config profile core' for the streamlined experience.");
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Delivery } from './global-config.js';
|
|
2
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
3
|
+
type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
4
|
+
/**
|
|
5
|
+
* Maps workflow IDs to their skill directory names.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WORKFLOW_TO_SKILL_DIR: Record<WorkflowId, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether a tool has at least one generated OpenSpec command file.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toolHasAnyConfiguredCommand(projectPath: string, toolId: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Returns tools with at least one generated command file on disk.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCommandConfiguredTools(projectPath: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Returns tools that are configured via either skills or commands.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getConfiguredToolsForProfileSync(projectPath: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Detects if a single tool has profile/delivery drift against the desired state.
|
|
22
|
+
*
|
|
23
|
+
* This function covers:
|
|
24
|
+
* - required artifacts missing for selected workflows
|
|
25
|
+
* - artifacts that should not exist for the selected delivery mode
|
|
26
|
+
* - artifacts for workflows that were deselected from the current profile
|
|
27
|
+
*/
|
|
28
|
+
export declare function hasToolProfileOrDeliveryDrift(projectPath: string, toolId: string, desiredWorkflows: readonly string[], delivery: Delivery): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Returns configured tools that currently need a profile/delivery sync.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getToolsNeedingProfileSync(projectPath: string, desiredWorkflows: readonly string[], delivery: Delivery, configuredTools?: readonly string[]): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Detects whether the current project has any profile/delivery drift.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hasProjectConfigDrift(projectPath: string, desiredWorkflows: readonly string[], delivery: Delivery): boolean;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=profile-sync-drift.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { AI_TOOLS } from './config.js';
|
|
4
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
5
|
+
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
6
|
+
import { COMMAND_IDS, getConfiguredTools } from './shared/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Maps workflow IDs to their skill directory names.
|
|
9
|
+
*/
|
|
10
|
+
export const WORKFLOW_TO_SKILL_DIR = {
|
|
11
|
+
'explore': 'openspec-explore',
|
|
12
|
+
'new': 'openspec-new-change',
|
|
13
|
+
'continue': 'openspec-continue-change',
|
|
14
|
+
'apply': 'openspec-apply-change',
|
|
15
|
+
'ff': 'openspec-ff-change',
|
|
16
|
+
'sync': 'openspec-sync-specs',
|
|
17
|
+
'archive': 'openspec-archive-change',
|
|
18
|
+
'bulk-archive': 'openspec-bulk-archive-change',
|
|
19
|
+
'verify': 'openspec-verify-change',
|
|
20
|
+
'onboard': 'openspec-onboard',
|
|
21
|
+
'propose': 'openspec-propose',
|
|
22
|
+
};
|
|
23
|
+
function toKnownWorkflows(workflows) {
|
|
24
|
+
return workflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks whether a tool has at least one generated OpenSpec command file.
|
|
28
|
+
*/
|
|
29
|
+
export function toolHasAnyConfiguredCommand(projectPath, toolId) {
|
|
30
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
31
|
+
if (!adapter)
|
|
32
|
+
return false;
|
|
33
|
+
for (const commandId of COMMAND_IDS) {
|
|
34
|
+
const cmdPath = adapter.getFilePath(commandId);
|
|
35
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
36
|
+
if (fs.existsSync(fullPath)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns tools with at least one generated command file on disk.
|
|
44
|
+
*/
|
|
45
|
+
export function getCommandConfiguredTools(projectPath) {
|
|
46
|
+
return AI_TOOLS
|
|
47
|
+
.filter((tool) => {
|
|
48
|
+
if (!tool.skillsDir)
|
|
49
|
+
return false;
|
|
50
|
+
const toolDir = path.join(projectPath, tool.skillsDir);
|
|
51
|
+
try {
|
|
52
|
+
return fs.statSync(toolDir).isDirectory();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
.map((tool) => tool.value)
|
|
59
|
+
.filter((toolId) => toolHasAnyConfiguredCommand(projectPath, toolId));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns tools that are configured via either skills or commands.
|
|
63
|
+
*/
|
|
64
|
+
export function getConfiguredToolsForProfileSync(projectPath) {
|
|
65
|
+
const skillConfigured = getConfiguredTools(projectPath);
|
|
66
|
+
const commandConfigured = getCommandConfiguredTools(projectPath);
|
|
67
|
+
return [...new Set([...skillConfigured, ...commandConfigured])];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Detects if a single tool has profile/delivery drift against the desired state.
|
|
71
|
+
*
|
|
72
|
+
* This function covers:
|
|
73
|
+
* - required artifacts missing for selected workflows
|
|
74
|
+
* - artifacts that should not exist for the selected delivery mode
|
|
75
|
+
* - artifacts for workflows that were deselected from the current profile
|
|
76
|
+
*/
|
|
77
|
+
export function hasToolProfileOrDeliveryDrift(projectPath, toolId, desiredWorkflows, delivery) {
|
|
78
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
79
|
+
if (!tool?.skillsDir)
|
|
80
|
+
return false;
|
|
81
|
+
const knownDesiredWorkflows = toKnownWorkflows(desiredWorkflows);
|
|
82
|
+
const desiredWorkflowSet = new Set(knownDesiredWorkflows);
|
|
83
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
84
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
85
|
+
const shouldGenerateSkills = delivery !== 'commands';
|
|
86
|
+
const shouldGenerateCommands = delivery !== 'skills';
|
|
87
|
+
if (shouldGenerateSkills) {
|
|
88
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
89
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
90
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
91
|
+
if (!fs.existsSync(skillFile)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Deselecting workflows in a profile should trigger sync.
|
|
96
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
97
|
+
if (desiredWorkflowSet.has(workflow))
|
|
98
|
+
continue;
|
|
99
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
100
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
101
|
+
if (fs.existsSync(skillDir)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
108
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
109
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
110
|
+
if (fs.existsSync(skillDir)) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (shouldGenerateCommands && adapter) {
|
|
116
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
117
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
118
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
119
|
+
if (!fs.existsSync(fullPath)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Deselecting workflows in a profile should trigger sync.
|
|
124
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
125
|
+
if (desiredWorkflowSet.has(workflow))
|
|
126
|
+
continue;
|
|
127
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
128
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
129
|
+
if (fs.existsSync(fullPath)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (!shouldGenerateCommands && adapter) {
|
|
135
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
136
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
137
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
138
|
+
if (fs.existsSync(fullPath)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns configured tools that currently need a profile/delivery sync.
|
|
147
|
+
*/
|
|
148
|
+
export function getToolsNeedingProfileSync(projectPath, desiredWorkflows, delivery, configuredTools) {
|
|
149
|
+
const tools = configuredTools ? [...new Set(configuredTools)] : getConfiguredToolsForProfileSync(projectPath);
|
|
150
|
+
return tools.filter((toolId) => hasToolProfileOrDeliveryDrift(projectPath, toolId, desiredWorkflows, delivery));
|
|
151
|
+
}
|
|
152
|
+
function getInstalledWorkflowsForTool(projectPath, toolId, options) {
|
|
153
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
154
|
+
if (!tool?.skillsDir)
|
|
155
|
+
return [];
|
|
156
|
+
const installed = new Set();
|
|
157
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
158
|
+
if (options.includeSkills) {
|
|
159
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
160
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
161
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
162
|
+
if (fs.existsSync(skillFile)) {
|
|
163
|
+
installed.add(workflow);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (options.includeCommands) {
|
|
168
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
169
|
+
if (adapter) {
|
|
170
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
171
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
172
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
173
|
+
if (fs.existsSync(fullPath)) {
|
|
174
|
+
installed.add(workflow);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return [...installed];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Detects whether the current project has any profile/delivery drift.
|
|
183
|
+
*/
|
|
184
|
+
export function hasProjectConfigDrift(projectPath, desiredWorkflows, delivery) {
|
|
185
|
+
const configuredTools = getConfiguredToolsForProfileSync(projectPath);
|
|
186
|
+
if (getToolsNeedingProfileSync(projectPath, desiredWorkflows, delivery, configuredTools).length > 0) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
const desiredSet = new Set(toKnownWorkflows(desiredWorkflows));
|
|
190
|
+
const includeSkills = delivery !== 'commands';
|
|
191
|
+
const includeCommands = delivery !== 'skills';
|
|
192
|
+
for (const toolId of configuredTools) {
|
|
193
|
+
const installed = getInstalledWorkflowsForTool(projectPath, toolId, { includeSkills, includeCommands });
|
|
194
|
+
if (installed.some((workflow) => !desiredSet.has(workflow))) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=profile-sync-drift.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile System
|
|
3
|
+
*
|
|
4
|
+
* Defines workflow profiles that control which workflows are installed.
|
|
5
|
+
* Profiles determine WHICH workflows; delivery (in global config) determines HOW.
|
|
6
|
+
*/
|
|
7
|
+
import type { Profile } from './global-config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Core workflows included in the 'core' profile.
|
|
10
|
+
* These provide the streamlined experience for new users.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CORE_WORKFLOWS: readonly ["propose", "explore", "apply", "archive"];
|
|
13
|
+
/**
|
|
14
|
+
* All available workflows in the system.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "sync", "archive", "bulk-archive", "verify", "onboard"];
|
|
17
|
+
export type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
18
|
+
export type CoreWorkflowId = (typeof CORE_WORKFLOWS)[number];
|
|
19
|
+
/**
|
|
20
|
+
* Resolves which workflows should be active for a given profile configuration.
|
|
21
|
+
*
|
|
22
|
+
* - 'core' profile always returns CORE_WORKFLOWS
|
|
23
|
+
* - 'custom' profile returns the provided customWorkflows, or empty array if not provided
|
|
24
|
+
*/
|
|
25
|
+
export declare function getProfileWorkflows(profile: Profile, customWorkflows?: string[]): readonly string[];
|
|
26
|
+
//# sourceMappingURL=profiles.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile System
|
|
3
|
+
*
|
|
4
|
+
* Defines workflow profiles that control which workflows are installed.
|
|
5
|
+
* Profiles determine WHICH workflows; delivery (in global config) determines HOW.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Core workflows included in the 'core' profile.
|
|
9
|
+
* These provide the streamlined experience for new users.
|
|
10
|
+
*/
|
|
11
|
+
export const CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive'];
|
|
12
|
+
/**
|
|
13
|
+
* All available workflows in the system.
|
|
14
|
+
*/
|
|
15
|
+
export const ALL_WORKFLOWS = [
|
|
16
|
+
'propose',
|
|
17
|
+
'explore',
|
|
18
|
+
'new',
|
|
19
|
+
'continue',
|
|
20
|
+
'apply',
|
|
21
|
+
'ff',
|
|
22
|
+
'sync',
|
|
23
|
+
'archive',
|
|
24
|
+
'bulk-archive',
|
|
25
|
+
'verify',
|
|
26
|
+
'onboard',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Resolves which workflows should be active for a given profile configuration.
|
|
30
|
+
*
|
|
31
|
+
* - 'core' profile always returns CORE_WORKFLOWS
|
|
32
|
+
* - 'custom' profile returns the provided customWorkflows, or empty array if not provided
|
|
33
|
+
*/
|
|
34
|
+
export function getProfileWorkflows(profile, customWorkflows) {
|
|
35
|
+
if (profile === 'custom') {
|
|
36
|
+
return customWorkflows ?? [];
|
|
37
|
+
}
|
|
38
|
+
return CORE_WORKFLOWS;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Common code shared between init and update commands.
|
|
5
5
|
*/
|
|
6
|
-
export { SKILL_NAMES, type SkillName, type ToolSkillStatus, type ToolVersionStatus, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
|
|
6
|
+
export { SKILL_NAMES, type SkillName, COMMAND_IDS, type CommandId, type ToolSkillStatus, type ToolVersionStatus, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
|
|
7
7
|
export { type SkillTemplateEntry, type CommandTemplateEntry, getSkillTemplates, getCommandTemplates, getCommandContents, generateSkillContent, } from './skill-generation.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Common code shared between init and update commands.
|
|
5
5
|
*/
|
|
6
|
-
export { SKILL_NAMES, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
|
|
6
|
+
export { SKILL_NAMES, COMMAND_IDS, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
|
|
7
7
|
export { getSkillTemplates, getCommandTemplates, getCommandContents, generateSkillContent, } from './skill-generation.js';
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
import { getOpsxExploreCommandTemplate, type SkillTemplate } from '../templates/skill-templates.js';
|
|
7
7
|
import type { CommandContent } from '../command-generation/index.js';
|
|
8
8
|
/**
|
|
9
|
-
* Skill template with directory name mapping.
|
|
9
|
+
* Skill template with directory name and workflow ID mapping.
|
|
10
10
|
*/
|
|
11
11
|
export interface SkillTemplateEntry {
|
|
12
12
|
template: SkillTemplate;
|
|
13
13
|
dirName: string;
|
|
14
|
+
workflowId: string;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Command template with ID mapping.
|
|
@@ -20,22 +21,29 @@ export interface CommandTemplateEntry {
|
|
|
20
21
|
id: string;
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
|
-
* Gets
|
|
24
|
+
* Gets skill templates with their directory names, optionally filtered by workflow IDs.
|
|
25
|
+
*
|
|
26
|
+
* @param workflowFilter - If provided, only return templates whose workflowId is in this array
|
|
24
27
|
*/
|
|
25
|
-
export declare function getSkillTemplates(): SkillTemplateEntry[];
|
|
28
|
+
export declare function getSkillTemplates(workflowFilter?: readonly string[]): SkillTemplateEntry[];
|
|
26
29
|
/**
|
|
27
|
-
* Gets
|
|
30
|
+
* Gets command templates with their IDs, optionally filtered by workflow IDs.
|
|
31
|
+
*
|
|
32
|
+
* @param workflowFilter - If provided, only return templates whose id is in this array
|
|
28
33
|
*/
|
|
29
|
-
export declare function getCommandTemplates(): CommandTemplateEntry[];
|
|
34
|
+
export declare function getCommandTemplates(workflowFilter?: readonly string[]): CommandTemplateEntry[];
|
|
30
35
|
/**
|
|
31
|
-
* Converts command templates to CommandContent array.
|
|
36
|
+
* Converts command templates to CommandContent array, optionally filtered by workflow IDs.
|
|
37
|
+
*
|
|
38
|
+
* @param workflowFilter - If provided, only return contents whose id is in this array
|
|
32
39
|
*/
|
|
33
|
-
export declare function getCommandContents(): CommandContent[];
|
|
40
|
+
export declare function getCommandContents(workflowFilter?: readonly string[]): CommandContent[];
|
|
34
41
|
/**
|
|
35
42
|
* Generates skill file content with YAML frontmatter.
|
|
36
43
|
*
|
|
37
44
|
* @param template - The skill template
|
|
38
45
|
* @param generatedByVersion - The OpenSpec version to embed in the file
|
|
46
|
+
* @param transformInstructions - Optional callback to transform the instructions content
|
|
39
47
|
*/
|
|
40
|
-
export declare function generateSkillContent(template: SkillTemplate, generatedByVersion: string): string;
|
|
48
|
+
export declare function generateSkillContent(template: SkillTemplate, generatedByVersion: string, transformInstructions?: (instructions: string) => string): string;
|
|
41
49
|
//# sourceMappingURL=skill-generation.d.ts.map
|
|
@@ -3,29 +3,38 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shared utilities for generating skill and command files.
|
|
5
5
|
*/
|
|
6
|
-
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate, getOpsxOnboardCommandTemplate, } from '../templates/skill-templates.js';
|
|
6
|
+
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxProposeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate, getOpsxOnboardCommandTemplate, getOpsxProposeCommandTemplate, } from '../templates/skill-templates.js';
|
|
7
7
|
/**
|
|
8
|
-
* Gets
|
|
8
|
+
* Gets skill templates with their directory names, optionally filtered by workflow IDs.
|
|
9
|
+
*
|
|
10
|
+
* @param workflowFilter - If provided, only return templates whose workflowId is in this array
|
|
9
11
|
*/
|
|
10
|
-
export function getSkillTemplates() {
|
|
11
|
-
|
|
12
|
-
{ template: getExploreSkillTemplate(), dirName: 'openspec-explore' },
|
|
13
|
-
{ template: getNewChangeSkillTemplate(), dirName: 'openspec-new-change' },
|
|
14
|
-
{ template: getContinueChangeSkillTemplate(), dirName: 'openspec-continue-change' },
|
|
15
|
-
{ template: getApplyChangeSkillTemplate(), dirName: 'openspec-apply-change' },
|
|
16
|
-
{ template: getFfChangeSkillTemplate(), dirName: 'openspec-ff-change' },
|
|
17
|
-
{ template: getSyncSpecsSkillTemplate(), dirName: 'openspec-sync-specs' },
|
|
18
|
-
{ template: getArchiveChangeSkillTemplate(), dirName: 'openspec-archive-change' },
|
|
19
|
-
{ template: getBulkArchiveChangeSkillTemplate(), dirName: 'openspec-bulk-archive-change' },
|
|
20
|
-
{ template: getVerifyChangeSkillTemplate(), dirName: 'openspec-verify-change' },
|
|
21
|
-
{ template: getOnboardSkillTemplate(), dirName: 'openspec-onboard' },
|
|
12
|
+
export function getSkillTemplates(workflowFilter) {
|
|
13
|
+
const all = [
|
|
14
|
+
{ template: getExploreSkillTemplate(), dirName: 'openspec-explore', workflowId: 'explore' },
|
|
15
|
+
{ template: getNewChangeSkillTemplate(), dirName: 'openspec-new-change', workflowId: 'new' },
|
|
16
|
+
{ template: getContinueChangeSkillTemplate(), dirName: 'openspec-continue-change', workflowId: 'continue' },
|
|
17
|
+
{ template: getApplyChangeSkillTemplate(), dirName: 'openspec-apply-change', workflowId: 'apply' },
|
|
18
|
+
{ template: getFfChangeSkillTemplate(), dirName: 'openspec-ff-change', workflowId: 'ff' },
|
|
19
|
+
{ template: getSyncSpecsSkillTemplate(), dirName: 'openspec-sync-specs', workflowId: 'sync' },
|
|
20
|
+
{ template: getArchiveChangeSkillTemplate(), dirName: 'openspec-archive-change', workflowId: 'archive' },
|
|
21
|
+
{ template: getBulkArchiveChangeSkillTemplate(), dirName: 'openspec-bulk-archive-change', workflowId: 'bulk-archive' },
|
|
22
|
+
{ template: getVerifyChangeSkillTemplate(), dirName: 'openspec-verify-change', workflowId: 'verify' },
|
|
23
|
+
{ template: getOnboardSkillTemplate(), dirName: 'openspec-onboard', workflowId: 'onboard' },
|
|
24
|
+
{ template: getOpsxProposeSkillTemplate(), dirName: 'openspec-propose', workflowId: 'propose' },
|
|
22
25
|
];
|
|
26
|
+
if (!workflowFilter)
|
|
27
|
+
return all;
|
|
28
|
+
const filterSet = new Set(workflowFilter);
|
|
29
|
+
return all.filter(entry => filterSet.has(entry.workflowId));
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
|
-
* Gets
|
|
32
|
+
* Gets command templates with their IDs, optionally filtered by workflow IDs.
|
|
33
|
+
*
|
|
34
|
+
* @param workflowFilter - If provided, only return templates whose id is in this array
|
|
26
35
|
*/
|
|
27
|
-
export function getCommandTemplates() {
|
|
28
|
-
|
|
36
|
+
export function getCommandTemplates(workflowFilter) {
|
|
37
|
+
const all = [
|
|
29
38
|
{ template: getOpsxExploreCommandTemplate(), id: 'explore' },
|
|
30
39
|
{ template: getOpsxNewCommandTemplate(), id: 'new' },
|
|
31
40
|
{ template: getOpsxContinueCommandTemplate(), id: 'continue' },
|
|
@@ -36,13 +45,20 @@ export function getCommandTemplates() {
|
|
|
36
45
|
{ template: getOpsxBulkArchiveCommandTemplate(), id: 'bulk-archive' },
|
|
37
46
|
{ template: getOpsxVerifyCommandTemplate(), id: 'verify' },
|
|
38
47
|
{ template: getOpsxOnboardCommandTemplate(), id: 'onboard' },
|
|
48
|
+
{ template: getOpsxProposeCommandTemplate(), id: 'propose' },
|
|
39
49
|
];
|
|
50
|
+
if (!workflowFilter)
|
|
51
|
+
return all;
|
|
52
|
+
const filterSet = new Set(workflowFilter);
|
|
53
|
+
return all.filter(entry => filterSet.has(entry.id));
|
|
40
54
|
}
|
|
41
55
|
/**
|
|
42
|
-
* Converts command templates to CommandContent array.
|
|
56
|
+
* Converts command templates to CommandContent array, optionally filtered by workflow IDs.
|
|
57
|
+
*
|
|
58
|
+
* @param workflowFilter - If provided, only return contents whose id is in this array
|
|
43
59
|
*/
|
|
44
|
-
export function getCommandContents() {
|
|
45
|
-
const commandTemplates = getCommandTemplates();
|
|
60
|
+
export function getCommandContents(workflowFilter) {
|
|
61
|
+
const commandTemplates = getCommandTemplates(workflowFilter);
|
|
46
62
|
return commandTemplates.map(({ template, id }) => ({
|
|
47
63
|
id,
|
|
48
64
|
name: template.name,
|
|
@@ -57,8 +73,12 @@ export function getCommandContents() {
|
|
|
57
73
|
*
|
|
58
74
|
* @param template - The skill template
|
|
59
75
|
* @param generatedByVersion - The OpenSpec version to embed in the file
|
|
76
|
+
* @param transformInstructions - Optional callback to transform the instructions content
|
|
60
77
|
*/
|
|
61
|
-
export function generateSkillContent(template, generatedByVersion) {
|
|
78
|
+
export function generateSkillContent(template, generatedByVersion, transformInstructions) {
|
|
79
|
+
const instructions = transformInstructions
|
|
80
|
+
? transformInstructions(template.instructions)
|
|
81
|
+
: template.instructions;
|
|
62
82
|
return `---
|
|
63
83
|
name: ${template.name}
|
|
64
84
|
description: ${template.description}
|
|
@@ -70,7 +90,7 @@ metadata:
|
|
|
70
90
|
generatedBy: "${generatedByVersion}"
|
|
71
91
|
---
|
|
72
92
|
|
|
73
|
-
${
|
|
93
|
+
${instructions}
|
|
74
94
|
`;
|
|
75
95
|
}
|
|
76
96
|
//# sourceMappingURL=skill-generation.js.map
|
|
@@ -6,17 +6,22 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Names of skill directories created by openspec init.
|
|
8
8
|
*/
|
|
9
|
-
export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-apply-change", "openspec-ff-change", "openspec-sync-specs", "openspec-archive-change", "openspec-bulk-archive-change", "openspec-verify-change"];
|
|
9
|
+
export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-apply-change", "openspec-ff-change", "openspec-sync-specs", "openspec-archive-change", "openspec-bulk-archive-change", "openspec-verify-change", "openspec-onboard", "openspec-propose"];
|
|
10
10
|
export type SkillName = (typeof SKILL_NAMES)[number];
|
|
11
|
+
/**
|
|
12
|
+
* IDs of command templates created by openspec init.
|
|
13
|
+
*/
|
|
14
|
+
export declare const COMMAND_IDS: readonly ["explore", "new", "continue", "apply", "ff", "sync", "archive", "bulk-archive", "verify", "onboard", "propose"];
|
|
15
|
+
export type CommandId = (typeof COMMAND_IDS)[number];
|
|
11
16
|
/**
|
|
12
17
|
* Status of skill configuration for a tool.
|
|
13
18
|
*/
|
|
14
19
|
export interface ToolSkillStatus {
|
|
15
20
|
/** Whether the tool has any skills configured */
|
|
16
21
|
configured: boolean;
|
|
17
|
-
/** Whether all
|
|
22
|
+
/** Whether all skills are configured */
|
|
18
23
|
fullyConfigured: boolean;
|
|
19
|
-
/** Number of skills currently configured
|
|
24
|
+
/** Number of skills currently configured */
|
|
20
25
|
skillCount: number;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
@@ -19,6 +19,24 @@ export const SKILL_NAMES = [
|
|
|
19
19
|
'openspec-archive-change',
|
|
20
20
|
'openspec-bulk-archive-change',
|
|
21
21
|
'openspec-verify-change',
|
|
22
|
+
'openspec-onboard',
|
|
23
|
+
'openspec-propose',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* IDs of command templates created by openspec init.
|
|
27
|
+
*/
|
|
28
|
+
export const COMMAND_IDS = [
|
|
29
|
+
'explore',
|
|
30
|
+
'new',
|
|
31
|
+
'continue',
|
|
32
|
+
'apply',
|
|
33
|
+
'ff',
|
|
34
|
+
'sync',
|
|
35
|
+
'archive',
|
|
36
|
+
'bulk-archive',
|
|
37
|
+
'verify',
|
|
38
|
+
'onboard',
|
|
39
|
+
'propose',
|
|
22
40
|
];
|
|
23
41
|
/**
|
|
24
42
|
* Gets the list of tools with skillsDir configured.
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* The old config file templates (AGENTS.md, project.md, claude-template, etc.)
|
|
5
5
|
* have been removed. The skill-based workflow uses skill-templates.ts directly.
|
|
6
6
|
*/
|
|
7
|
-
export
|
|
7
|
+
export * from './skill-templates.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* The old config file templates (AGENTS.md, project.md, claude-template, etc.)
|
|
5
5
|
* have been removed. The skill-based workflow uses skill-templates.ts directly.
|
|
6
6
|
*/
|
|
7
|
-
// Re-export skill templates
|
|
8
|
-
export
|
|
7
|
+
// Re-export all skill templates and related types through the compatibility facade.
|
|
8
|
+
export * from './skill-templates.js';
|
|
9
9
|
//# sourceMappingURL=index.js.map
|