mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-10 01:12:33 +08:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f9d39c327 | ||
|
|
c9bc915f62 |
@@ -1,25 +1,5 @@
|
||||
# @fission-ai/openspec
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#747](https://github.com/Fission-AI/OpenSpec/pull/747) [`1e94443`](https://github.com/Fission-AI/OpenSpec/commit/1e94443a3551b228eecbc89e95d96d3b9600a192) Thanks [@TabishB](https://github.com/TabishB)! - ### New Features
|
||||
|
||||
- **Profile system** — Choose between `core` (4 essential workflows) and `custom` (pick any subset) profiles to control which skills get installed. Manage profiles with the new `openspec config profile` command
|
||||
- **Propose workflow** — New one-step workflow creates a complete change proposal with design, specs, and tasks from a single request — no need to run `new` then `ff` separately
|
||||
- **AI tool auto-detection** — `openspec init` now scans your project for existing tool directories (`.claude/`, `.cursor/`, etc.) and pre-selects detected tools
|
||||
- **Pi (pi.dev) support** — Pi coding agent is now a supported tool with prompt and skill generation
|
||||
- **Kiro support** — AWS Kiro IDE is now a supported tool with prompt and skill generation
|
||||
- **Sync prunes deselected workflows** — `openspec update` now removes command files and skill directories for workflows you've deselected, keeping your project clean
|
||||
- **Config drift warning** — `openspec config list` warns when global config is out of sync with the current project
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed onboard preflight giving a false "not initialized" error on freshly initialized projects
|
||||
- Fixed archive workflow stopping mid-way when syncing — it now properly resumes after sync completes
|
||||
- Added Windows PowerShell alternatives for onboard shell commands
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -160,14 +160,15 @@ The update command SHALL only run inside an initialized OpenSpec project.
|
||||
- **THEN** the system SHALL display: "No OpenSpec project found. Run 'openspec init' to set up."
|
||||
- **THEN** the system SHALL exit with code 1
|
||||
|
||||
### Requirement: Extra workflows synchronized to active profile
|
||||
The update command SHALL remove workflow files that are no longer selected in the current profile.
|
||||
### Requirement: Extra workflows preserved
|
||||
The update command SHALL NOT remove workflow files that aren't in the current profile.
|
||||
|
||||
#### Scenario: Deselected workflows from previous profile
|
||||
#### Scenario: Extra workflows from previous profile
|
||||
- **WHEN** user runs `openspec update`
|
||||
- **AND** project has workflows not in current profile (e.g., user switched from custom to core or deselected workflows via `openspec config profile`)
|
||||
- **THEN** the system SHALL delete skill and command workflow files for deselected workflows (respecting active delivery mode)
|
||||
- **THEN** the system SHALL keep only workflows currently selected in profile
|
||||
- **AND** project has workflows not in current profile (e.g., user switched from custom to core)
|
||||
- **THEN** the system SHALL NOT delete those extra workflow files
|
||||
- **THEN** the system SHALL only add/update workflows in the current profile
|
||||
- **THEN** the system SHALL display a note: "Note: <count> extra workflows not in profile (use `openspec config profile` to manage)"
|
||||
|
||||
#### Scenario: Delivery change with extra workflows
|
||||
- **WHEN** user runs `openspec update`
|
||||
|
||||
@@ -15,19 +15,21 @@ The design goal is to preserve current behavior while making extension points ex
|
||||
- Define one canonical source for workflow content and metadata
|
||||
- Make tool/agent-specific behavior explicit and centrally discoverable
|
||||
- Keep command adapters as the formatting boundary for tool syntax differences
|
||||
- Represent tool-specific command surfaces and terminology explicitly (not as scattered string rewrites)
|
||||
- Consolidate artifact generation/write orchestration into one reusable engine
|
||||
- Improve correctness with enforceable validation and parity tests
|
||||
|
||||
**Non-Goals:**
|
||||
- Redesigning command semantics or workflow instruction content
|
||||
- Changing user-facing CLI command names/flags in this proposal
|
||||
- Guaranteeing fully accurate literal slash-command strings for every supported tool on day one
|
||||
- Merging unrelated legacy cleanup behavior beyond artifact generation reuse
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Canonical `WorkflowManifest`
|
||||
|
||||
**Decision**: Represent each workflow once in a manifest entry containing canonical skill and command definitions plus metadata defaults.
|
||||
**Decision**: Represent each workflow once in a manifest entry containing canonical skill and command definitions plus metadata defaults. Canonical text uses semantic tokens for tool-specific references.
|
||||
|
||||
Suggested shape:
|
||||
|
||||
@@ -41,6 +43,12 @@ interface WorkflowManifestEntry {
|
||||
tags: string[];
|
||||
compatibility: string;
|
||||
}
|
||||
|
||||
// Examples in canonical workflow text:
|
||||
// - {{cmd.apply}}
|
||||
// - {{cmd.continue.withArg}}
|
||||
// - {{term.change}}
|
||||
// - {{term.workflow}}
|
||||
```
|
||||
|
||||
**Rationale**:
|
||||
@@ -50,7 +58,7 @@ interface WorkflowManifestEntry {
|
||||
|
||||
### 2. `ToolProfileRegistry` for capability wiring
|
||||
|
||||
**Decision**: Add a tool profile layer that maps tool IDs to generation capabilities and behavior.
|
||||
**Decision**: Add a tool profile layer that maps tool IDs to generation capabilities, command-surface rendering, and terminology.
|
||||
|
||||
Suggested shape:
|
||||
|
||||
@@ -59,6 +67,16 @@ interface ToolProfile {
|
||||
toolId: string;
|
||||
skillsDir?: string;
|
||||
commandAdapterId?: string;
|
||||
commandSurface: {
|
||||
pattern: 'opsx-colon' | 'opsx-hyphen' | 'opsx-slash' | 'openspec-hyphen' | 'custom';
|
||||
verified: boolean;
|
||||
aliases?: string[];
|
||||
};
|
||||
terminology: {
|
||||
change: string;
|
||||
workflow: string;
|
||||
command: string;
|
||||
};
|
||||
transforms: string[];
|
||||
}
|
||||
```
|
||||
@@ -67,10 +85,12 @@ interface ToolProfile {
|
||||
- Prevents capability drift between `AI_TOOLS`, adapter registry, and detection logic
|
||||
- Allows intentional "skills-only" tools without implicit special casing
|
||||
- Provides one place to answer "what does this tool support?"
|
||||
- Makes command rendering decisions explicit and testable
|
||||
- Supports future terminology tailoring without copy/paste template forks
|
||||
|
||||
### 3. First-class transform pipeline
|
||||
|
||||
**Decision**: Model transforms as ordered plugins with scope + phase + applicability.
|
||||
**Decision**: Model transforms as ordered plugins with scope + phase + applicability. Include token rendering in the transform pipeline instead of hardcoding literal command strings in templates.
|
||||
|
||||
Suggested shape:
|
||||
|
||||
@@ -87,17 +107,32 @@ interface ArtifactTransform {
|
||||
|
||||
Execution order:
|
||||
1. Render canonical content from manifest
|
||||
2. Apply matching `preAdapter` transforms
|
||||
3. For commands, run adapter formatting
|
||||
4. Apply matching `postAdapter` transforms
|
||||
5. Validate and write
|
||||
2. Apply token-render transform (`{{cmd.*}}`, `{{term.*}}`) using tool profile
|
||||
3. Apply matching `preAdapter` transforms
|
||||
4. For commands, run adapter formatting
|
||||
5. Apply matching `postAdapter` transforms
|
||||
6. Validate and write
|
||||
|
||||
**Rationale**:
|
||||
- Keeps adapters focused on tool formatting, not scattered behavioral rewrites
|
||||
- Makes agent-specific modifications explicit and testable
|
||||
- Replaces ad-hoc transform calls in `init`/`update`
|
||||
- Enables neutral fallback rendering when a tool profile is not verified for literal command syntax
|
||||
|
||||
### 4. Shared `ArtifactSyncEngine`
|
||||
### 4. Fallback policy for unverified command surfaces
|
||||
|
||||
**Decision**: When a tool profile has `commandSurface.verified === false`, command tokens SHALL render to neutral workflow guidance instead of literal slash-command strings.
|
||||
|
||||
Examples:
|
||||
- Literal (verified): `Run {{cmd.apply}}`
|
||||
- Neutral (unverified): `Run the Apply workflow` or `use the apply skill`
|
||||
|
||||
**Rationale**:
|
||||
- Prevents confidently wrong guidance in generated artifacts
|
||||
- Allows incremental tool-surface verification without blocking rollout
|
||||
- Keeps templates stable while rendering policy evolves
|
||||
|
||||
### 5. Shared `ArtifactSyncEngine`
|
||||
|
||||
**Decision**: Introduce a single orchestration engine used by all generation entry points.
|
||||
|
||||
@@ -112,13 +147,15 @@ Responsibilities:
|
||||
- Enables dry-run and future preview features without re-implementing logic
|
||||
- Improves reliability of updates and legacy migrations
|
||||
|
||||
### 5. Validation + parity guardrails
|
||||
### 6. Validation + parity guardrails
|
||||
|
||||
**Decision**: Add strict checks in tests (and optional runtime assertions in dev builds) for:
|
||||
|
||||
- Required skill metadata fields (`license`, `compatibility`, `metadata`) present for all manifest entries
|
||||
- Projection consistency (skills, commands, detection names derived from manifest)
|
||||
- Tool profile consistency (adapter existence, expected capabilities)
|
||||
- Token coverage checks (no unresolved `{{...}}` tokens in rendered outputs)
|
||||
- Tool command-surface verification matrix and fallback expectations
|
||||
- Golden/parity output for key workflows/tools
|
||||
|
||||
**Rationale**:
|
||||
@@ -143,7 +180,8 @@ Adding manifest/profile/transform registries increases conceptual surface area.
|
||||
## Implementation Approach
|
||||
|
||||
1. Build manifest + profile + transform types and registries behind current public API
|
||||
2. Rewire `getSkillTemplates`/`getCommandContents` to derive from manifest
|
||||
3. Introduce `ArtifactSyncEngine` and switch `init` to use it with parity checks
|
||||
4. Switch `update` and legacy upgrade flows to same engine
|
||||
5. Remove duplicate/hardcoded lists after parity is green
|
||||
2. Tokenize command/terminology references in workflow templates
|
||||
3. Rewire `getSkillTemplates`/`getCommandContents` to derive from manifest
|
||||
4. Introduce `ArtifactSyncEngine` and switch `init` to use it with parity checks
|
||||
5. Switch `update` and legacy upgrade flows to same engine
|
||||
6. Remove duplicate/hardcoded lists after parity is green
|
||||
|
||||
@@ -4,7 +4,8 @@ The recent split of `skill-templates.ts` into workflow modules improved readabil
|
||||
|
||||
- Workflow definitions are split from projection logic (`getSkillTemplates`, `getCommandTemplates`, `getCommandContents`)
|
||||
- Tool capability and compatibility are spread across `AI_TOOLS`, `CommandAdapterRegistry`, and hardcoded lists like `SKILL_NAMES`
|
||||
- Agent/tool-specific transformations (for example OpenCode command reference rewrites) are applied in different places (`init`, `update`, and adapter code)
|
||||
- Agent/tool-specific transformations are applied in different places (`init`, `update`, and adapter code)
|
||||
- Command and terminology references are currently hardcoded in workflow text, but tool invocation surfaces vary (`/opsx:apply`, `/opsx-apply`, `/opsx/apply`, and tool-specific naming)
|
||||
- Artifact writing logic is duplicated across `init`, `update`, and legacy-upgrade flow
|
||||
|
||||
This fragmentation creates drift risk (missing exports, missing metadata parity, mismatched counts/support) and makes future workflow/tool additions slower and less predictable.
|
||||
@@ -15,13 +16,16 @@ This fragmentation creates drift risk (missing exports, missing metadata parity,
|
||||
- Introduce a `ToolProfileRegistry` to centralize tool capabilities (skills path, command adapter, transforms)
|
||||
- Introduce a first-class transform pipeline with explicit phases (`preAdapter`, `postAdapter`) and scopes (`skill`, `command`, `both`)
|
||||
- Introduce a shared `ArtifactSyncEngine` used by `init`, `update`, and legacy upgrade paths
|
||||
- Add tokenized workflow text rendering so command references and tool terminology are resolved per tool profile at generation time
|
||||
- Add explicit command-surface profiles per tool (pattern, namespace/path style, alias support, verification status)
|
||||
- Add safe fallback behavior: when a tool command surface is not verified, render neutral workflow guidance (for example skill/workflow names) instead of potentially wrong literal command strings
|
||||
- Add strict validation and test guardrails to preserve fidelity during migration and future changes
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `template-artifact-pipeline`: Unified workflow manifest, tool profile registry, transform pipeline, and sync engine for skill/command generation
|
||||
- `template-artifact-pipeline`: Unified workflow manifest, tool profile registry, token-aware transform pipeline, and sync engine for skill/command generation
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
@@ -41,7 +45,9 @@ This fragmentation creates drift risk (missing exports, missing metadata parity,
|
||||
- **Testing additions**:
|
||||
- Manifest completeness tests (workflows, required metadata, projection parity)
|
||||
- Transform ordering and applicability tests
|
||||
- Tool command-surface/terminology profile validation tests
|
||||
- End-to-end parity tests for generated skill/command outputs across tools
|
||||
- **User-facing behavior**:
|
||||
- No new CLI surface area required
|
||||
- Existing generated artifacts remain behaviorally equivalent unless explicitly changed in future deltas
|
||||
- Generated text may become more tool-accurate for verified tool profiles
|
||||
- Generated text may intentionally use neutral workflow wording for unverified tools to avoid incorrect slash-command guidance
|
||||
|
||||
@@ -10,14 +10,18 @@
|
||||
- [ ] 2.1 Add `ToolProfile` types and `ToolProfileRegistry`
|
||||
- [ ] 2.2 Map all currently supported tools to explicit profile entries
|
||||
- [ ] 2.3 Wire profile lookups to command adapter resolution and skills path resolution
|
||||
- [ ] 2.4 Replace hardcoded detection arrays (for example `SKILL_NAMES`) with manifest-derived values
|
||||
- [ ] 2.4 Add per-tool `commandSurface` metadata (pattern, aliases, `verified` flag)
|
||||
- [ ] 2.5 Add per-tool terminology metadata (for example change/workflow/command labels)
|
||||
- [ ] 2.6 Replace hardcoded detection arrays (for example `SKILL_NAMES`) with manifest-derived values
|
||||
|
||||
## 3. Transform Pipeline
|
||||
|
||||
- [ ] 3.1 Introduce transform interfaces (`scope`, `phase`, `priority`, `applies`, `transform`)
|
||||
- [ ] 3.2 Implement transform runner with deterministic ordering
|
||||
- [ ] 3.3 Migrate OpenCode command reference rewrite to transform pipeline
|
||||
- [ ] 3.4 Remove ad-hoc transform invocation from `init` and `update`
|
||||
- [ ] 3.3 Add token renderer transform for command + terminology tokens (`{{cmd.*}}`, `{{term.*}}`)
|
||||
- [ ] 3.4 Implement neutral fallback rendering for tools with unverified command surfaces
|
||||
- [ ] 3.5 Migrate OpenCode command reference rewrite to transform pipeline
|
||||
- [ ] 3.6 Remove ad-hoc transform invocation from `init` and `update`
|
||||
|
||||
## 4. Artifact Sync Engine
|
||||
|
||||
@@ -29,10 +33,12 @@
|
||||
## 5. Validation and Tests
|
||||
|
||||
- [ ] 5.1 Add manifest completeness tests (metadata required fields, command IDs, dir names)
|
||||
- [ ] 5.2 Add tool-profile consistency tests (skillsDir support and adapter/profile alignment)
|
||||
- [ ] 5.3 Add transform applicability/order tests
|
||||
- [ ] 5.4 Expand parity tests for representative workflow/tool matrix
|
||||
- [ ] 5.5 Run full test suite and verify generated artifacts remain stable
|
||||
- [ ] 5.2 Add tool-profile consistency tests (skillsDir support, adapter/profile alignment, command-surface metadata)
|
||||
- [ ] 5.3 Add token rendering tests (all tokens resolved, per-tool rendering correctness)
|
||||
- [ ] 5.4 Add fallback tests for unverified tool command surfaces
|
||||
- [ ] 5.5 Add transform applicability/order tests
|
||||
- [ ] 5.6 Expand parity tests for representative workflow/tool matrix
|
||||
- [ ] 5.7 Run full test suite and verify generated artifacts remain stable
|
||||
|
||||
## 6. Cleanup and Documentation
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fission-ai/openspec",
|
||||
"version": "1.2.0",
|
||||
"version": "1.1.1",
|
||||
"description": "AI-native system for spec-driven development",
|
||||
"keywords": [
|
||||
"openspec",
|
||||
|
||||
@@ -80,10 +80,11 @@ export function getConfiguredToolsForProfileSync(projectPath: string): string[]
|
||||
/**
|
||||
* Detects if a single tool has profile/delivery drift against the desired state.
|
||||
*
|
||||
* This function covers:
|
||||
* - required artifacts missing for selected workflows
|
||||
* - artifacts that should not exist for the selected delivery mode
|
||||
* - artifacts for workflows that were deselected from the current profile
|
||||
* Note: this function is intentionally scoped to "required artifacts missing"
|
||||
* and "artifacts that should not exist for the selected delivery mode".
|
||||
* Extra workflows that are outside the desired profile are handled by
|
||||
* `hasProjectConfigDrift`, which compares installed workflow IDs against
|
||||
* the desired workflow set.
|
||||
*/
|
||||
export function hasToolProfileOrDeliveryDrift(
|
||||
projectPath: string,
|
||||
@@ -95,7 +96,6 @@ export function hasToolProfileOrDeliveryDrift(
|
||||
if (!tool?.skillsDir) return false;
|
||||
|
||||
const knownDesiredWorkflows = toKnownWorkflows(desiredWorkflows);
|
||||
const desiredWorkflowSet = new Set<WorkflowId>(knownDesiredWorkflows);
|
||||
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
||||
const adapter = CommandAdapterRegistry.get(toolId);
|
||||
const shouldGenerateSkills = delivery !== 'commands';
|
||||
@@ -109,16 +109,6 @@ export function hasToolProfileOrDeliveryDrift(
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Deselecting workflows in a profile should trigger sync.
|
||||
for (const workflow of ALL_WORKFLOWS) {
|
||||
if (desiredWorkflowSet.has(workflow)) continue;
|
||||
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
||||
const skillDir = path.join(skillsDir, dirName);
|
||||
if (fs.existsSync(skillDir)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const workflow of ALL_WORKFLOWS) {
|
||||
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
||||
@@ -137,16 +127,6 @@ export function hasToolProfileOrDeliveryDrift(
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Deselecting workflows in a profile should trigger sync.
|
||||
for (const workflow of ALL_WORKFLOWS) {
|
||||
if (desiredWorkflowSet.has(workflow)) continue;
|
||||
const cmdPath = adapter.getFilePath(workflow);
|
||||
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (!shouldGenerateCommands && adapter) {
|
||||
for (const workflow of ALL_WORKFLOWS) {
|
||||
const cmdPath = adapter.getFilePath(workflow);
|
||||
|
||||
@@ -176,8 +176,6 @@ export class UpdateCommand {
|
||||
const failedTools: Array<{ name: string; error: string }> = [];
|
||||
let removedCommandCount = 0;
|
||||
let removedSkillCount = 0;
|
||||
let removedDeselectedCommandCount = 0;
|
||||
let removedDeselectedSkillCount = 0;
|
||||
|
||||
for (const toolId of toolsToUpdate) {
|
||||
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
||||
@@ -199,8 +197,6 @@ export class UpdateCommand {
|
||||
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
||||
await FileSystemUtils.writeFile(skillFile, skillContent);
|
||||
}
|
||||
|
||||
removedDeselectedSkillCount += await this.removeUnselectedSkillDirs(skillsDir, desiredWorkflows);
|
||||
}
|
||||
|
||||
// Delete skill directories if delivery is commands-only
|
||||
@@ -218,12 +214,6 @@ export class UpdateCommand {
|
||||
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
|
||||
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
||||
}
|
||||
|
||||
removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(
|
||||
resolvedProjectPath,
|
||||
toolId,
|
||||
desiredWorkflows
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,12 +247,6 @@ export class UpdateCommand {
|
||||
if (removedSkillCount > 0) {
|
||||
console.log(chalk.dim(`Removed: ${removedSkillCount} skill directories (delivery: commands)`));
|
||||
}
|
||||
if (removedDeselectedCommandCount > 0) {
|
||||
console.log(chalk.dim(`Removed: ${removedDeselectedCommandCount} command files (deselected workflows)`));
|
||||
}
|
||||
if (removedDeselectedSkillCount > 0) {
|
||||
console.log(chalk.dim(`Removed: ${removedDeselectedSkillCount} skill directories (deselected workflows)`));
|
||||
}
|
||||
|
||||
// 12. Show onboarding message for newly configured tools from legacy upgrade
|
||||
if (newlyConfiguredTools.length > 0) {
|
||||
@@ -394,36 +378,6 @@ export class UpdateCommand {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes skill directories for workflows that are no longer selected in the active profile.
|
||||
* Returns the number of directories removed.
|
||||
*/
|
||||
private async removeUnselectedSkillDirs(
|
||||
skillsDir: string,
|
||||
desiredWorkflows: readonly (typeof ALL_WORKFLOWS)[number][]
|
||||
): Promise<number> {
|
||||
const desiredSet = new Set(desiredWorkflows);
|
||||
let removed = 0;
|
||||
|
||||
for (const workflow of ALL_WORKFLOWS) {
|
||||
if (desiredSet.has(workflow)) continue;
|
||||
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
||||
if (!dirName) continue;
|
||||
|
||||
const skillDir = path.join(skillsDir, dirName);
|
||||
try {
|
||||
if (fs.existsSync(skillDir)) {
|
||||
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
||||
removed++;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes command files for workflows when delivery changed to skills-only.
|
||||
* Returns the number of files removed.
|
||||
@@ -454,40 +408,6 @@ export class UpdateCommand {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes command files for workflows that are no longer selected in the active profile.
|
||||
* Returns the number of files removed.
|
||||
*/
|
||||
private async removeUnselectedCommandFiles(
|
||||
projectPath: string,
|
||||
toolId: string,
|
||||
desiredWorkflows: readonly (typeof ALL_WORKFLOWS)[number][]
|
||||
): Promise<number> {
|
||||
let removed = 0;
|
||||
|
||||
const adapter = CommandAdapterRegistry.get(toolId);
|
||||
if (!adapter) return 0;
|
||||
|
||||
const desiredSet = new Set(desiredWorkflows);
|
||||
|
||||
for (const workflow of ALL_WORKFLOWS) {
|
||||
if (desiredSet.has(workflow)) continue;
|
||||
const cmdPath = adapter.getFilePath(workflow);
|
||||
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(fullPath)) {
|
||||
await fs.promises.unlink(fullPath);
|
||||
removed++;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and handle legacy OpenSpec artifacts.
|
||||
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
||||
|
||||
@@ -1567,7 +1567,7 @@ content
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should remove workflows outside profile during update sync', async () => {
|
||||
it('should display extra workflows note when workflows outside profile exist', async () => {
|
||||
// Set core profile (propose, explore, apply, archive)
|
||||
setMockConfig({
|
||||
featureFlags: {},
|
||||
@@ -1583,28 +1583,19 @@ content
|
||||
// Add a non-core workflow
|
||||
await fs.mkdir(path.join(skillsDir, 'openspec-new-change'), { recursive: true });
|
||||
await fs.writeFile(path.join(skillsDir, 'openspec-new-change', 'SKILL.md'), 'old');
|
||||
const extraCommandFile = path.join(testDir, '.claude', 'commands', 'opsx', 'new.md');
|
||||
await fs.mkdir(path.dirname(extraCommandFile), { recursive: true });
|
||||
await fs.writeFile(extraCommandFile, 'old');
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'log');
|
||||
|
||||
await updateCommand.execute(testDir);
|
||||
|
||||
// Deselected workflow artifacts should be removed for both delivery surfaces.
|
||||
expect(await FileSystemUtils.fileExists(
|
||||
path.join(skillsDir, 'openspec-new-change', 'SKILL.md')
|
||||
)).toBe(false);
|
||||
expect(await FileSystemUtils.fileExists(extraCommandFile)).toBe(false);
|
||||
|
||||
// Should report deselected workflow cleanup.
|
||||
// Should display note about extra workflows
|
||||
const calls = consoleSpy.mock.calls.map(call =>
|
||||
call.map(arg => String(arg)).join(' ')
|
||||
);
|
||||
const hasDeselectedRemovalNote = calls.some(call =>
|
||||
call.includes('deselected workflows')
|
||||
const hasExtraNote = calls.some(call =>
|
||||
call.includes('extra workflows not in profile')
|
||||
);
|
||||
expect(hasDeselectedRemovalNote).toBe(true);
|
||||
expect(hasExtraNote).toBe(true);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user