@fission-ai/openspec 1.3.1 → 1.4.1

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 (165) hide show
  1. package/README.md +9 -2
  2. package/bin/openspec.js +3 -1
  3. package/dist/cli/index.d.ts +4 -1
  4. package/dist/cli/index.js +65 -3
  5. package/dist/commands/completion.js +7 -0
  6. package/dist/commands/config.js +64 -5
  7. package/dist/commands/context-store.d.ts +3 -0
  8. package/dist/commands/context-store.js +475 -0
  9. package/dist/commands/initiative.d.ts +13 -0
  10. package/dist/commands/initiative.js +318 -0
  11. package/dist/commands/workflow/index.d.ts +2 -0
  12. package/dist/commands/workflow/index.js +1 -0
  13. package/dist/commands/workflow/initiative-link.d.ts +24 -0
  14. package/dist/commands/workflow/initiative-link.js +47 -0
  15. package/dist/commands/workflow/instructions.d.ts +1 -1
  16. package/dist/commands/workflow/instructions.js +28 -11
  17. package/dist/commands/workflow/new-change.d.ts +6 -0
  18. package/dist/commands/workflow/new-change.js +114 -17
  19. package/dist/commands/workflow/set-change.d.ts +13 -0
  20. package/dist/commands/workflow/set-change.js +87 -0
  21. package/dist/commands/workflow/shared.d.ts +4 -2
  22. package/dist/commands/workflow/shared.js +6 -6
  23. package/dist/commands/workflow/status.js +19 -4
  24. package/dist/commands/workspace/context-status.d.ts +4 -0
  25. package/dist/commands/workspace/context-status.js +59 -0
  26. package/dist/commands/workspace/open-target-selection.d.ts +13 -0
  27. package/dist/commands/workspace/open-target-selection.js +146 -0
  28. package/dist/commands/workspace/open-view.d.ts +62 -0
  29. package/dist/commands/workspace/open-view.js +249 -0
  30. package/dist/commands/workspace/open.d.ts +37 -0
  31. package/dist/commands/workspace/open.js +110 -0
  32. package/dist/commands/workspace/opener-selection.d.ts +11 -0
  33. package/dist/commands/workspace/opener-selection.js +98 -0
  34. package/dist/commands/workspace/operations.d.ts +29 -0
  35. package/dist/commands/workspace/operations.js +543 -0
  36. package/dist/commands/workspace/prompt-theme.d.ts +29 -0
  37. package/dist/commands/workspace/prompt-theme.js +24 -0
  38. package/dist/commands/workspace/registration.d.ts +13 -0
  39. package/dist/commands/workspace/registration.js +84 -0
  40. package/dist/commands/workspace/selection.d.ts +8 -0
  41. package/dist/commands/workspace/selection.js +104 -0
  42. package/dist/commands/workspace/setup-prompts.d.ts +13 -0
  43. package/dist/commands/workspace/setup-prompts.js +121 -0
  44. package/dist/commands/workspace/types.d.ts +103 -0
  45. package/dist/commands/workspace/types.js +36 -0
  46. package/dist/commands/workspace.d.ts +5 -0
  47. package/dist/commands/workspace.js +574 -0
  48. package/dist/core/artifact-graph/index.d.ts +2 -1
  49. package/dist/core/artifact-graph/instruction-loader.d.ts +41 -1
  50. package/dist/core/artifact-graph/instruction-loader.js +46 -6
  51. package/dist/core/artifact-graph/types.d.ts +0 -5
  52. package/dist/core/artifact-graph/types.js +0 -14
  53. package/dist/core/change-metadata/index.d.ts +2 -0
  54. package/dist/core/change-metadata/index.js +2 -0
  55. package/dist/core/change-metadata/schema.d.ts +18 -0
  56. package/dist/core/change-metadata/schema.js +28 -0
  57. package/dist/core/change-status-policy.d.ts +50 -0
  58. package/dist/core/change-status-policy.js +70 -0
  59. package/dist/core/collections/index.d.ts +3 -0
  60. package/dist/core/collections/index.js +3 -0
  61. package/dist/core/collections/initiatives/collection.d.ts +4 -0
  62. package/dist/core/collections/initiatives/collection.js +17 -0
  63. package/dist/core/collections/initiatives/index.d.ts +6 -0
  64. package/dist/core/collections/initiatives/index.js +6 -0
  65. package/dist/core/collections/initiatives/operations.d.ts +49 -0
  66. package/dist/core/collections/initiatives/operations.js +175 -0
  67. package/dist/core/collections/initiatives/resolution.d.ts +87 -0
  68. package/dist/core/collections/initiatives/resolution.js +374 -0
  69. package/dist/core/collections/initiatives/schema.d.ts +41 -0
  70. package/dist/core/collections/initiatives/schema.js +134 -0
  71. package/dist/core/collections/initiatives/templates.d.ts +12 -0
  72. package/dist/core/collections/initiatives/templates.js +90 -0
  73. package/dist/core/collections/runtime.d.ts +46 -0
  74. package/dist/core/collections/runtime.js +194 -0
  75. package/dist/core/completions/command-registry.d.ts +1 -5
  76. package/dist/core/completions/command-registry.js +532 -32
  77. package/dist/core/completions/completion-provider.d.ts +11 -0
  78. package/dist/core/completions/completion-provider.js +27 -0
  79. package/dist/core/completions/generators/bash-generator.d.ts +3 -0
  80. package/dist/core/completions/generators/bash-generator.js +60 -4
  81. package/dist/core/completions/generators/fish-generator.js +3 -0
  82. package/dist/core/completions/generators/powershell-generator.d.ts +3 -0
  83. package/dist/core/completions/generators/powershell-generator.js +63 -4
  84. package/dist/core/completions/generators/zsh-generator.d.ts +3 -0
  85. package/dist/core/completions/generators/zsh-generator.js +46 -22
  86. package/dist/core/completions/installers/zsh-installer.d.ts +0 -8
  87. package/dist/core/completions/installers/zsh-installer.js +3 -31
  88. package/dist/core/completions/shared-flags.d.ts +12 -0
  89. package/dist/core/completions/shared-flags.js +28 -0
  90. package/dist/core/completions/templates/bash-templates.d.ts +1 -1
  91. package/dist/core/completions/templates/bash-templates.js +6 -0
  92. package/dist/core/completions/templates/fish-templates.d.ts +1 -1
  93. package/dist/core/completions/templates/fish-templates.js +6 -0
  94. package/dist/core/completions/templates/powershell-templates.d.ts +1 -1
  95. package/dist/core/completions/templates/powershell-templates.js +9 -0
  96. package/dist/core/completions/templates/zsh-templates.d.ts +1 -1
  97. package/dist/core/completions/templates/zsh-templates.js +9 -0
  98. package/dist/core/completions/types.d.ts +23 -1
  99. package/dist/core/config.js +3 -1
  100. package/dist/core/context-store/binding.d.ts +53 -0
  101. package/dist/core/context-store/binding.js +197 -0
  102. package/dist/core/context-store/errors.d.ts +20 -0
  103. package/dist/core/context-store/errors.js +22 -0
  104. package/dist/core/context-store/foundation.d.ts +55 -0
  105. package/dist/core/context-store/foundation.js +321 -0
  106. package/dist/core/context-store/index.d.ts +6 -0
  107. package/dist/core/context-store/index.js +6 -0
  108. package/dist/core/context-store/operations.d.ts +85 -0
  109. package/dist/core/context-store/operations.js +528 -0
  110. package/dist/core/context-store/registry.d.ts +45 -0
  111. package/dist/core/context-store/registry.js +229 -0
  112. package/dist/core/global-config.d.ts +6 -1
  113. package/dist/core/global-config.js +15 -16
  114. package/dist/core/index.d.ts +5 -1
  115. package/dist/core/index.js +4 -0
  116. package/dist/core/parsers/requirement-blocks.js +5 -5
  117. package/dist/core/parsers/spec-structure.js +1 -1
  118. package/dist/core/planning-home.d.ts +21 -0
  119. package/dist/core/planning-home.js +108 -0
  120. package/dist/core/profiles.d.ts +1 -1
  121. package/dist/core/profiles.js +1 -1
  122. package/dist/core/specs-apply.js +1 -1
  123. package/dist/core/templates/workflows/apply-change.js +6 -0
  124. package/dist/core/templates/workflows/archive-change.js +21 -15
  125. package/dist/core/templates/workflows/bulk-archive-change.js +14 -10
  126. package/dist/core/templates/workflows/continue-change.js +6 -4
  127. package/dist/core/templates/workflows/explore.js +8 -10
  128. package/dist/core/templates/workflows/ff-change.js +8 -6
  129. package/dist/core/templates/workflows/new-change.js +6 -6
  130. package/dist/core/templates/workflows/onboard.js +13 -15
  131. package/dist/core/templates/workflows/propose.js +8 -6
  132. package/dist/core/templates/workflows/sync-specs.js +28 -10
  133. package/dist/core/templates/workflows/verify-change.js +10 -4
  134. package/dist/core/update.d.ts +5 -0
  135. package/dist/core/update.js +20 -0
  136. package/dist/core/validation/validator.d.ts +11 -0
  137. package/dist/core/validation/validator.js +19 -2
  138. package/dist/core/workspace/foundation.d.ts +67 -0
  139. package/dist/core/workspace/foundation.js +295 -0
  140. package/dist/core/workspace/index.d.ts +8 -0
  141. package/dist/core/workspace/index.js +8 -0
  142. package/dist/core/workspace/legacy-state.d.ts +28 -0
  143. package/dist/core/workspace/legacy-state.js +200 -0
  144. package/dist/core/workspace/link-input.d.ts +9 -0
  145. package/dist/core/workspace/link-input.js +32 -0
  146. package/dist/core/workspace/open-surface.d.ts +45 -0
  147. package/dist/core/workspace/open-surface.js +215 -0
  148. package/dist/core/workspace/openers.d.ts +21 -0
  149. package/dist/core/workspace/openers.js +124 -0
  150. package/dist/core/workspace/registry.d.ts +24 -0
  151. package/dist/core/workspace/registry.js +146 -0
  152. package/dist/core/workspace/skills.d.ts +57 -0
  153. package/dist/core/workspace/skills.js +334 -0
  154. package/dist/core/workspace/state-io.d.ts +10 -0
  155. package/dist/core/workspace/state-io.js +121 -0
  156. package/dist/utils/change-metadata.d.ts +5 -2
  157. package/dist/utils/change-metadata.js +7 -13
  158. package/dist/utils/change-utils.d.ts +9 -0
  159. package/dist/utils/change-utils.js +7 -5
  160. package/package.json +2 -1
  161. package/schemas/workspace-planning/schema.yaml +72 -0
  162. package/schemas/workspace-planning/templates/design.md +33 -0
  163. package/schemas/workspace-planning/templates/proposal.md +28 -0
  164. package/schemas/workspace-planning/templates/spec.md +9 -0
  165. package/schemas/workspace-planning/templates/tasks.md +15 -0
@@ -24,9 +24,12 @@ export function getVerifyChangeSkillTemplate() {
24
24
  \`\`\`
25
25
  Parse the JSON to understand:
26
26
  - \`schemaName\`: The workflow being used (e.g., "spec-driven")
27
+ - \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`: path and scope context
27
28
  - Which artifacts exist for this change
28
29
 
29
- 3. **Get the change directory and load artifacts**
30
+ If status reports \`actionContext.mode: "workspace-planning"\`, explain that full workspace implementation verification is not supported in this slice and STOP. Do not infer repo-local implementation ownership or edit linked repos.
31
+
32
+ 3. **Get planning context and load artifacts**
30
33
 
31
34
  \`\`\`bash
32
35
  openspec instructions apply --change "<name>" --json
@@ -54,7 +57,7 @@ export function getVerifyChangeSkillTemplate() {
54
57
  - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
55
58
 
56
59
  **Spec Coverage**:
57
- - If delta specs exist in \`openspec/changes/<name>/specs/\`:
60
+ - If delta specs exist in \`contextFiles.specs\`:
58
61
  - Extract all requirements (marked with "### Requirement:")
59
62
  - For each requirement:
60
63
  - Search codebase for keywords related to the requirement
@@ -192,9 +195,12 @@ export function getOpsxVerifyCommandTemplate() {
192
195
  \`\`\`
193
196
  Parse the JSON to understand:
194
197
  - \`schemaName\`: The workflow being used (e.g., "spec-driven")
198
+ - \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`: path and scope context
195
199
  - Which artifacts exist for this change
196
200
 
197
- 3. **Get the change directory and load artifacts**
201
+ If status reports \`actionContext.mode: "workspace-planning"\`, explain that full workspace implementation verification is not supported in this slice and STOP. Do not infer repo-local implementation ownership or edit linked repos.
202
+
203
+ 3. **Get planning context and load artifacts**
198
204
 
199
205
  \`\`\`bash
200
206
  openspec instructions apply --change "<name>" --json
@@ -222,7 +228,7 @@ export function getOpsxVerifyCommandTemplate() {
222
228
  - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
223
229
 
224
230
  **Spec Coverage**:
225
- - If delta specs exist in \`openspec/changes/<name>/specs/\`:
231
+ - If delta specs exist in \`contextFiles.specs\`:
226
232
  - Extract all requirements (marked with "### Requirement:")
227
233
  - For each requirement:
228
234
  - Search codebase for keywords related to the requirement
@@ -38,6 +38,11 @@ export declare class UpdateCommand {
38
38
  * Displays a note about extra workflows installed that aren't in the current profile.
39
39
  */
40
40
  private displayExtraWorkflowsNote;
41
+ /**
42
+ * Suggest opting back into core when a custom profile still matches the old
43
+ * pre-sync core set. Keep custom profiles user-owned; do not mutate them.
44
+ */
45
+ private displayOldCoreCustomProfileNote;
41
46
  /**
42
47
  * Removes skill directories for workflows when delivery changed to commands-only.
43
48
  * Returns the number of directories removed.
@@ -23,6 +23,7 @@ import { WORKFLOW_TO_SKILL_DIR, getCommandConfiguredTools, getConfiguredToolsFor
23
23
  import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, } from './migration.js';
24
24
  const require = createRequire(import.meta.url);
25
25
  const { version: OPENSPEC_VERSION } = require('../../package.json');
26
+ const OLD_CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive'];
26
27
  /**
27
28
  * Scans installed workflow artifacts (skills and managed commands) across all configured tools.
28
29
  * Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
@@ -95,6 +96,7 @@ export class UpdateCommand {
95
96
  // Still check for new tool directories and extra workflows
96
97
  this.detectNewTools(resolvedProjectPath, configuredTools);
97
98
  this.displayExtraWorkflowsNote(resolvedProjectPath, configuredTools, desiredWorkflows);
99
+ this.displayOldCoreCustomProfileNote(profile, globalConfig.workflows);
98
100
  return;
99
101
  }
100
102
  // 8. Display update plan
@@ -201,6 +203,7 @@ export class UpdateCommand {
201
203
  this.detectNewTools(resolvedProjectPath, configuredAndNewTools);
202
204
  // 14. Display note about extra workflows not in profile
203
205
  this.displayExtraWorkflowsNote(resolvedProjectPath, configuredAndNewTools, desiredWorkflows);
206
+ this.displayOldCoreCustomProfileNote(profile, globalConfig.workflows);
204
207
  // 15. List affected tools
205
208
  if (updatedTools.length > 0) {
206
209
  const toolDisplayNames = updatedTools;
@@ -264,6 +267,23 @@ export class UpdateCommand {
264
267
  console.log(chalk.dim(`Note: ${extraWorkflows.length} extra workflows not in profile (use \`openspec config profile\` to manage)`));
265
268
  }
266
269
  }
270
+ /**
271
+ * Suggest opting back into core when a custom profile still matches the old
272
+ * pre-sync core set. Keep custom profiles user-owned; do not mutate them.
273
+ */
274
+ displayOldCoreCustomProfileNote(profile, workflows) {
275
+ if (profile !== 'custom' || !workflows) {
276
+ return;
277
+ }
278
+ const workflowSet = new Set(workflows);
279
+ const matchesOldCore = workflowSet.size === OLD_CORE_WORKFLOWS.length &&
280
+ OLD_CORE_WORKFLOWS.every((workflow) => workflowSet.has(workflow));
281
+ if (!matchesOldCore) {
282
+ return;
283
+ }
284
+ console.log(chalk.dim('Note: The core profile now includes sync. Your custom profile is preserving the old core workflow set.'));
285
+ console.log(chalk.dim('Run `openspec config profile core` and then `openspec update` to add sync.'));
286
+ }
267
287
  /**
268
288
  * Removes skill directories for workflows when delivery changed to commands-only.
269
289
  * Returns the number of directories removed.
@@ -27,6 +27,17 @@ export declare class Validator {
27
27
  isValid(report: ValidationReport): boolean;
28
28
  private extractRequirementText;
29
29
  private containsShallOrMust;
30
+ /**
31
+ * Build an error message for a requirement block whose body lacks SHALL/MUST.
32
+ *
33
+ * When the SHALL/MUST keyword already appears in the requirement header (e.g.
34
+ * `### Requirement: The system SHALL ...`) the original generic error
35
+ * ("must contain SHALL or MUST") is confusing because the keyword is visibly
36
+ * present in the spec. Per the OpenSpec conventions the keyword has to live
37
+ * on the requirement body line (the line right after the header), so we point
38
+ * the author at that exact fix when the keyword is found in the header only.
39
+ */
40
+ private buildMissingShallOrMustMessage;
30
41
  private countScenarios;
31
42
  private formatSectionList;
32
43
  }
@@ -150,7 +150,7 @@ export class Validator {
150
150
  issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" is missing requirement text` });
151
151
  }
152
152
  else if (!this.containsShallOrMust(requirementText)) {
153
- issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must contain SHALL or MUST` });
153
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage('ADDED', block.name) });
154
154
  }
155
155
  const scenarioCount = this.countScenarios(block.raw);
156
156
  if (scenarioCount < 1) {
@@ -172,7 +172,7 @@ export class Validator {
172
172
  issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" is missing requirement text` });
173
173
  }
174
174
  else if (!this.containsShallOrMust(requirementText)) {
175
- issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must contain SHALL or MUST` });
175
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage('MODIFIED', block.name) });
176
176
  }
177
177
  const scenarioCount = this.countScenarios(block.raw);
178
178
  if (scenarioCount < 1) {
@@ -401,6 +401,23 @@ export class Validator {
401
401
  containsShallOrMust(text) {
402
402
  return /\b(SHALL|MUST)\b/.test(text);
403
403
  }
404
+ /**
405
+ * Build an error message for a requirement block whose body lacks SHALL/MUST.
406
+ *
407
+ * When the SHALL/MUST keyword already appears in the requirement header (e.g.
408
+ * `### Requirement: The system SHALL ...`) the original generic error
409
+ * ("must contain SHALL or MUST") is confusing because the keyword is visibly
410
+ * present in the spec. Per the OpenSpec conventions the keyword has to live
411
+ * on the requirement body line (the line right after the header), so we point
412
+ * the author at that exact fix when the keyword is found in the header only.
413
+ */
414
+ buildMissingShallOrMustMessage(action, blockName) {
415
+ const base = `${action} "${blockName}" must contain SHALL or MUST`;
416
+ if (this.containsShallOrMust(blockName)) {
417
+ return `${base} in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the "### Requirement: ..." header.`;
418
+ }
419
+ return base;
420
+ }
404
421
  countScenarios(blockRaw) {
405
422
  const matches = blockRaw.match(/^####\s+/gm);
406
423
  return matches ? matches.length : 0;
@@ -0,0 +1,67 @@
1
+ import { type ContextStoreBinding, type ContextStoreSelector } from '../context-store/index.js';
2
+ export declare const WORKSPACE_METADATA_DIR_NAME = ".openspec-workspace";
3
+ export declare const WORKSPACE_VIEW_STATE_FILE_NAME = "view.yaml";
4
+ export declare const WORKSPACE_CHANGES_DIR_NAME = "changes";
5
+ export declare const WORKSPACE_CODE_WORKSPACE_EXTENSION = ".code-workspace";
6
+ export declare const WORKSPACE_SUPPORTED_OPENER_VALUES: readonly ["codex-cli", "claude", "github-copilot", "editor"];
7
+ export declare const WORKSPACE_AGENT_OPENER_IDS: readonly ["codex-cli", "claude", "github-copilot"];
8
+ export declare const WORKSPACE_EDITOR_OPENER_IDS: readonly ["vscode"];
9
+ export type WorkspaceSupportedOpenerValue = typeof WORKSPACE_SUPPORTED_OPENER_VALUES[number];
10
+ export type WorkspaceAgentOpenerId = typeof WORKSPACE_AGENT_OPENER_IDS[number];
11
+ export type WorkspaceEditorOpenerId = typeof WORKSPACE_EDITOR_OPENER_IDS[number];
12
+ export type WorkspacePreferredOpener = {
13
+ kind: 'agent';
14
+ id: WorkspaceAgentOpenerId;
15
+ } | {
16
+ kind: 'editor';
17
+ id: WorkspaceEditorOpenerId;
18
+ };
19
+ export interface WorkspaceContextState {
20
+ kind: 'initiative';
21
+ store: ContextStoreBinding;
22
+ initiative: {
23
+ id: string;
24
+ };
25
+ }
26
+ export interface WorkspaceViewState {
27
+ version: 1;
28
+ name: string;
29
+ context: WorkspaceContextState | null;
30
+ links: Record<string, string | null>;
31
+ preferred_opener?: WorkspacePreferredOpener;
32
+ tools?: string[];
33
+ workspace_skills?: WorkspaceSkillState;
34
+ }
35
+ export interface WorkspaceSkillState {
36
+ selected_agents: string[];
37
+ last_applied_profile?: 'core' | 'custom';
38
+ last_applied_delivery?: 'both' | 'skills' | 'commands';
39
+ last_applied_workflow_ids?: string[];
40
+ last_applied_at?: string;
41
+ }
42
+ export declare function getWorkspaceMetadataDir(workspaceRoot: string): string;
43
+ export declare function getWorkspaceViewStatePath(workspaceRoot: string): string;
44
+ export declare function getWorkspaceChangesDir(workspaceRoot: string): string;
45
+ export declare function getWorkspaceCodeWorkspaceFileName(workspaceName: string): string;
46
+ export declare function getWorkspaceCodeWorkspacePath(workspaceRoot: string, workspaceName: string): string;
47
+ /**
48
+ * @deprecated Managed workspaces no longer create portable ignore rules.
49
+ * This compatibility shim remains for callers that still ask which ignore
50
+ * patterns OpenSpec owns for workspace-local generated files.
51
+ */
52
+ export declare function getWorkspacePortableIgnorePatterns(_workspaceName?: string): string[];
53
+ export declare function validateWorkspaceName(name: string): string;
54
+ export declare function validateWorkspaceLinkName(name: string): string;
55
+ export declare function isValidWorkspaceName(name: string): boolean;
56
+ export declare function isValidWorkspaceLinkName(name: string): boolean;
57
+ export declare function isWorkspaceAgentOpenerId(value: string): value is WorkspaceAgentOpenerId;
58
+ export declare function isWorkspaceSupportedOpenerValue(value: string): value is WorkspaceSupportedOpenerValue;
59
+ export declare function parseWorkspacePreferredOpenerValue(value: string): WorkspacePreferredOpener;
60
+ export declare function validateWorkspacePreferredOpener(opener: WorkspacePreferredOpener): WorkspacePreferredOpener;
61
+ export declare function createWorkspaceInitiativeContext(store: ContextStoreBinding, initiativeId: string): WorkspaceContextState;
62
+ export declare function getWorkspaceContextStoreId(context: WorkspaceContextState): string;
63
+ export declare function getWorkspaceContextStoreSelector(context: WorkspaceContextState): ContextStoreSelector;
64
+ export declare function getWorkspaceContextInitiativeId(context: WorkspaceContextState): string;
65
+ export declare function parseWorkspaceViewState(content: string): WorkspaceViewState;
66
+ export declare function serializeWorkspaceViewState(state: WorkspaceViewState): string;
67
+ //# sourceMappingURL=foundation.d.ts.map
@@ -0,0 +1,295 @@
1
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
2
+ import { z } from 'zod';
3
+ import { normalizeContextStoreBinding, } from '../context-store/index.js';
4
+ import { FileSystemUtils } from '../../utils/file-system.js';
5
+ export const WORKSPACE_METADATA_DIR_NAME = '.openspec-workspace';
6
+ export const WORKSPACE_VIEW_STATE_FILE_NAME = 'view.yaml';
7
+ export const WORKSPACE_CHANGES_DIR_NAME = 'changes';
8
+ export const WORKSPACE_CODE_WORKSPACE_EXTENSION = '.code-workspace';
9
+ export const WORKSPACE_SUPPORTED_OPENER_VALUES = [
10
+ 'codex-cli',
11
+ 'claude',
12
+ 'github-copilot',
13
+ 'editor',
14
+ ];
15
+ export const WORKSPACE_AGENT_OPENER_IDS = [
16
+ 'codex-cli',
17
+ 'claude',
18
+ 'github-copilot',
19
+ ];
20
+ export const WORKSPACE_EDITOR_OPENER_IDS = ['vscode'];
21
+ function joinWorkspacePath(basePath, ...segments) {
22
+ return FileSystemUtils.joinPath(basePath, ...segments);
23
+ }
24
+ export function getWorkspaceMetadataDir(workspaceRoot) {
25
+ return joinWorkspacePath(workspaceRoot, WORKSPACE_METADATA_DIR_NAME);
26
+ }
27
+ export function getWorkspaceViewStatePath(workspaceRoot) {
28
+ return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_VIEW_STATE_FILE_NAME);
29
+ }
30
+ export function getWorkspaceChangesDir(workspaceRoot) {
31
+ return joinWorkspacePath(workspaceRoot, WORKSPACE_CHANGES_DIR_NAME);
32
+ }
33
+ export function getWorkspaceCodeWorkspaceFileName(workspaceName) {
34
+ validateWorkspaceName(workspaceName);
35
+ return `${workspaceName}${WORKSPACE_CODE_WORKSPACE_EXTENSION}`;
36
+ }
37
+ export function getWorkspaceCodeWorkspacePath(workspaceRoot, workspaceName) {
38
+ return joinWorkspacePath(workspaceRoot, getWorkspaceCodeWorkspaceFileName(workspaceName));
39
+ }
40
+ /**
41
+ * @deprecated Managed workspaces no longer create portable ignore rules.
42
+ * This compatibility shim remains for callers that still ask which ignore
43
+ * patterns OpenSpec owns for workspace-local generated files.
44
+ */
45
+ export function getWorkspacePortableIgnorePatterns(_workspaceName) {
46
+ return [];
47
+ }
48
+ function validateFolderStyleName(name, label) {
49
+ if (name.length === 0) {
50
+ throw new Error(`${label} must not be empty`);
51
+ }
52
+ if (name === '.' || name === '..') {
53
+ throw new Error(`${label} must not be '${name}'`);
54
+ }
55
+ if (/[\\/]/u.test(name)) {
56
+ throw new Error(`${label} must not contain path separators`);
57
+ }
58
+ return name;
59
+ }
60
+ export function validateWorkspaceName(name) {
61
+ validateFolderStyleName(name, 'Workspace name');
62
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name)) {
63
+ throw new Error('Workspace name must be kebab-case with lowercase letters, numbers, and single hyphen separators');
64
+ }
65
+ return name;
66
+ }
67
+ export function validateWorkspaceLinkName(name) {
68
+ return validateFolderStyleName(name, 'Workspace link name');
69
+ }
70
+ export function isValidWorkspaceName(name) {
71
+ try {
72
+ validateWorkspaceName(name);
73
+ return true;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ export function isValidWorkspaceLinkName(name) {
80
+ try {
81
+ validateWorkspaceLinkName(name);
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ const ContextStoreSelectorSchema = z.union([
89
+ z
90
+ .object({
91
+ kind: z.literal('registry'),
92
+ id: z.string(),
93
+ })
94
+ .strict(),
95
+ z
96
+ .object({
97
+ kind: z.literal('path'),
98
+ path: z.string(),
99
+ observed_id: z.string().optional(),
100
+ })
101
+ .strict(),
102
+ ]);
103
+ const ContextStoreBindingSchema = z
104
+ .object({
105
+ id: z.string(),
106
+ selector: ContextStoreSelectorSchema,
107
+ })
108
+ .strict();
109
+ const WorkspaceInitiativeContextSchema = z
110
+ .object({
111
+ kind: z.literal('initiative'),
112
+ store: ContextStoreBindingSchema,
113
+ initiative: z
114
+ .object({
115
+ id: z.string(),
116
+ })
117
+ .strict(),
118
+ })
119
+ .strict();
120
+ const WorkspaceContextSchema = WorkspaceInitiativeContextSchema;
121
+ const WorkspaceSkillStateSchema = z
122
+ .object({
123
+ selected_agents: z.array(z.string()),
124
+ last_applied_profile: z.enum(['core', 'custom']).optional(),
125
+ last_applied_delivery: z.enum(['both', 'skills', 'commands']).optional(),
126
+ last_applied_workflow_ids: z.array(z.string()).optional(),
127
+ last_applied_at: z.string().optional(),
128
+ })
129
+ .strict();
130
+ const PreferredOpenerSchema = z
131
+ .object({
132
+ kind: z.enum(['agent', 'editor']),
133
+ id: z.string(),
134
+ })
135
+ .strict();
136
+ const ViewStateSchema = z
137
+ .object({
138
+ version: z.literal(1),
139
+ name: z.string(),
140
+ context: WorkspaceContextSchema.nullable(),
141
+ links: z.record(z.string(), z.string().nullable()),
142
+ preferred_opener: PreferredOpenerSchema.optional(),
143
+ tools: z.array(z.string()).optional(),
144
+ workspace_skills: WorkspaceSkillStateSchema.optional(),
145
+ })
146
+ .strict();
147
+ function formatZodIssues(error) {
148
+ return error.issues
149
+ .map((issue) => {
150
+ const location = issue.path.length > 0 ? issue.path.join('.') : 'root';
151
+ return `${location}: ${issue.message}`;
152
+ })
153
+ .join('; ');
154
+ }
155
+ function parseYamlObject(content, label) {
156
+ try {
157
+ return parseYaml(content);
158
+ }
159
+ catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ throw new Error(`Invalid ${label}: ${message}`);
162
+ }
163
+ }
164
+ function assertValidMapKeys(keys, validator, label) {
165
+ for (const key of keys) {
166
+ try {
167
+ validator(key);
168
+ }
169
+ catch (error) {
170
+ const message = error instanceof Error ? error.message : String(error);
171
+ throw new Error(`Invalid ${label} '${key}': ${message}`);
172
+ }
173
+ }
174
+ }
175
+ function formatSupportedOpenerValues() {
176
+ return WORKSPACE_SUPPORTED_OPENER_VALUES.join(', ');
177
+ }
178
+ function normalizeWorkspaceAgentOpenerId(value) {
179
+ if (value === 'codex') {
180
+ return 'codex-cli';
181
+ }
182
+ if (isWorkspaceAgentOpenerId(value)) {
183
+ return value;
184
+ }
185
+ return null;
186
+ }
187
+ export function isWorkspaceAgentOpenerId(value) {
188
+ return WORKSPACE_AGENT_OPENER_IDS.includes(value);
189
+ }
190
+ export function isWorkspaceSupportedOpenerValue(value) {
191
+ return WORKSPACE_SUPPORTED_OPENER_VALUES.includes(value);
192
+ }
193
+ export function parseWorkspacePreferredOpenerValue(value) {
194
+ if (value === 'editor') {
195
+ return {
196
+ kind: 'editor',
197
+ id: 'vscode',
198
+ };
199
+ }
200
+ const agentId = normalizeWorkspaceAgentOpenerId(value);
201
+ if (agentId) {
202
+ return {
203
+ kind: 'agent',
204
+ id: agentId,
205
+ };
206
+ }
207
+ throw new Error(`Unsupported workspace opener '${value}'. Supported values: ${formatSupportedOpenerValues()}`);
208
+ }
209
+ export function validateWorkspacePreferredOpener(opener) {
210
+ if (opener.kind === 'editor' && opener.id === 'vscode') {
211
+ return opener;
212
+ }
213
+ if (opener.kind === 'agent') {
214
+ const agentId = normalizeWorkspaceAgentOpenerId(opener.id);
215
+ if (agentId) {
216
+ return {
217
+ kind: 'agent',
218
+ id: agentId,
219
+ };
220
+ }
221
+ }
222
+ throw new Error(`Unsupported workspace opener '${opener.kind}:${opener.id}'. Supported values: ${formatSupportedOpenerValues()}`);
223
+ }
224
+ function normalizeWorkspaceContextState(context) {
225
+ return createWorkspaceInitiativeContext(normalizeContextStoreBinding(context.store), context.initiative.id);
226
+ }
227
+ function normalizeOptionalWorkspaceContextState(context) {
228
+ return context ? normalizeWorkspaceContextState(context) : null;
229
+ }
230
+ export function createWorkspaceInitiativeContext(store, initiativeId) {
231
+ if (initiativeId.length === 0) {
232
+ throw new Error('Workspace initiative id must not be empty.');
233
+ }
234
+ return {
235
+ kind: 'initiative',
236
+ store: normalizeContextStoreBinding(store),
237
+ initiative: {
238
+ id: initiativeId,
239
+ },
240
+ };
241
+ }
242
+ export function getWorkspaceContextStoreId(context) {
243
+ return context.store.id;
244
+ }
245
+ export function getWorkspaceContextStoreSelector(context) {
246
+ return context.store.selector;
247
+ }
248
+ export function getWorkspaceContextInitiativeId(context) {
249
+ return context.initiative.id;
250
+ }
251
+ export function parseWorkspaceViewState(content) {
252
+ const raw = parseYamlObject(content, 'workspace state');
253
+ const result = ViewStateSchema.safeParse(raw);
254
+ if (!result.success) {
255
+ throw new Error(`Invalid workspace state: ${formatZodIssues(result.error)}`);
256
+ }
257
+ validateWorkspaceName(result.data.name);
258
+ assertValidMapKeys(Object.keys(result.data.links), validateWorkspaceLinkName, 'workspace link name');
259
+ const preferredOpener = result.data.preferred_opener
260
+ ? validateWorkspacePreferredOpener(result.data.preferred_opener)
261
+ : undefined;
262
+ return {
263
+ version: 1,
264
+ name: result.data.name,
265
+ context: normalizeOptionalWorkspaceContextState(result.data.context),
266
+ links: result.data.links,
267
+ ...(preferredOpener ? { preferred_opener: preferredOpener } : {}),
268
+ ...(result.data.tools ? { tools: result.data.tools } : {}),
269
+ ...(result.data.workspace_skills
270
+ ? { workspace_skills: result.data.workspace_skills }
271
+ : {}),
272
+ };
273
+ }
274
+ export function serializeWorkspaceViewState(state) {
275
+ validateWorkspaceName(state.name);
276
+ assertValidMapKeys(Object.keys(state.links), validateWorkspaceLinkName, 'workspace link name');
277
+ for (const [linkName, localPath] of Object.entries(state.links)) {
278
+ if (localPath !== null && typeof localPath !== 'string') {
279
+ throw new Error(`Invalid workspace link '${linkName}': path must be a string or null`);
280
+ }
281
+ }
282
+ const preferredOpener = state.preferred_opener
283
+ ? validateWorkspacePreferredOpener(state.preferred_opener)
284
+ : undefined;
285
+ return stringifyYaml({
286
+ version: 1,
287
+ name: state.name,
288
+ context: state.context ? normalizeWorkspaceContextState(state.context) : null,
289
+ links: state.links,
290
+ ...(preferredOpener ? { preferred_opener: preferredOpener } : {}),
291
+ ...(state.tools ? { tools: state.tools } : {}),
292
+ ...(state.workspace_skills ? { workspace_skills: state.workspace_skills } : {}),
293
+ });
294
+ }
295
+ //# sourceMappingURL=foundation.js.map
@@ -0,0 +1,8 @@
1
+ export * from './foundation.js';
2
+ export * from './link-input.js';
3
+ export * from './openers.js';
4
+ export * from './open-surface.js';
5
+ export * from './registry.js';
6
+ export * from './skills.js';
7
+ export * from './state-io.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,8 @@
1
+ export * from './foundation.js';
2
+ export * from './link-input.js';
3
+ export * from './openers.js';
4
+ export * from './open-surface.js';
5
+ export * from './registry.js';
6
+ export * from './skills.js';
7
+ export * from './state-io.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ import { type WorkspaceContextState, type WorkspacePreferredOpener, type WorkspaceSkillState, type WorkspaceViewState } from './foundation.js';
2
+ export declare const WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME = "workspace.yaml";
3
+ export declare const WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME = "local.yaml";
4
+ export declare const WORKSPACE_LEGACY_LOCAL_STATE_IGNORE_PATTERN = ".openspec-workspace/local.yaml";
5
+ export type WorkspaceLinkState = Record<string, unknown>;
6
+ export interface WorkspaceSharedState {
7
+ version: 1;
8
+ name: string;
9
+ context: WorkspaceContextState | null;
10
+ links: Record<string, WorkspaceLinkState>;
11
+ }
12
+ export interface WorkspaceLocalState {
13
+ version: 1;
14
+ paths: Record<string, string>;
15
+ preferred_opener?: WorkspacePreferredOpener;
16
+ tools?: string[];
17
+ workspace_skills?: WorkspaceSkillState;
18
+ }
19
+ export declare function getWorkspaceLegacySharedStatePath(workspaceRoot: string): string;
20
+ export declare function getWorkspaceLegacyLocalStatePath(workspaceRoot: string): string;
21
+ export declare function workspaceViewToSharedState(state: WorkspaceViewState): WorkspaceSharedState;
22
+ export declare function workspaceViewToLocalState(state: WorkspaceViewState): WorkspaceLocalState;
23
+ export declare function workspaceStatePartsToViewState(sharedState: WorkspaceSharedState, localState: WorkspaceLocalState | null): WorkspaceViewState;
24
+ export declare function parseWorkspaceSharedState(content: string): WorkspaceSharedState;
25
+ export declare function parseWorkspaceLocalState(content: string): WorkspaceLocalState;
26
+ export declare function serializeWorkspaceSharedState(state: WorkspaceSharedState): string;
27
+ export declare function serializeWorkspaceLocalState(state: WorkspaceLocalState): string;
28
+ //# sourceMappingURL=legacy-state.d.ts.map