@fission-ai/openspec 1.7.0 → 1.9.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.
Files changed (113) hide show
  1. package/README.md +3 -1
  2. package/dist/cli/index.d.ts +15 -0
  3. package/dist/cli/index.js +39 -5
  4. package/dist/commands/change.js +16 -1
  5. package/dist/commands/config.d.ts +5 -0
  6. package/dist/commands/config.js +5 -1
  7. package/dist/commands/schema.js +281 -47
  8. package/dist/commands/spec.js +30 -6
  9. package/dist/commands/validate.d.ts +23 -0
  10. package/dist/commands/validate.js +148 -4
  11. package/dist/commands/workflow/instructions.js +32 -27
  12. package/dist/commands/workflow/schemas.d.ts +2 -0
  13. package/dist/commands/workflow/schemas.js +9 -2
  14. package/dist/commands/workflow/status.js +2 -2
  15. package/dist/commands/workflow/templates.js +15 -5
  16. package/dist/core/archive.d.ts +14 -0
  17. package/dist/core/archive.js +1272 -151
  18. package/dist/core/artifact-graph/index.d.ts +1 -1
  19. package/dist/core/artifact-graph/index.js +1 -1
  20. package/dist/core/artifact-graph/instruction-loader.d.ts +3 -1
  21. package/dist/core/artifact-graph/instruction-loader.js +15 -5
  22. package/dist/core/artifact-graph/outputs.d.ts +1 -0
  23. package/dist/core/artifact-graph/outputs.js +67 -5
  24. package/dist/core/artifact-graph/resolver.d.ts +0 -12
  25. package/dist/core/artifact-graph/resolver.js +49 -9
  26. package/dist/core/artifact-graph/types.js +21 -3
  27. package/dist/core/available-tools.d.ts +2 -2
  28. package/dist/core/available-tools.js +14 -3
  29. package/dist/core/change-metadata/schema.d.ts +1 -0
  30. package/dist/core/change-metadata/schema.js +7 -0
  31. package/dist/core/change-status-policy.js +2 -2
  32. package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
  33. package/dist/core/command-generation/adapters/command-code.js +34 -0
  34. package/dist/core/command-generation/adapters/index.d.ts +1 -0
  35. package/dist/core/command-generation/adapters/index.js +1 -0
  36. package/dist/core/command-generation/registry.js +2 -0
  37. package/dist/core/completions/command-registry.js +13 -0
  38. package/dist/core/config-schema.d.ts +3 -0
  39. package/dist/core/config-schema.js +28 -1
  40. package/dist/core/config.d.ts +4 -0
  41. package/dist/core/config.js +40 -17
  42. package/dist/core/file-state.js +56 -24
  43. package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
  44. package/dist/core/github-copilot/cloud-agent.js +497 -0
  45. package/dist/core/global-config.d.ts +11 -0
  46. package/dist/core/init.d.ts +23 -0
  47. package/dist/core/init.js +253 -46
  48. package/dist/core/legacy-cleanup.d.ts +10 -0
  49. package/dist/core/legacy-cleanup.js +52 -28
  50. package/dist/core/migration.d.ts +6 -4
  51. package/dist/core/migration.js +78 -22
  52. package/dist/core/parsers/requirement-blocks.d.ts +10 -0
  53. package/dist/core/parsers/requirement-blocks.js +93 -1
  54. package/dist/core/parsers/requirement-text.d.ts +8 -0
  55. package/dist/core/parsers/requirement-text.js +4 -3
  56. package/dist/core/parsers/spec-structure.d.ts +1 -1
  57. package/dist/core/parsers/spec-structure.js +17 -1
  58. package/dist/core/profile-sync-drift.js +40 -5
  59. package/dist/core/project-config.d.ts +3 -0
  60. package/dist/core/project-config.js +31 -1
  61. package/dist/core/root-selection.d.ts +1 -1
  62. package/dist/core/shared/index.d.ts +1 -0
  63. package/dist/core/shared/index.js +1 -0
  64. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  65. package/dist/core/shared/skill-content-equivalence.js +41 -0
  66. package/dist/core/shared/skill-paths.d.ts +13 -0
  67. package/dist/core/shared/skill-paths.js +23 -0
  68. package/dist/core/shared/tool-detection.js +66 -35
  69. package/dist/core/shared-skill-target.d.ts +31 -0
  70. package/dist/core/shared-skill-target.js +186 -0
  71. package/dist/core/specs-apply.d.ts +72 -0
  72. package/dist/core/specs-apply.js +459 -59
  73. package/dist/core/templates/skill-templates.d.ts +1 -1
  74. package/dist/core/templates/skill-templates.js +1 -1
  75. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  76. package/dist/core/templates/workflows/apply-change.js +25 -182
  77. package/dist/core/templates/workflows/archive-change.js +8 -4
  78. package/dist/core/templates/workflows/bulk-archive-change.js +30 -26
  79. package/dist/core/templates/workflows/continue-change.js +6 -6
  80. package/dist/core/templates/workflows/explore.js +42 -18
  81. package/dist/core/templates/workflows/onboard.js +9 -3
  82. package/dist/core/templates/workflows/propose.js +72 -20
  83. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  84. package/dist/core/templates/workflows/store-selection.js +1 -1
  85. package/dist/core/templates/workflows/sync-specs.js +96 -18
  86. package/dist/core/templates/workflows/update-change.js +10 -8
  87. package/dist/core/update.d.ts +1 -0
  88. package/dist/core/update.js +136 -30
  89. package/dist/core/validation/task-numbering.d.ts +16 -0
  90. package/dist/core/validation/task-numbering.js +57 -0
  91. package/dist/core/validation/validator.d.ts +26 -3
  92. package/dist/core/validation/validator.js +189 -19
  93. package/dist/core/version-check.js +6 -11
  94. package/dist/telemetry/config.d.ts +3 -4
  95. package/dist/telemetry/index.d.ts +12 -5
  96. package/dist/telemetry/index.js +27 -9
  97. package/dist/utils/change-metadata.d.ts +17 -4
  98. package/dist/utils/change-metadata.js +26 -4
  99. package/dist/utils/ci.d.ts +12 -0
  100. package/dist/utils/ci.js +16 -0
  101. package/dist/utils/command-references.d.ts +14 -2
  102. package/dist/utils/command-references.js +45 -3
  103. package/dist/utils/file-system.d.ts +10 -0
  104. package/dist/utils/file-system.js +70 -0
  105. package/dist/utils/interactive.d.ts +52 -0
  106. package/dist/utils/interactive.js +135 -0
  107. package/dist/utils/spec-discovery.d.ts +4 -4
  108. package/dist/utils/spec-discovery.js +19 -6
  109. package/dist/utils/task-progress.d.ts +51 -1
  110. package/dist/utils/task-progress.js +93 -41
  111. package/package.json +1 -1
  112. package/schemas/spec-driven/schema.yaml +8 -6
  113. package/schemas/spec-driven/templates/proposal.md +6 -4
@@ -1,44 +1,67 @@
1
1
  export const OPENSPEC_DIR_NAME = 'openspec';
2
+ export const OPENSPEC_SKILL_NAMES = [
3
+ 'openspec-explore',
4
+ 'openspec-new-change',
5
+ 'openspec-continue-change',
6
+ 'openspec-apply-change',
7
+ 'openspec-update-change',
8
+ 'openspec-ff-change',
9
+ 'openspec-sync-specs',
10
+ 'openspec-archive-change',
11
+ 'openspec-bulk-archive-change',
12
+ 'openspec-verify-change',
13
+ 'openspec-onboard',
14
+ 'openspec-propose',
15
+ ];
2
16
  export const OPENSPEC_MARKERS = {
3
17
  start: '<!-- OPENSPEC:START -->',
4
18
  end: '<!-- OPENSPEC:END -->'
5
19
  };
6
20
  export const AI_TOOLS = [
7
- { name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq' },
8
- { name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent' },
21
+ { name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq', requiresIdeRestart: true },
22
+ { name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent', requiresIdeRestart: true },
9
23
  { name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' },
10
24
  { name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' },
11
25
  { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
12
- { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' },
26
+ { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline', requiresIdeRestart: true },
27
+ { name: 'Command Code', value: 'command-code', available: true, successLabel: 'Command Code', skillsDir: '.commandcode' },
13
28
  { name: 'CodeArts', value: 'codeartsagent', available: true, successLabel: 'CodeArts', skillsDir: '.codeartsdoer' },
14
- { name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.codex' },
15
- { name: 'Devin Desktop (formerly Windsurf)', value: 'devin', available: true, successLabel: 'Devin Desktop', skillsDir: '.devin', detectionPaths: ['.devin', '.windsurf'] },
29
+ { name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.agents', legacySkillsDirs: ['.codex'], detectionPaths: ['.agents/skills', '.codex/skills'] },
30
+ { name: 'Devin Desktop (formerly Windsurf)', value: 'devin', available: true, successLabel: 'Devin Desktop', skillsDir: '.devin', detectionPaths: ['.devin', '.windsurf'], requiresIdeRestart: true },
16
31
  { name: 'ForgeCode', value: 'forgecode', available: true, successLabel: 'ForgeCode', skillsDir: '.forge' },
17
32
  { name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code', skillsDir: '.codebuddy' },
18
- { name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue' },
19
- { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec' },
33
+ { name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue', requiresIdeRestart: true },
34
+ { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec', requiresIdeRestart: true },
20
35
  { name: 'Crush', value: 'crush', available: true, successLabel: 'Crush', skillsDir: '.crush' },
21
- { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor' },
36
+ { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor', requiresIdeRestart: true },
22
37
  { name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid', skillsDir: '.factory' },
23
38
  { name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI', skillsDir: '.gemini' },
24
- { name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot', skillsDir: '.github', detectionPaths: ['.github/copilot-instructions.md', '.github/instructions', '.github/workflows/copilot-setup-steps.yml', '.github/prompts', '.github/agents', '.github/skills', '.github/.mcp.json'] },
39
+ { name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot', skillsDir: '.github', detectionPaths: ['.github/copilot-instructions.md', '.github/instructions', '.github/workflows/copilot-setup-steps.yml', '.github/prompts', '.github/agents', '.github/skills', '.github/.mcp.json'], requiresIdeRestart: true },
25
40
  { name: 'Hermes Agent', value: 'hermes', available: true, successLabel: 'Hermes Agent', skillsDir: '.hermes', detectionPaths: ['.hermes', 'HERMES.md', '.hermes.md'], setupNote: "Hermes only loads skills from ~/.hermes/skills by default. Add this project's .hermes/skills directory to skills.external_dirs in ~/.hermes/config.yaml so Hermes picks up the generated OpenSpec skills." },
26
41
  { name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
27
- { name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie' },
28
- { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
42
+ { name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie', requiresIdeRestart: true },
43
+ { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode', requiresIdeRestart: true },
29
44
  { name: 'Kimi Code', value: 'kimi', available: true, successLabel: 'Kimi Code', skillsDir: '.kimi-code', detectionPaths: ['.kimi-code', '.kimi'] },
30
- { name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
31
- { name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
45
+ { name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro', requiresIdeRestart: true },
46
+ { name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma', requiresIdeRestart: true },
47
+ { name: 'MiniMax Code', value: 'minimax-code', available: true, successLabel: 'MiniMax Code', globalSkillsDir: '.minimax' },
32
48
  { name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
33
49
  { name: 'Oh My Pi', value: 'oh-my-pi', available: true, successLabel: 'Oh My Pi', skillsDir: '.omp' },
34
50
  { name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
35
51
  { name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
36
- { name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
52
+ { name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder', requiresIdeRestart: true },
37
53
  { name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
38
- { name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo' },
39
- { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' },
54
+ { name: 'Rovo Dev CLI', value: 'rovodev', available: true, successLabel: 'Rovo Dev CLI', skillsDir: '.rovodev', detectionPaths: ['.rovodev/skills', '.rovodev'] },
55
+ { name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo', requiresIdeRestart: true },
56
+ { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae', requiresIdeRestart: true },
40
57
  { name: 'ZCode', value: 'zcode', available: true, successLabel: 'ZCode', skillsDir: '.zcode' },
41
- { name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
58
+ // Vendor-neutral target for assistants that read the shared `.agents` root.
59
+ // Detection keys off `.agents/skills` rather than the bare root: frameworks use
60
+ // `.agents/` for more than skills, so the root alone says nothing about skills.
61
+ // A project that does keep skills there is a project this target fits, the same
62
+ // way `.claude/` selects Claude Code — the signal is the user's setup, not
63
+ // OpenSpec's own files.
64
+ { name: 'Shared .agents skills', value: 'agents', available: true, successLabel: 'shared .agents skills', skillsDir: '.agents', detectionPaths: ['.agents/skills'] }
42
65
  ];
43
66
  /**
44
67
  * Retired tool ids that still resolve, so a rebrand does not break scripted
@@ -1,5 +1,6 @@
1
1
  import * as nodeFs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
3
4
  import { FileSystemUtils } from '../utils/file-system.js';
4
5
  import { StoreError } from './store/errors.js';
5
6
  const fs = nodeFs.promises;
@@ -19,9 +20,15 @@ export function makeLockErrorFactory(data) {
19
20
  });
20
21
  };
21
22
  }
22
- const STALE_LOCK_THRESHOLD_MS = 30_000;
23
23
  const LOCK_DEADLINE_MS = 5000;
24
24
  const LOCK_POLL_MS = 25;
25
+ const PRIVATE_FILE_MODE = 0o600;
26
+ const lockOwnership = new WeakMap();
27
+ function isUnsupportedSyncError(error) {
28
+ return (isNodeErrorCode(error, 'EINVAL') ||
29
+ isNodeErrorCode(error, 'ENOTSUP') ||
30
+ isNodeErrorCode(error, 'ENOSYS'));
31
+ }
25
32
  export function isNodeErrorCode(error, code) {
26
33
  return (typeof error === 'object' &&
27
34
  error !== null &&
@@ -56,7 +63,10 @@ export async function writeFileAtomically(filePath, content) {
56
63
  await FileSystemUtils.createDirectory(dirPath);
57
64
  const tempPath = path.join(dirPath, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
58
65
  try {
59
- await fs.writeFile(tempPath, content, 'utf-8');
66
+ await fs.writeFile(tempPath, content, {
67
+ encoding: 'utf-8',
68
+ mode: PRIVATE_FILE_MODE,
69
+ });
60
70
  await fs.rename(tempPath, filePath);
61
71
  }
62
72
  catch (error) {
@@ -74,39 +84,61 @@ export async function acquireFileLock(options) {
74
84
  const deadline = Date.now() + LOCK_DEADLINE_MS;
75
85
  while (true) {
76
86
  try {
77
- return await fs.open(lockPath, 'wx');
87
+ const lock = await fs.open(lockPath, 'wx', PRIVATE_FILE_MODE);
88
+ const ownershipToken = `${process.pid}:${randomUUID()}`;
89
+ try {
90
+ await lock.writeFile(ownershipToken, 'utf-8');
91
+ try {
92
+ await lock.sync();
93
+ }
94
+ catch (error) {
95
+ // Some FUSE and network filesystems support exclusive lock files but
96
+ // explicitly do not implement fsync. The token is still visible to
97
+ // cooperating processes, so do not make those projects unusable.
98
+ if (!isUnsupportedSyncError(error)) {
99
+ throw error;
100
+ }
101
+ }
102
+ }
103
+ catch (error) {
104
+ await lock.close().catch(() => undefined);
105
+ await fs.rm(lockPath, { force: true }).catch(() => undefined);
106
+ throw error;
107
+ }
108
+ lockOwnership.set(lock, ownershipToken);
109
+ return lock;
78
110
  }
79
111
  catch (error) {
80
112
  if (!isNodeErrorCode(error, 'EEXIST')) {
81
113
  // A permission or filesystem problem, not contention - say so.
82
114
  throw errorFor('create-failed', { lockPath, cause: error });
83
115
  }
84
- // A crashed process leaves the lock behind forever; state-file
85
- // writes are sub-second, so an old lock is an orphan - steal it.
86
- let staleStolen = false;
87
- try {
88
- const lockStat = await fs.stat(lockPath);
89
- if (Date.now() - lockStat.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
90
- await fs.rm(lockPath, { force: true });
91
- staleStolen = true;
92
- }
93
- }
94
- catch {
95
- // The holder released between open and stat - retry, but stay
96
- // bounded: a persistently failing stat (EPERM, delete-pending)
97
- // must hit the deadline instead of spinning forever.
98
- }
99
- if (!staleStolen) {
100
- if (Date.now() >= deadline) {
101
- throw errorFor('timeout', { lockPath });
102
- }
103
- await sleep(LOCK_POLL_MS);
116
+ // Never steal by age: unlinking a supposedly stale path can race with
117
+ // its replacement and erase a live owner's lock. The timeout diagnostic
118
+ // gives the user an explicit recovery path for genuinely orphaned locks.
119
+ if (Date.now() >= deadline) {
120
+ throw errorFor('timeout', { lockPath });
104
121
  }
122
+ await sleep(LOCK_POLL_MS);
105
123
  }
106
124
  }
107
125
  }
108
126
  export async function releaseFileLock(lock, lockPath) {
127
+ const ownershipToken = lockOwnership.get(lock);
128
+ lockOwnership.delete(lock);
109
129
  await lock.close().catch(() => undefined);
110
- await fs.rm(lockPath, { force: true }).catch(() => undefined);
130
+ if (ownershipToken === undefined) {
131
+ return;
132
+ }
133
+ try {
134
+ const currentToken = await fs.readFile(lockPath, 'utf-8');
135
+ if (currentToken === ownershipToken) {
136
+ await fs.rm(lockPath, { force: true });
137
+ }
138
+ }
139
+ catch {
140
+ // The lock was already removed or replaced with an unreadable path.
141
+ // In either case, this owner must not remove anything else.
142
+ }
111
143
  }
112
144
  //# sourceMappingURL=file-state.js.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * GitHub Copilot Cloud Agent Support
3
+ *
4
+ * Generates copilot-setup-steps.yml and .github/agents/openspec.agent.md
5
+ * when the github-copilot tool is selected during init/update.
6
+ * These files enable the GitHub Copilot coding agent (cloud) to use the
7
+ * OpenSpec CLI in its ephemeral dev environment.
8
+ */
9
+ /**
10
+ * Check if a tool list includes github-copilot.
11
+ */
12
+ export declare function includesGitHubCopilot(toolIds: string[]): boolean;
13
+ /**
14
+ * Generate the copilot-setup-steps.yml workflow file content.
15
+ * This workflow pre-installs the OpenSpec CLI in the Copilot coding agent's
16
+ * ephemeral GitHub Actions environment.
17
+ */
18
+ export declare function generateCopilotSetupSteps(): string;
19
+ /**
20
+ * Generate the .github/agents/openspec.agent.md custom agent file content.
21
+ * This tells the GitHub Copilot coding agent how to use the OpenSpec CLI.
22
+ */
23
+ export declare function generateCopilotAgentFile(): string;
24
+ /**
25
+ * File paths (relative to project root) for the generated files.
26
+ */
27
+ export declare const COPILOT_CLOUD_FILES: {
28
+ readonly setupSteps: string;
29
+ readonly agent: string;
30
+ };
31
+ /**
32
+ * Reconcile Copilot cloud agent files in the project directory.
33
+ * Creates missing files and refreshes recognized legacy generated files while
34
+ * preserving current generated content and user customizations.
35
+ *
36
+ * @returns Object indicating which files were written.
37
+ */
38
+ export declare function writeCopilotCloudFiles(projectPath: string): Promise<{
39
+ setupStepsWritten: boolean;
40
+ agentWritten: boolean;
41
+ }>;
42
+ /**
43
+ * Remove copilot cloud agent files from the project directory.
44
+ * Used when github-copilot is deselected.
45
+ *
46
+ * @returns Number of files removed.
47
+ */
48
+ export declare function removeCopilotCloudFiles(projectPath: string): Promise<number>;
49
+ /**
50
+ * Read the persisted opt-in for Copilot cloud-file generation.
51
+ *
52
+ * Tri-state: `true` (opted in), `false` (explicitly opted out), or `undefined`
53
+ * (never decided). A malformed value is treated as undecided rather than an
54
+ * error, matching how {@link readProjectConfig} degrades on bad fields.
55
+ */
56
+ export declare function readCopilotCloudOptIn(projectPath: string): boolean | undefined;
57
+ /**
58
+ * True when a managed Copilot cloud file (the current generation or a
59
+ * recognized legacy one) already exists. Projects created before the opt-in
60
+ * prompt existed are treated as implicitly opted in, so `openspec update`
61
+ * keeps their files current instead of silently abandoning them.
62
+ */
63
+ export declare function hasExistingManagedCloudFiles(projectPath: string): Promise<boolean>;
64
+ /**
65
+ * Effective decision on whether to generate/refresh Copilot cloud files.
66
+ * An explicit opt-in or opt-out always wins; when undecided, fall back to
67
+ * whether managed files already exist (the migration path above).
68
+ */
69
+ export declare function isCopilotCloudEnabled(projectPath: string): Promise<boolean>;
70
+ /**
71
+ * Persist the Copilot cloud opt-in into openspec/config.yaml.
72
+ *
73
+ * Uses the YAML document model rather than a re-serialize so the user's
74
+ * existing comments, ordering, and formatting survive untouched — the config
75
+ * file is hand-authored and heavily commented, so a lossy round-trip would be
76
+ * its own source of toil. No-op when no config file exists yet (init creates it
77
+ * before this is called); the caller treats persistence failures as non-fatal.
78
+ */
79
+ export declare function persistCopilotCloudOptIn(projectPath: string, value: boolean): Promise<void>;
80
+ /**
81
+ * Return the managed cloud-file paths (relative to the project root) that
82
+ * currently hold user-owned, non-managed content — i.e. files OpenSpec will
83
+ * deliberately leave untouched. Used to tell an opted-in user that we preserved
84
+ * their existing file rather than silently doing nothing, which is the honest
85
+ * answer to "will this affect my existing Copilot cloud setup?".
86
+ */
87
+ export declare function findUnmanagedCloudFiles(projectPath: string): Promise<string[]>;
88
+ /**
89
+ * Return the managed cloud-file paths (relative to the project root) that
90
+ * currently exist and hold OpenSpec-generated content. Callers report this
91
+ * rather than the intended paths, so output never claims a file that a write
92
+ * skipped (user already owns it) or that reconciliation removed.
93
+ */
94
+ export declare function listManagedCloudFiles(projectPath: string): Promise<string[]>;
95
+ //# sourceMappingURL=cloud-agent.d.ts.map