@codewalla_india/openspec 1.3.0 → 1.3.2
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 -22
- package/README.md +272 -230
- package/bin/openspec.js +5 -5
- package/dist/cli/index.js +99 -62
- package/dist/commands/change.d.ts +0 -2
- package/dist/commands/change.js +107 -86
- package/dist/commands/config.js +14 -16
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/feedback.js +79 -30
- package/dist/commands/modify.d.ts +26 -0
- package/dist/commands/modify.js +147 -0
- package/dist/commands/schema.js +170 -91
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +49 -29
- package/dist/commands/store.js +15 -21
- package/dist/commands/validate.d.ts +8 -0
- package/dist/commands/validate.js +43 -20
- package/dist/commands/workflow/index.d.ts +2 -2
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/instructions.d.ts +6 -26
- package/dist/commands/workflow/instructions.js +183 -376
- package/dist/commands/workflow/new-change.d.ts +0 -4
- package/dist/commands/workflow/new-change.js +10 -28
- package/dist/commands/workflow/shared.d.ts +34 -19
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +10 -32
- package/dist/commands/workflow/templates.js +15 -5
- package/dist/commands/workset.d.ts +2 -2
- package/dist/commands/workset.js +19 -22
- package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
- package/dist/comprehension-quiz/fingerprint.js +84 -0
- package/dist/comprehension-quiz/index.d.ts +42 -0
- package/dist/comprehension-quiz/index.js +47 -0
- package/dist/comprehension-quiz/pass-record.d.ts +44 -0
- package/dist/comprehension-quiz/pass-record.js +98 -0
- package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
- package/dist/comprehension-quiz/providers/anthropic.js +53 -0
- package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
- package/dist/comprehension-quiz/providers/factory.js +174 -0
- package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
- package/dist/comprehension-quiz/providers/interface.js +63 -0
- package/dist/comprehension-quiz/providers/local.d.ts +34 -0
- package/dist/comprehension-quiz/providers/local.js +53 -0
- package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
- package/dist/comprehension-quiz/providers/ollama.js +53 -0
- package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
- package/dist/comprehension-quiz/providers/openai.js +53 -0
- package/dist/comprehension-quiz/question-generator.d.ts +69 -0
- package/dist/comprehension-quiz/question-generator.js +141 -0
- package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
- package/dist/comprehension-quiz/quiz-executor.js +165 -0
- package/dist/comprehension-quiz/types.d.ts +115 -0
- package/dist/comprehension-quiz/types.js +11 -0
- package/dist/core/archive.d.ts +14 -1
- package/dist/core/archive.js +1350 -190
- package/dist/core/artifact-graph/graph.d.ts +28 -9
- package/dist/core/artifact-graph/graph.js +94 -43
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
- package/dist/core/artifact-graph/instruction-loader.js +70 -12
- package/dist/core/artifact-graph/outputs.d.ts +1 -0
- package/dist/core/artifact-graph/outputs.js +67 -5
- package/dist/core/artifact-graph/resolver.d.ts +14 -0
- package/dist/core/artifact-graph/resolver.js +69 -15
- package/dist/core/artifact-graph/types.js +21 -3
- package/dist/core/available-tools.d.ts +2 -2
- package/dist/core/available-tools.js +14 -3
- package/dist/core/change-metadata/schema.d.ts +2 -0
- package/dist/core/change-metadata/schema.js +13 -0
- package/dist/core/change-status-policy.d.ts +10 -1
- package/dist/core/change-status-policy.js +30 -2
- package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
- package/dist/core/command-generation/adapters/amazon-q.js +11 -5
- package/dist/core/command-generation/adapters/antigravity.js +6 -5
- package/dist/core/command-generation/adapters/auggie.js +7 -6
- package/dist/core/command-generation/adapters/bob.d.ts +5 -1
- package/dist/core/command-generation/adapters/bob.js +11 -10
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +12 -17
- package/dist/core/command-generation/adapters/cline.js +5 -5
- package/dist/core/command-generation/adapters/codebuddy.js +8 -7
- package/dist/core/command-generation/adapters/continue.js +8 -7
- package/dist/core/command-generation/adapters/costrict.js +7 -6
- package/dist/core/command-generation/adapters/crush.js +9 -9
- package/dist/core/command-generation/adapters/cursor.js +8 -8
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/factory.js +7 -6
- package/dist/core/command-generation/adapters/gemini.js +40 -5
- package/dist/core/command-generation/adapters/github-copilot.js +6 -5
- package/dist/core/command-generation/adapters/iflow.js +9 -8
- package/dist/core/command-generation/adapters/index.d.ts +4 -2
- package/dist/core/command-generation/adapters/index.js +4 -2
- package/dist/core/command-generation/adapters/junie.js +6 -5
- package/dist/core/command-generation/adapters/kilocode.js +1 -1
- package/dist/core/command-generation/adapters/kiro.js +6 -5
- package/dist/core/command-generation/adapters/lingma.js +9 -9
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
- package/dist/core/command-generation/adapters/opencode.js +6 -8
- package/dist/core/command-generation/adapters/pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/pi.js +7 -10
- package/dist/core/command-generation/adapters/qoder.js +9 -9
- package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
- package/dist/core/command-generation/adapters/qwen.js +14 -9
- package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
- package/dist/core/command-generation/adapters/roocode.js +9 -9
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +28 -0
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +8 -0
- package/dist/core/command-generation/generator.js +15 -1
- package/dist/core/command-generation/index.d.ts +1 -1
- package/dist/core/command-generation/index.js +1 -1
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.js +8 -4
- package/dist/core/command-generation/types.d.ts +9 -2
- package/dist/core/command-generation/yaml.d.ts +20 -6
- package/dist/core/command-generation/yaml.js +31 -21
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/completions/command-registry.js +16 -61
- package/dist/core/completions/generators/bash-generator.js +41 -41
- package/dist/core/completions/generators/fish-generator.js +7 -7
- package/dist/core/completions/generators/powershell-generator.js +44 -37
- package/dist/core/completions/generators/zsh-generator.js +33 -33
- package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
- package/dist/core/completions/installers/zsh-installer.js +20 -4
- package/dist/core/completions/templates/bash-templates.js +24 -24
- package/dist/core/completions/templates/fish-templates.js +38 -38
- package/dist/core/completions/templates/powershell-templates.js +28 -28
- package/dist/core/completions/templates/zsh-templates.js +39 -39
- package/dist/core/config-prompts.d.ts +1 -1
- package/dist/core/config-prompts.js +14 -1
- package/dist/core/config-schema.d.ts +10 -1
- package/dist/core/config-schema.js +67 -1
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +46 -5
- package/dist/core/file-state.js +56 -24
- package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
- package/dist/core/github-copilot/cloud-agent.js +497 -0
- package/dist/core/global-config.d.ts +16 -0
- package/dist/core/init.d.ts +58 -0
- package/dist/core/init.js +474 -73
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +253 -24
- package/dist/core/list.js +23 -16
- package/dist/core/migration.d.ts +73 -1
- package/dist/core/migration.js +360 -11
- package/dist/core/modify/conflict-detector.d.ts +26 -0
- package/dist/core/modify/conflict-detector.js +62 -0
- package/dist/core/modify/dependency-propagator.d.ts +38 -0
- package/dist/core/modify/dependency-propagator.js +103 -0
- package/dist/core/modify/history-tracker.d.ts +34 -0
- package/dist/core/modify/history-tracker.js +114 -0
- package/dist/core/modify/index.d.ts +7 -0
- package/dist/core/modify/index.js +7 -0
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/openspec-root.js +18 -15
- package/dist/core/parsers/change-parser.d.ts +17 -2
- package/dist/core/parsers/change-parser.js +35 -21
- package/dist/core/parsers/code-fence.d.ts +15 -0
- package/dist/core/parsers/code-fence.js +49 -0
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +7 -60
- package/dist/core/parsers/requirement-blocks.d.ts +29 -0
- package/dist/core/parsers/requirement-blocks.js +150 -31
- package/dist/core/parsers/requirement-text.d.ts +48 -0
- package/dist/core/parsers/requirement-text.js +98 -0
- package/dist/core/parsers/spec-structure.d.ts +1 -1
- package/dist/core/parsers/spec-structure.js +20 -30
- package/dist/core/profile-sync-drift.d.ts +0 -8
- package/dist/core/profile-sync-drift.js +49 -50
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -2
- package/dist/core/project-config.d.ts +27 -18
- package/dist/core/project-config.js +101 -34
- package/dist/core/references.js +42 -4
- package/dist/core/relationship-health.d.ts +8 -0
- package/dist/core/relationship-health.js +13 -0
- package/dist/core/root-selection.d.ts +9 -6
- package/dist/core/root-selection.js +42 -5
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +41 -0
- package/dist/core/shared/skill-generation.js +17 -13
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +27 -7
- package/dist/core/shared/tool-detection.js +167 -37
- package/dist/core/shared-skill-target.d.ts +16 -0
- package/dist/core/shared-skill-target.js +156 -0
- package/dist/core/specs-apply.d.ts +80 -33
- package/dist/core/specs-apply.js +667 -145
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/store/operations.js +20 -0
- package/dist/core/templates/skill-templates.d.ts +3 -2
- package/dist/core/templates/skill-templates.js +3 -2
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +186 -323
- package/dist/core/templates/workflows/archive-change.js +387 -260
- package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
- package/dist/core/templates/workflows/continue-change.js +210 -219
- package/dist/core/templates/workflows/explore.js +479 -443
- package/dist/core/templates/workflows/feedback.js +97 -97
- package/dist/core/templates/workflows/ff-change.js +200 -191
- package/dist/core/templates/workflows/modify-change.d.ts +4 -1
- package/dist/core/templates/workflows/modify-change.js +237 -115
- package/dist/core/templates/workflows/new-change.js +127 -134
- package/dist/core/templates/workflows/onboard.js +548 -542
- package/dist/core/templates/workflows/propose.js +273 -217
- package/dist/core/templates/workflows/store-selection.d.ts +1 -1
- package/dist/core/templates/workflows/store-selection.js +1 -1
- package/dist/core/templates/workflows/sync-specs.js +500 -273
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +177 -0
- package/dist/core/templates/workflows/verify-change.js +324 -326
- package/dist/core/update.d.ts +40 -5
- package/dist/core/update.js +411 -96
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/plan-validator.d.ts +8 -0
- package/dist/core/validation/plan-validator.js +45 -0
- package/dist/core/validation/task-numbering.d.ts +16 -0
- package/dist/core/validation/task-numbering.js +57 -0
- package/dist/core/validation/validator.d.ts +27 -3
- package/dist/core/validation/validator.js +337 -48
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +731 -0
- package/dist/core/view.js +13 -18
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/caller-detection.d.ts +27 -0
- package/dist/telemetry/caller-detection.js +85 -0
- package/dist/telemetry/caller-detection.test.d.ts +5 -0
- package/dist/telemetry/config.d.ts +48 -2
- package/dist/telemetry/config.js +113 -11
- package/dist/telemetry/config.test.d.ts +5 -0
- package/dist/telemetry/deduplication.d.ts +35 -0
- package/dist/telemetry/deduplication.js +82 -0
- package/dist/telemetry/deduplication.test.d.ts +5 -0
- package/dist/telemetry/identity.d.ts +62 -22
- package/dist/telemetry/identity.js +147 -102
- package/dist/telemetry/index.d.ts +126 -15
- package/dist/telemetry/index.js +569 -37
- package/dist/telemetry/sanitization.d.ts +40 -0
- package/dist/telemetry/sanitization.js +114 -0
- package/dist/telemetry/sanitization.test.d.ts +5 -0
- package/dist/telemetry/throttling.d.ts +20 -0
- package/dist/telemetry/throttling.js +70 -0
- package/dist/ui/welcome-screen.d.ts +9 -1
- package/dist/ui/welcome-screen.js +91 -38
- package/dist/utils/change-metadata.d.ts +43 -0
- package/dist/utils/change-metadata.js +123 -7
- package/dist/utils/change-utils.d.ts +10 -4
- package/dist/utils/change-utils.js +20 -13
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +92 -6
- package/dist/utils/command-references.js +180 -7
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/file-system.d.ts +10 -0
- package/dist/utils/file-system.js +70 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/interactive.d.ts +23 -0
- package/dist/utils/interactive.js +32 -0
- package/dist/utils/item-discovery.d.ts +19 -0
- package/dist/utils/item-discovery.js +30 -48
- package/dist/utils/shell-detection.d.ts +1 -1
- package/dist/utils/shell-detection.js +64 -11
- package/dist/utils/spec-discovery.d.ts +40 -0
- package/dist/utils/spec-discovery.js +115 -0
- package/dist/utils/task-progress.d.ts +29 -1
- package/dist/utils/task-progress.js +109 -15
- package/package.json +96 -84
- package/schemas/spec-driven/schema.yaml +252 -184
- package/schemas/spec-driven/templates/design.md +19 -19
- package/schemas/spec-driven/templates/plan-template.md +91 -0
- package/schemas/spec-driven/templates/proposal.md +29 -24
- package/schemas/spec-driven/templates/spec.md +11 -8
- package/schemas/spec-driven/templates/tasks.md +9 -9
- package/scripts/postinstall.js +83 -83
- package/dist/core/command-generation/adapters/codex.d.ts +0 -16
- package/dist/core/command-generation/adapters/codex.js +0 -39
- package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
- package/dist/core/command-generation/adapters/windsurf.js +0 -38
- package/dist/core/comprehension/config.d.ts +0 -20
- package/dist/core/comprehension/config.js +0 -23
- package/dist/core/comprehension/fingerprint.d.ts +0 -14
- package/dist/core/comprehension/fingerprint.js +0 -33
- package/dist/core/comprehension/index.d.ts +0 -57
- package/dist/core/comprehension/index.js +0 -87
- package/dist/core/comprehension/pass-record.d.ts +0 -29
- package/dist/core/comprehension/pass-record.js +0 -64
- package/dist/core/comprehension/stats.d.ts +0 -35
- package/dist/core/comprehension/stats.js +0 -133
- package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
- package/dist/core/templates/workflows/comprehension-guidance.js +0 -71
- package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
- package/dist/core/templates/workflows/mcp-guidance.js +0 -131
- package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
- package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
- package/dist/telemetry/caller.d.ts +0 -5
- package/dist/telemetry/caller.js +0 -29
- package/dist/telemetry/client.d.ts +0 -27
- package/dist/telemetry/client.js +0 -127
- package/dist/telemetry/command-context.d.ts +0 -13
- package/dist/telemetry/command-context.js +0 -59
- package/dist/telemetry/comprehension.d.ts +0 -44
- package/dist/telemetry/comprehension.js +0 -105
- package/dist/telemetry/content.d.ts +0 -10
- package/dist/telemetry/content.js +0 -56
- package/dist/telemetry/git-stats.d.ts +0 -12
- package/dist/telemetry/git-stats.js +0 -69
- package/dist/telemetry/identify-cache.d.ts +0 -7
- package/dist/telemetry/identify-cache.js +0 -47
- package/dist/telemetry/input.d.ts +0 -17
- package/dist/telemetry/input.js +0 -68
- package/dist/telemetry/marker.d.ts +0 -37
- package/dist/telemetry/marker.js +0 -67
- package/dist/telemetry/workflow.d.ts +0 -85
- package/dist/telemetry/workflow.js +0 -318
- package/schemas/spec-driven/templates/plan.md +0 -19
package/dist/core/migration.js
CHANGED
|
@@ -4,26 +4,359 @@
|
|
|
4
4
|
* One-time migration logic for existing projects when profile system is introduced.
|
|
5
5
|
* Called by both init and update commands before profile resolution.
|
|
6
6
|
*/
|
|
7
|
+
import { AI_TOOLS } from './config.js';
|
|
7
8
|
import { getGlobalConfig, getGlobalConfigPath, saveGlobalConfig } from './global-config.js';
|
|
8
9
|
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
10
|
+
import { resolveCommandInvocation, resolveCommandSurfaceCapability, shouldGenerateCommandsForTool, } from './command-surface.js';
|
|
9
11
|
import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js';
|
|
12
|
+
import { COMMAND_IDS } from './shared/tool-detection.js';
|
|
10
13
|
import { ALL_WORKFLOWS } from './profiles.js';
|
|
14
|
+
import { getSkillReferenceTransformer, getTransformerForTool } from '../utils/command-references.js';
|
|
15
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
16
|
+
import { isSharedSkillTargetActive } from './shared-skill-target.js';
|
|
17
|
+
import { isLegacyCodexSkillEquivalentToCurrent } from './shared/skill-content-equivalence.js';
|
|
11
18
|
import path from 'path';
|
|
12
19
|
import * as fs from 'fs';
|
|
13
|
-
|
|
20
|
+
import { resolveToolSkillsDir, toolSupportsSkills } from './shared/skill-paths.js';
|
|
21
|
+
/**
|
|
22
|
+
* Former tool roots whose OpenSpec-managed content belongs under the tool's
|
|
23
|
+
* current skillsDir. User files are never touched.
|
|
24
|
+
*/
|
|
25
|
+
export const LEGACY_TOOL_ROOTS = {
|
|
26
|
+
// Kimi CLI became Kimi Code and moved from .kimi to .kimi-code.
|
|
27
|
+
kimi: [{ root: '.kimi', needsConsent: false }],
|
|
28
|
+
// Windsurf was rebranded to Devin Desktop on 2026-06-02 and its config
|
|
29
|
+
// directory moved to .devin/. Devin Desktop reads .windsurf/ only as a
|
|
30
|
+
// fallback and Devin Local does not read it at all, so moving is the right
|
|
31
|
+
// default — but a pre-rebrand Windsurf build reads ONLY .windsurf/, and
|
|
32
|
+
// nothing on disk tells that user apart, so the move is offered, not taken.
|
|
33
|
+
devin: [{ root: '.windsurf', needsConsent: true }],
|
|
34
|
+
// Codex now reads the canonical shared .agents root. Generate the current
|
|
35
|
+
// replacement first so a divergent legacy file is preserved, not overwritten.
|
|
36
|
+
codex: [{ root: '.codex', needsConsent: false, timing: 'after-generation' }],
|
|
37
|
+
};
|
|
38
|
+
function classifyManagedFile(source, destination) {
|
|
39
|
+
if (isSamePath(source, destination))
|
|
40
|
+
return 'skip';
|
|
41
|
+
if (!fs.existsSync(destination))
|
|
42
|
+
return 'move';
|
|
43
|
+
try {
|
|
44
|
+
const sourceContent = fs.readFileSync(source, 'utf-8');
|
|
45
|
+
const destinationContent = fs.readFileSync(destination, 'utf-8');
|
|
46
|
+
const equivalentGeneratedSkills = path.basename(source) === 'SKILL.md' &&
|
|
47
|
+
path.basename(destination) === 'SKILL.md' &&
|
|
48
|
+
isLegacyCodexSkillEquivalentToCurrent(sourceContent, destinationContent);
|
|
49
|
+
return sourceContent === destinationContent || equivalentGeneratedSkills
|
|
50
|
+
? 'drop'
|
|
51
|
+
: 'keep';
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return 'keep';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Rewrites a generated command path from the tool's current root to a legacy
|
|
59
|
+
* one, so `.devin/workflows/opsx-apply.md` locates its `.windsurf/` twin
|
|
60
|
+
* without the migration hard-coding either layout.
|
|
61
|
+
*
|
|
62
|
+
* Returns undefined for adapters whose paths are absolute (global-scoped
|
|
63
|
+
* command files) or do not start at the tool root — neither can be relocated
|
|
64
|
+
* by swapping a leading segment.
|
|
65
|
+
*/
|
|
66
|
+
function legacyCommandPath(commandPath, currentRoot, legacyRoot) {
|
|
67
|
+
if (path.isAbsolute(commandPath))
|
|
68
|
+
return undefined;
|
|
69
|
+
const segments = commandPath.split(/[\\/]/);
|
|
70
|
+
if (segments[0] !== currentRoot)
|
|
71
|
+
return undefined;
|
|
72
|
+
segments[0] = legacyRoot;
|
|
73
|
+
return path.join(...segments);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reports the OpenSpec content sitting under each tool's legacy root, without
|
|
77
|
+
* moving anything. Callers use this to ask before a move that needs consent.
|
|
78
|
+
*/
|
|
79
|
+
export function findLegacyToolMigrations(projectPath, timing = 'before-generation') {
|
|
80
|
+
return collectLegacyToolMigrations(projectPath, false, undefined, timing);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Moves OpenSpec-managed skill directories (openspec-*) and command files
|
|
84
|
+
* (opsx-*) from a tool's legacy root to its current one. When the destination
|
|
85
|
+
* already exists the legacy copy is removed instead. Legacy directories are
|
|
86
|
+
* deleted only when left empty, so user files under the old location — a
|
|
87
|
+
* hand-written Cascade workflow next to the generated ones — are preserved.
|
|
88
|
+
*
|
|
89
|
+
* @param projectPath - Project root
|
|
90
|
+
* @param toolIds - Restrict the move to these tools; omit to move every tool
|
|
91
|
+
* whose legacy root needs no consent
|
|
92
|
+
*/
|
|
93
|
+
export function migrateLegacyToolDirs(projectPath, toolIds, timing = 'before-generation') {
|
|
94
|
+
return collectLegacyToolMigrations(projectPath, true, toolIds, timing);
|
|
95
|
+
}
|
|
96
|
+
function collectLegacyToolMigrations(projectPath, apply, toolIds, timing = 'before-generation') {
|
|
97
|
+
const migrations = [];
|
|
98
|
+
for (const tool of AI_TOOLS) {
|
|
99
|
+
if (!tool.skillsDir)
|
|
100
|
+
continue;
|
|
101
|
+
if (toolIds && !toolIds.includes(tool.value))
|
|
102
|
+
continue;
|
|
103
|
+
for (const legacy of LEGACY_TOOL_ROOTS[tool.value] ?? []) {
|
|
104
|
+
const legacyTiming = legacy.timing ?? 'before-generation';
|
|
105
|
+
if (legacyTiming !== timing)
|
|
106
|
+
continue;
|
|
107
|
+
if (legacy.root === tool.skillsDir)
|
|
108
|
+
continue;
|
|
109
|
+
// Without an explicit tool list, only moves that need no consent run.
|
|
110
|
+
if (apply && !toolIds && legacy.needsConsent)
|
|
111
|
+
continue;
|
|
112
|
+
const legacyRootPath = path.join(projectPath, legacy.root);
|
|
113
|
+
if (!fs.existsSync(legacyRootPath))
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
FileSystemUtils.assertProjectArtifactPath(projectPath, legacyRootPath);
|
|
117
|
+
FileSystemUtils.assertProjectArtifactPath(projectPath, path.join(projectPath, tool.skillsDir));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
console.warn(`Skipping legacy ${legacy.root}/ migration because the directory resolves outside this project.`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const skills = migrateSkillDirs(projectPath, tool.skillsDir, legacy.root, apply, legacyTiming === 'after-generation');
|
|
124
|
+
const commands = migrateCommandFiles(projectPath, tool, legacy.root, apply);
|
|
125
|
+
if (apply) {
|
|
126
|
+
removeDirIfEmpty(path.join(legacyRootPath, 'skills'));
|
|
127
|
+
removeDirIfEmpty(path.join(legacyRootPath, 'workflows'));
|
|
128
|
+
removeDirIfEmpty(legacyRootPath);
|
|
129
|
+
}
|
|
130
|
+
// Kept-only results are retained deliberately. When every legacy file
|
|
131
|
+
// differs from its counterpart nothing is movable, and dropping the
|
|
132
|
+
// record here would leave the user with two divergent copies and no
|
|
133
|
+
// word of it.
|
|
134
|
+
if (skills.moved > 0 || commands.moved > 0 || skills.kept > 0 || commands.kept > 0) {
|
|
135
|
+
migrations.push({
|
|
136
|
+
toolId: tool.value,
|
|
137
|
+
from: legacy.root,
|
|
138
|
+
to: tool.skillsDir,
|
|
139
|
+
skillDirs: skills.moved,
|
|
140
|
+
commandFiles: commands.moved,
|
|
141
|
+
keptInPlace: skills.kept + commands.kept,
|
|
142
|
+
needsConsent: legacy.needsConsent,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return migrations;
|
|
148
|
+
}
|
|
149
|
+
function migrateSkillDirs(projectPath, currentRoot, legacyRoot, apply, requireDestination = false) {
|
|
150
|
+
const legacySkillsDir = path.join(projectPath, legacyRoot, 'skills');
|
|
151
|
+
if (!fs.existsSync(legacySkillsDir))
|
|
152
|
+
return { moved: 0, kept: 0 };
|
|
153
|
+
const currentSkillsDir = path.join(projectPath, currentRoot, 'skills');
|
|
154
|
+
let moved = 0;
|
|
155
|
+
let kept = 0;
|
|
156
|
+
for (const workflowId of ALL_WORKFLOWS) {
|
|
157
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflowId];
|
|
158
|
+
const source = path.join(legacySkillsDir, dirName);
|
|
159
|
+
const sourceSkill = path.join(source, 'SKILL.md');
|
|
160
|
+
if (!fs.existsSync(sourceSkill))
|
|
161
|
+
continue;
|
|
162
|
+
const destination = path.join(currentSkillsDir, dirName);
|
|
163
|
+
const destinationSkill = path.join(destination, 'SKILL.md');
|
|
164
|
+
if (requireDestination && !fs.existsSync(destinationSkill))
|
|
165
|
+
continue;
|
|
166
|
+
if (!areProjectArtifacts(projectPath, sourceSkill, destinationSkill)) {
|
|
167
|
+
console.warn(`Skipping legacy ${legacyRoot}/skills/${dirName} migration because it resolves outside this project.`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const disposition = classifyManagedFile(sourceSkill, destinationSkill);
|
|
171
|
+
if (disposition === 'skip')
|
|
172
|
+
continue;
|
|
173
|
+
if (disposition === 'keep') {
|
|
174
|
+
kept++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!apply) {
|
|
178
|
+
moved++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
// Move the generated file, never the directory around it. A skill
|
|
183
|
+
// directory can also hold files the user wrote, and this destination is
|
|
184
|
+
// one OpenSpec deletes on its own — commands-only delivery and a
|
|
185
|
+
// deselected workflow both remove the whole skill directory. Carrying a
|
|
186
|
+
// user's file across would be handing it to that later removal.
|
|
187
|
+
if (disposition === 'drop') {
|
|
188
|
+
fs.rmSync(sourceSkill, { force: true });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
192
|
+
fs.renameSync(sourceSkill, destinationSkill);
|
|
193
|
+
}
|
|
194
|
+
// Anything the user left beside it stays under the legacy root.
|
|
195
|
+
removeDirIfEmpty(source);
|
|
196
|
+
moved++;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Leave the legacy directory in place if it cannot be moved
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { moved, kept };
|
|
203
|
+
}
|
|
204
|
+
function migrateCommandFiles(projectPath, tool, legacyRoot, apply) {
|
|
205
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
206
|
+
if (!adapter || !tool.skillsDir)
|
|
207
|
+
return { moved: 0, kept: 0 };
|
|
208
|
+
let moved = 0;
|
|
209
|
+
let kept = 0;
|
|
210
|
+
for (const commandId of COMMAND_IDS) {
|
|
211
|
+
const currentPath = adapter.getFilePath(commandId);
|
|
212
|
+
const legacyPath = legacyCommandPath(currentPath, tool.skillsDir, legacyRoot);
|
|
213
|
+
if (!legacyPath)
|
|
214
|
+
continue;
|
|
215
|
+
const source = path.join(projectPath, legacyPath);
|
|
216
|
+
if (!fs.existsSync(source))
|
|
217
|
+
continue;
|
|
218
|
+
const destination = path.join(projectPath, currentPath);
|
|
219
|
+
if (!areProjectArtifacts(projectPath, source, destination)) {
|
|
220
|
+
console.warn(`Skipping legacy ${legacyPath} migration because it resolves outside this project.`);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const disposition = classifyManagedFile(source, destination);
|
|
224
|
+
if (disposition === 'skip')
|
|
225
|
+
continue;
|
|
226
|
+
if (disposition === 'keep') {
|
|
227
|
+
kept++;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!apply) {
|
|
231
|
+
moved++;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
if (disposition === 'drop') {
|
|
236
|
+
fs.rmSync(source, { force: true });
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
240
|
+
fs.renameSync(source, destination);
|
|
241
|
+
}
|
|
242
|
+
moved++;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Leave the legacy file in place if it cannot be moved
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { moved, kept };
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Summarizes what a migration moved, e.g. "6 skills and 6 commands".
|
|
252
|
+
*/
|
|
253
|
+
export function describeLegacyMigration(migration) {
|
|
254
|
+
const parts = [];
|
|
255
|
+
if (migration.skillDirs > 0) {
|
|
256
|
+
parts.push(`${migration.skillDirs} skill${migration.skillDirs === 1 ? '' : 's'}`);
|
|
257
|
+
}
|
|
258
|
+
if (migration.commandFiles > 0) {
|
|
259
|
+
parts.push(`${migration.commandFiles} command${migration.commandFiles === 1 ? '' : 's'}`);
|
|
260
|
+
}
|
|
261
|
+
return parts.join(' and ');
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Names OpenSpec-managed files the move deliberately left behind, so a user
|
|
265
|
+
* who customized one knows there are now two copies to reconcile.
|
|
266
|
+
*/
|
|
267
|
+
export function keptInPlaceNotice(migration) {
|
|
268
|
+
if (migration.keptInPlace === 0)
|
|
269
|
+
return undefined;
|
|
270
|
+
const n = migration.keptInPlace;
|
|
271
|
+
// Deliberately does not claim the difference came from an edit: an older
|
|
272
|
+
// OpenSpec version's output differs too. Either way nothing was overwritten,
|
|
273
|
+
// and the user is the one who decides which copy to keep.
|
|
274
|
+
return (`Left ${n} file${n === 1 ? '' : 's'} in ${migration.from}/ that ` +
|
|
275
|
+
`differ${n === 1 ? 's' : ''} from the copy in ${migration.to}/. Nothing was ` +
|
|
276
|
+
`overwritten — compare the two and delete the ${migration.from}/ copy once ` +
|
|
277
|
+
`you have kept anything you customized.`);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Whether a migration has anything to move, as opposed to only files left in
|
|
281
|
+
* place. Callers use this to avoid offering a move of nothing.
|
|
282
|
+
*/
|
|
283
|
+
export function hasMovableContent(migration) {
|
|
284
|
+
return migration.skillDirs > 0 || migration.commandFiles > 0;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Explains why a consent-gated move is being offered, in the user's terms.
|
|
288
|
+
* Keyed by tool so the reason is specific rather than a generic "files moved".
|
|
289
|
+
*/
|
|
290
|
+
export function legacyMigrationNotice(migration) {
|
|
291
|
+
if (migration.toolId === 'devin') {
|
|
292
|
+
return (`Windsurf is now Devin Desktop, and its config directory moved from ` +
|
|
293
|
+
`${migration.from}/ to ${migration.to}/. Devin Desktop reads ${migration.from}/ ` +
|
|
294
|
+
`only as a fallback, and Devin Local does not read it at all.`);
|
|
295
|
+
}
|
|
296
|
+
return `${migration.from}/ is the former location for this tool; ${migration.to}/ is current.`;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Whether two paths are the same file on disk once symlinks are resolved.
|
|
300
|
+
*
|
|
301
|
+
* Symlinking one tool root at the other is a realistic way to straddle a
|
|
302
|
+
* rebrand (`ln -s .devin .windsurf` to keep an older build working). Without
|
|
303
|
+
* this check the "destination already exists, drop the legacy copy" branch
|
|
304
|
+
* deletes the destination itself, taking the only copy with it.
|
|
305
|
+
*/
|
|
306
|
+
function isSamePath(a, b) {
|
|
307
|
+
try {
|
|
308
|
+
return fs.realpathSync(a) === fs.realpathSync(b);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function areProjectArtifacts(projectPath, ...artifactPaths) {
|
|
315
|
+
try {
|
|
316
|
+
for (const artifactPath of artifactPaths) {
|
|
317
|
+
FileSystemUtils.assertProjectArtifactPath(projectPath, artifactPath);
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function removeDirIfEmpty(dirPath) {
|
|
326
|
+
try {
|
|
327
|
+
if (fs.readdirSync(dirPath).length === 0) {
|
|
328
|
+
fs.rmdirSync(dirPath);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Missing or non-empty directory — nothing to do
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function scanInstalledWorkflowArtifacts(projectPath, tools, includeLegacySkills = false) {
|
|
14
336
|
const installed = new Set();
|
|
15
337
|
let hasSkills = false;
|
|
16
338
|
let hasCommands = false;
|
|
17
339
|
for (const tool of tools) {
|
|
18
|
-
if (!tool
|
|
340
|
+
if (!toolSupportsSkills(tool))
|
|
19
341
|
continue;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
342
|
+
const skillsDirs = [];
|
|
343
|
+
if (tool.globalSkillsDir) {
|
|
344
|
+
skillsDirs.push(resolveToolSkillsDir(projectPath, tool));
|
|
345
|
+
}
|
|
346
|
+
else if (isSharedSkillTargetActive(projectPath, tool.value)) {
|
|
347
|
+
skillsDirs.push(resolveToolSkillsDir(projectPath, tool));
|
|
348
|
+
if (includeLegacySkills) {
|
|
349
|
+
skillsDirs.push(...(tool.legacySkillsDirs ?? []).map((root) => path.join(projectPath, root, 'skills')));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const skillsDir of skillsDirs) {
|
|
353
|
+
for (const workflowId of ALL_WORKFLOWS) {
|
|
354
|
+
const skillDirName = WORKFLOW_TO_SKILL_DIR[workflowId];
|
|
355
|
+
const skillFile = path.join(skillsDir, skillDirName, 'SKILL.md');
|
|
356
|
+
if (fs.existsSync(skillFile)) {
|
|
357
|
+
installed.add(workflowId);
|
|
358
|
+
hasSkills = true;
|
|
359
|
+
}
|
|
27
360
|
}
|
|
28
361
|
}
|
|
29
362
|
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
@@ -89,7 +422,7 @@ export function migrateIfNeeded(projectPath, tools) {
|
|
|
89
422
|
return;
|
|
90
423
|
}
|
|
91
424
|
// Scan for installed workflows
|
|
92
|
-
const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools);
|
|
425
|
+
const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools, true);
|
|
93
426
|
const installedWorkflows = artifacts.workflows;
|
|
94
427
|
if (installedWorkflows.length === 0) {
|
|
95
428
|
// No workflows installed, new user — defaults will apply
|
|
@@ -103,6 +436,22 @@ export function migrateIfNeeded(projectPath, tools) {
|
|
|
103
436
|
}
|
|
104
437
|
saveGlobalConfig(config);
|
|
105
438
|
console.log(`Migrated: custom profile with ${installedWorkflows.length} workflows`);
|
|
106
|
-
|
|
439
|
+
// Each detected tool resolves to a propose reference for its surface: the
|
|
440
|
+
// command name its generated files answer to when commands will exist for it
|
|
441
|
+
// under the effective delivery (/opsx:propose when namespaced under opsx/,
|
|
442
|
+
// /opsx-propose when the filename is the command), its documented skill
|
|
443
|
+
// invocation otherwise. When the tools disagree — including command tools
|
|
444
|
+
// mixed with skill-only tools — stay syntax-neutral rather than advertise a
|
|
445
|
+
// form that is wrong for one of them.
|
|
446
|
+
const effectiveDelivery = config.delivery ?? 'both';
|
|
447
|
+
const proposeReferences = new Set(tools.map((tool) => {
|
|
448
|
+
if (shouldGenerateCommandsForTool(tool.value, effectiveDelivery)) {
|
|
449
|
+
const transformer = getTransformerForTool(tool.value, effectiveDelivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
|
|
450
|
+
return transformer ? transformer('/opsx:propose') : '/opsx:propose';
|
|
451
|
+
}
|
|
452
|
+
return getSkillReferenceTransformer(tool.value)('/opsx:propose');
|
|
453
|
+
}));
|
|
454
|
+
const proposeReference = proposeReferences.size === 1 ? [...proposeReferences][0] : 'the openspec-propose skill';
|
|
455
|
+
console.log(`New in this version: ${proposeReference}. Try 'openspec config profile core' for the streamlined experience.`);
|
|
107
456
|
}
|
|
108
457
|
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ConflictInfo {
|
|
2
|
+
artifactId: string;
|
|
3
|
+
affectedSections: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Detects conflicts where manual edits would be overwritten by propagation.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ConflictDetector {
|
|
9
|
+
private changeRoot;
|
|
10
|
+
constructor(changeRoot: string);
|
|
11
|
+
/**
|
|
12
|
+
* Detects conflicts for a specific artifact.
|
|
13
|
+
* Returns conflict info if conflicts exist, null otherwise.
|
|
14
|
+
*/
|
|
15
|
+
detectConflicts(artifactId: string, artifactPath: string, upstreamContent: string): Promise<ConflictInfo | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Heuristic to detect if content has manual edits.
|
|
18
|
+
* In a full implementation, this would use git diff or similar.
|
|
19
|
+
*/
|
|
20
|
+
private hasManualEdits;
|
|
21
|
+
/**
|
|
22
|
+
* Detects conflicts for multiple artifacts.
|
|
23
|
+
*/
|
|
24
|
+
detectAllConflicts(artifactPaths: Map<string, string>, upstreamContents: Map<string, string>): Promise<ConflictInfo[]>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=conflict-detector.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
/**
|
|
3
|
+
* Detects conflicts where manual edits would be overwritten by propagation.
|
|
4
|
+
*/
|
|
5
|
+
export class ConflictDetector {
|
|
6
|
+
changeRoot;
|
|
7
|
+
constructor(changeRoot) {
|
|
8
|
+
this.changeRoot = changeRoot;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Detects conflicts for a specific artifact.
|
|
12
|
+
* Returns conflict info if conflicts exist, null otherwise.
|
|
13
|
+
*/
|
|
14
|
+
async detectConflicts(artifactId, artifactPath, upstreamContent) {
|
|
15
|
+
try {
|
|
16
|
+
const currentContent = await readFile(artifactPath, 'utf-8');
|
|
17
|
+
// In a full implementation, this would:
|
|
18
|
+
// 1. Parse both current and upstream content
|
|
19
|
+
// 2. Identify sections that would be overwritten
|
|
20
|
+
// 3. Check if manual edits exist in those sections
|
|
21
|
+
// For now, simplified approach: if content differs and has manual edits, flag as conflict
|
|
22
|
+
if (currentContent !== upstreamContent && this.hasManualEdits(currentContent)) {
|
|
23
|
+
return {
|
|
24
|
+
artifactId,
|
|
25
|
+
affectedSections: ['entire file'], // Simplified: flag entire file as conflict
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// If file doesn't exist, no conflict
|
|
32
|
+
if (error.code === 'ENOENT') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Heuristic to detect if content has manual edits.
|
|
40
|
+
* In a full implementation, this would use git diff or similar.
|
|
41
|
+
*/
|
|
42
|
+
hasManualEdits(content) {
|
|
43
|
+
// Simplified heuristic: check for markers of manual editing
|
|
44
|
+
// In a real implementation, this would compare with template-generated content
|
|
45
|
+
return content.length > 0; // Placeholder: assume non-empty content has manual edits
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Detects conflicts for multiple artifacts.
|
|
49
|
+
*/
|
|
50
|
+
async detectAllConflicts(artifactPaths, upstreamContents) {
|
|
51
|
+
const conflicts = [];
|
|
52
|
+
for (const [artifactId, artifactPath] of artifactPaths.entries()) {
|
|
53
|
+
const upstreamContent = upstreamContents.get(artifactId) || '';
|
|
54
|
+
const conflict = await this.detectConflicts(artifactId, artifactPath, upstreamContent);
|
|
55
|
+
if (conflict) {
|
|
56
|
+
conflicts.push(conflict);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return conflicts;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=conflict-detector.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ArtifactGraph } from '../artifact-graph/graph.js';
|
|
2
|
+
export interface DependencyPropagationResult {
|
|
3
|
+
artifactId: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
modificationScope: 'direct' | 'propagated';
|
|
6
|
+
}
|
|
7
|
+
export interface ConflictInfo {
|
|
8
|
+
artifactId: string;
|
|
9
|
+
affectedSections: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Propagates changes from modified artifacts to their dependents.
|
|
13
|
+
* Uses diff-based merge to preserve non-conflicting manual edits.
|
|
14
|
+
*/
|
|
15
|
+
export declare class DependencyPropagator {
|
|
16
|
+
private graph;
|
|
17
|
+
private changeRoot;
|
|
18
|
+
constructor(graph: ArtifactGraph, changeRoot: string);
|
|
19
|
+
/**
|
|
20
|
+
* Calculates all artifacts that need to be updated given the modified artifacts.
|
|
21
|
+
* Returns artifact IDs in update order (deepest dependents first).
|
|
22
|
+
*/
|
|
23
|
+
calculateUpdateScope(modifiedArtifacts: string[]): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Performs diff-based merge to preserve non-conflicting manual edits.
|
|
26
|
+
* Returns the merged content.
|
|
27
|
+
*/
|
|
28
|
+
mergeContent(currentContent: string, upstreamContent: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Propagates changes to a single artifact.
|
|
31
|
+
*/
|
|
32
|
+
propagateToArtifact(artifactId: string, artifactPath: string, upstreamChanges: Map<string, string>): Promise<DependencyPropagationResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Propagates changes to all affected artifacts.
|
|
35
|
+
*/
|
|
36
|
+
propagateChanges(modifiedArtifacts: string[], artifactPaths: Map<string, string>): Promise<DependencyPropagationResult[]>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=dependency-propagator.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
/**
|
|
3
|
+
* Propagates changes from modified artifacts to their dependents.
|
|
4
|
+
* Uses diff-based merge to preserve non-conflicting manual edits.
|
|
5
|
+
*/
|
|
6
|
+
export class DependencyPropagator {
|
|
7
|
+
graph;
|
|
8
|
+
changeRoot;
|
|
9
|
+
constructor(graph, changeRoot) {
|
|
10
|
+
this.graph = graph;
|
|
11
|
+
this.changeRoot = changeRoot;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Calculates all artifacts that need to be updated given the modified artifacts.
|
|
15
|
+
* Returns artifact IDs in update order (deepest dependents first).
|
|
16
|
+
*/
|
|
17
|
+
calculateUpdateScope(modifiedArtifacts) {
|
|
18
|
+
const toUpdate = new Set();
|
|
19
|
+
for (const artifactId of modifiedArtifacts) {
|
|
20
|
+
try {
|
|
21
|
+
const dependents = this.graph.getDependents(artifactId);
|
|
22
|
+
for (const dep of dependents) {
|
|
23
|
+
toUpdate.add(dep);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
// If getDependents throws (e.g., circular dependency), re-throw
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Add the modified artifacts themselves
|
|
32
|
+
for (const artifactId of modifiedArtifacts) {
|
|
33
|
+
toUpdate.add(artifactId);
|
|
34
|
+
}
|
|
35
|
+
// Sort by reverse build order (deepest first)
|
|
36
|
+
const buildOrder = this.graph.getBuildOrder();
|
|
37
|
+
const updateOrder = Array.from(toUpdate).sort((a, b) => {
|
|
38
|
+
const posA = buildOrder.indexOf(a);
|
|
39
|
+
const posB = buildOrder.indexOf(b);
|
|
40
|
+
return posB - posA;
|
|
41
|
+
});
|
|
42
|
+
return updateOrder;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Performs diff-based merge to preserve non-conflicting manual edits.
|
|
46
|
+
* Returns the merged content.
|
|
47
|
+
*/
|
|
48
|
+
async mergeContent(currentContent, upstreamContent) {
|
|
49
|
+
// Simple line-by-line diff-based merge
|
|
50
|
+
const currentLines = currentContent.split('\n');
|
|
51
|
+
const upstreamLines = upstreamContent.split('\n');
|
|
52
|
+
// For now, return upstream content (simplified approach)
|
|
53
|
+
// In a full implementation, this would do a proper 3-way merge
|
|
54
|
+
// preserving manual edits that don't conflict with upstream changes
|
|
55
|
+
return upstreamContent;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Propagates changes to a single artifact.
|
|
59
|
+
*/
|
|
60
|
+
async propagateToArtifact(artifactId, artifactPath, upstreamChanges) {
|
|
61
|
+
// Read current content
|
|
62
|
+
const currentContent = await readFile(artifactPath, 'utf-8');
|
|
63
|
+
// In a full implementation, this would:
|
|
64
|
+
// 1. Load the template for the artifact
|
|
65
|
+
// 2. Regenerate with upstream changes as context
|
|
66
|
+
// 3. Merge with current content using diff-based merge
|
|
67
|
+
// For now, we'll return a placeholder result
|
|
68
|
+
return {
|
|
69
|
+
artifactId,
|
|
70
|
+
filePath: artifactPath,
|
|
71
|
+
modificationScope: 'propagated',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Propagates changes to all affected artifacts.
|
|
76
|
+
*/
|
|
77
|
+
async propagateChanges(modifiedArtifacts, artifactPaths) {
|
|
78
|
+
const updateOrder = this.calculateUpdateScope(modifiedArtifacts);
|
|
79
|
+
const results = [];
|
|
80
|
+
for (const artifactId of updateOrder) {
|
|
81
|
+
const artifactPath = artifactPaths.get(artifactId);
|
|
82
|
+
if (!artifactPath) {
|
|
83
|
+
throw new Error(`No path found for artifact: ${artifactId}`);
|
|
84
|
+
}
|
|
85
|
+
if (modifiedArtifacts.includes(artifactId)) {
|
|
86
|
+
// Direct modification
|
|
87
|
+
results.push({
|
|
88
|
+
artifactId,
|
|
89
|
+
filePath: artifactPath,
|
|
90
|
+
modificationScope: 'direct',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Propagated change
|
|
95
|
+
const result = await this.propagateToArtifact(artifactId, artifactPath, new Map() // upstream changes would be passed here
|
|
96
|
+
);
|
|
97
|
+
results.push(result);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=dependency-propagator.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ModificationRecord {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
modifiedArtifacts: string[];
|
|
4
|
+
intent: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ChangeMarker {
|
|
7
|
+
modifyHistory: ModificationRecord[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Tracks modification history in the change marker (.openspec.yaml).
|
|
11
|
+
*/
|
|
12
|
+
export declare class HistoryTracker {
|
|
13
|
+
private changeRoot;
|
|
14
|
+
constructor(changeRoot: string);
|
|
15
|
+
/**
|
|
16
|
+
* Records a modification event in the change marker.
|
|
17
|
+
*/
|
|
18
|
+
recordModification(changeName: string, modifiedArtifacts: string[], intent: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Gets the modification history for a change.
|
|
21
|
+
*/
|
|
22
|
+
getHistory(changeName: string): Promise<ModificationRecord[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Parses the change marker YAML content.
|
|
25
|
+
* In a full implementation, this would use a proper YAML parser.
|
|
26
|
+
*/
|
|
27
|
+
private parseMarker;
|
|
28
|
+
/**
|
|
29
|
+
* Stringifies the change marker to YAML format.
|
|
30
|
+
* In a full implementation, this would use a proper YAML stringifier.
|
|
31
|
+
*/
|
|
32
|
+
private stringifyMarker;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=history-tracker.d.ts.map
|