gmc-openspec 1.0.0 → 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.
- package/README.md +2 -2
- package/bin/openspec.js +3 -1
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.js +36 -2
- package/dist/commands/config.js +4 -4
- package/dist/commands/context-store.d.ts +3 -0
- package/dist/commands/context-store.js +475 -0
- package/dist/commands/initiative.d.ts +13 -0
- package/dist/commands/initiative.js +318 -0
- package/dist/commands/workflow/index.d.ts +2 -0
- package/dist/commands/workflow/index.js +1 -0
- package/dist/commands/workflow/initiative-link.d.ts +24 -0
- package/dist/commands/workflow/initiative-link.js +47 -0
- package/dist/commands/workflow/instructions.js +10 -2
- package/dist/commands/workflow/new-change.d.ts +4 -0
- package/dist/commands/workflow/new-change.js +72 -23
- package/dist/commands/workflow/set-change.d.ts +13 -0
- package/dist/commands/workflow/set-change.js +87 -0
- package/dist/commands/workflow/shared.d.ts +2 -0
- package/dist/commands/workflow/status.js +3 -0
- package/dist/commands/workspace/context-status.d.ts +4 -0
- package/dist/commands/workspace/context-status.js +59 -0
- package/dist/commands/workspace/open-target-selection.d.ts +13 -0
- package/dist/commands/workspace/open-target-selection.js +146 -0
- package/dist/commands/workspace/open-view.d.ts +62 -0
- package/dist/commands/workspace/open-view.js +249 -0
- package/dist/commands/workspace/open.d.ts +16 -8
- package/dist/commands/workspace/open.js +40 -14
- package/dist/commands/workspace/opener-selection.d.ts +11 -0
- package/dist/commands/workspace/opener-selection.js +98 -0
- package/dist/commands/workspace/operations.d.ts +14 -8
- package/dist/commands/workspace/operations.js +228 -160
- package/dist/commands/workspace/prompt-theme.d.ts +29 -0
- package/dist/commands/workspace/prompt-theme.js +24 -0
- package/dist/commands/workspace/registration.d.ts +13 -0
- package/dist/commands/workspace/registration.js +84 -0
- package/dist/commands/workspace/selection.d.ts +3 -0
- package/dist/commands/workspace/selection.js +42 -40
- package/dist/commands/workspace/setup-prompts.d.ts +13 -0
- package/dist/commands/workspace/setup-prompts.js +121 -0
- package/dist/commands/workspace/types.d.ts +15 -0
- package/dist/commands/workspace.js +59 -340
- package/dist/core/artifact-graph/index.d.ts +2 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +10 -23
- package/dist/core/artifact-graph/instruction-loader.js +28 -89
- package/dist/core/artifact-graph/types.d.ts +0 -7
- package/dist/core/artifact-graph/types.js +0 -19
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +18 -0
- package/dist/core/change-metadata/schema.js +28 -0
- package/dist/core/change-status-policy.d.ts +50 -0
- package/dist/core/change-status-policy.js +70 -0
- package/dist/core/collections/index.d.ts +3 -0
- package/dist/core/collections/index.js +3 -0
- package/dist/core/collections/initiatives/collection.d.ts +4 -0
- package/dist/core/collections/initiatives/collection.js +17 -0
- package/dist/core/collections/initiatives/index.d.ts +6 -0
- package/dist/core/collections/initiatives/index.js +6 -0
- package/dist/core/collections/initiatives/operations.d.ts +49 -0
- package/dist/core/collections/initiatives/operations.js +175 -0
- package/dist/core/collections/initiatives/resolution.d.ts +87 -0
- package/dist/core/collections/initiatives/resolution.js +374 -0
- package/dist/core/collections/initiatives/schema.d.ts +41 -0
- package/dist/core/collections/initiatives/schema.js +134 -0
- package/dist/core/collections/initiatives/templates.d.ts +12 -0
- package/dist/core/collections/initiatives/templates.js +90 -0
- package/dist/core/collections/runtime.d.ts +46 -0
- package/dist/core/collections/runtime.js +194 -0
- package/dist/core/completions/command-registry.d.ts +1 -5
- package/dist/core/completions/command-registry.js +475 -70
- package/dist/core/completions/shared-flags.d.ts +12 -0
- package/dist/core/completions/shared-flags.js +28 -0
- package/dist/core/config.js +2 -1
- package/dist/core/context-store/binding.d.ts +53 -0
- package/dist/core/context-store/binding.js +197 -0
- package/dist/core/context-store/errors.d.ts +20 -0
- package/dist/core/context-store/errors.js +22 -0
- package/dist/core/context-store/foundation.d.ts +55 -0
- package/dist/core/context-store/foundation.js +321 -0
- package/dist/core/context-store/index.d.ts +6 -0
- package/dist/core/context-store/index.js +6 -0
- package/dist/core/context-store/operations.d.ts +85 -0
- package/dist/core/context-store/operations.js +528 -0
- package/dist/core/context-store/registry.d.ts +45 -0
- package/dist/core/context-store/registry.js +229 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/planning-home.js +5 -21
- package/dist/core/validation/validator.d.ts +11 -0
- package/dist/core/validation/validator.js +19 -2
- package/dist/core/workspace/foundation.d.ts +28 -48
- package/dist/core/workspace/foundation.js +130 -214
- package/dist/core/workspace/index.d.ts +2 -0
- package/dist/core/workspace/index.js +2 -0
- package/dist/core/workspace/legacy-state.d.ts +28 -0
- package/dist/core/workspace/legacy-state.js +200 -0
- package/dist/core/workspace/open-surface.d.ts +29 -8
- package/dist/core/workspace/open-surface.js +122 -44
- package/dist/core/workspace/openers.js +11 -6
- package/dist/core/workspace/registry.d.ts +24 -0
- package/dist/core/workspace/registry.js +146 -0
- package/dist/core/workspace/skills.d.ts +4 -2
- package/dist/core/workspace/skills.js +2 -2
- package/dist/core/workspace/state-io.d.ts +10 -0
- package/dist/core/workspace/state-io.js +119 -0
- package/dist/utils/change-metadata.d.ts +5 -2
- package/dist/utils/change-metadata.js +6 -12
- package/dist/utils/change-utils.d.ts +2 -2
- package/package.json +17 -19
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
import * as nodeFs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
1
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
4
2
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
3
|
+
import { normalizeContextStoreBinding, } from '../context-store/index.js';
|
|
6
4
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
7
|
-
const fs = nodeFs.promises;
|
|
8
5
|
export const WORKSPACE_METADATA_DIR_NAME = '.openspec-workspace';
|
|
9
|
-
export const
|
|
10
|
-
export const WORKSPACE_LOCAL_STATE_FILE_NAME = 'local.yaml';
|
|
6
|
+
export const WORKSPACE_VIEW_STATE_FILE_NAME = 'workspace.yaml';
|
|
11
7
|
export const WORKSPACE_CHANGES_DIR_NAME = 'changes';
|
|
12
|
-
export const MANAGED_WORKSPACES_DIR_NAME = 'workspaces';
|
|
13
|
-
export const WORKSPACE_REGISTRY_FILE_NAME = 'registry.yaml';
|
|
14
|
-
export const WORKSPACE_LOCAL_STATE_IGNORE_PATTERN = `${WORKSPACE_METADATA_DIR_NAME}/${WORKSPACE_LOCAL_STATE_FILE_NAME}`;
|
|
15
8
|
export const WORKSPACE_CODE_WORKSPACE_EXTENSION = '.code-workspace';
|
|
16
9
|
export const WORKSPACE_SUPPORTED_OPENER_VALUES = [
|
|
17
|
-
'codex',
|
|
10
|
+
'codex-cli',
|
|
18
11
|
'claude',
|
|
19
12
|
'github-copilot',
|
|
20
13
|
'editor',
|
|
21
14
|
];
|
|
22
15
|
export const WORKSPACE_AGENT_OPENER_IDS = [
|
|
23
|
-
'codex',
|
|
16
|
+
'codex-cli',
|
|
24
17
|
'claude',
|
|
25
18
|
'github-copilot',
|
|
26
19
|
];
|
|
@@ -31,25 +24,12 @@ function joinWorkspacePath(basePath, ...segments) {
|
|
|
31
24
|
export function getWorkspaceMetadataDir(workspaceRoot) {
|
|
32
25
|
return joinWorkspacePath(workspaceRoot, WORKSPACE_METADATA_DIR_NAME);
|
|
33
26
|
}
|
|
34
|
-
export function
|
|
35
|
-
return joinWorkspacePath(
|
|
36
|
-
}
|
|
37
|
-
export function getWorkspaceLocalStatePath(workspaceRoot) {
|
|
38
|
-
return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_LOCAL_STATE_FILE_NAME);
|
|
27
|
+
export function getWorkspaceViewStatePath(workspaceRoot) {
|
|
28
|
+
return joinWorkspacePath(workspaceRoot, WORKSPACE_VIEW_STATE_FILE_NAME);
|
|
39
29
|
}
|
|
40
30
|
export function getWorkspaceChangesDir(workspaceRoot) {
|
|
41
31
|
return joinWorkspacePath(workspaceRoot, WORKSPACE_CHANGES_DIR_NAME);
|
|
42
32
|
}
|
|
43
|
-
export function getManagedWorkspacesDir(options = {}) {
|
|
44
|
-
return joinWorkspacePath(options.globalDataDir ?? getGlobalDataDir(), MANAGED_WORKSPACES_DIR_NAME);
|
|
45
|
-
}
|
|
46
|
-
export function getManagedWorkspaceRoot(workspaceName, options = {}) {
|
|
47
|
-
validateWorkspaceName(workspaceName);
|
|
48
|
-
return joinWorkspacePath(getManagedWorkspacesDir(options), workspaceName);
|
|
49
|
-
}
|
|
50
|
-
export function getWorkspaceRegistryPath(options = {}) {
|
|
51
|
-
return joinWorkspacePath(getManagedWorkspacesDir(options), WORKSPACE_REGISTRY_FILE_NAME);
|
|
52
|
-
}
|
|
53
33
|
export function getWorkspaceCodeWorkspaceFileName(workspaceName) {
|
|
54
34
|
validateWorkspaceName(workspaceName);
|
|
55
35
|
return `${workspaceName}${WORKSPACE_CODE_WORKSPACE_EXTENSION}`;
|
|
@@ -57,10 +37,13 @@ export function getWorkspaceCodeWorkspaceFileName(workspaceName) {
|
|
|
57
37
|
export function getWorkspaceCodeWorkspacePath(workspaceRoot, workspaceName) {
|
|
58
38
|
return joinWorkspacePath(workspaceRoot, getWorkspaceCodeWorkspaceFileName(workspaceName));
|
|
59
39
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 [];
|
|
64
47
|
}
|
|
65
48
|
function validateFolderStyleName(name, label) {
|
|
66
49
|
if (name.length === 0) {
|
|
@@ -102,86 +85,65 @@ export function isValidWorkspaceLinkName(name) {
|
|
|
102
85
|
return false;
|
|
103
86
|
}
|
|
104
87
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return (await fs.stat(filePath)).isFile();
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
async function pathIsDirectory(dirPath) {
|
|
114
|
-
try {
|
|
115
|
-
return (await fs.stat(dirPath)).isDirectory();
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
export async function isWorkspaceRoot(candidateRoot) {
|
|
122
|
-
return pathIsFile(getWorkspaceSharedStatePath(candidateRoot));
|
|
123
|
-
}
|
|
124
|
-
async function getSearchStartDirectory(startPath) {
|
|
125
|
-
const resolvedStart = path.resolve(startPath);
|
|
126
|
-
try {
|
|
127
|
-
const stats = await fs.stat(resolvedStart);
|
|
128
|
-
return stats.isDirectory() ? resolvedStart : path.dirname(resolvedStart);
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return resolvedStart;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
export async function findWorkspaceRoot(startPath = process.cwd()) {
|
|
135
|
-
let currentDir = await getSearchStartDirectory(startPath);
|
|
136
|
-
while (true) {
|
|
137
|
-
if (await isWorkspaceRoot(currentDir)) {
|
|
138
|
-
return process.platform === 'win32'
|
|
139
|
-
? FileSystemUtils.canonicalizeExistingPath(currentDir)
|
|
140
|
-
: currentDir;
|
|
141
|
-
}
|
|
142
|
-
const parentDir = path.dirname(currentDir);
|
|
143
|
-
if (parentDir === currentDir) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
currentDir = parentDir;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function isPlainObject(value) {
|
|
150
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
151
|
-
}
|
|
152
|
-
const PlainObjectSchema = z.custom(isPlainObject, {
|
|
153
|
-
message: 'must be an object',
|
|
154
|
-
});
|
|
155
|
-
const SharedStateSchema = z.object({
|
|
156
|
-
version: z.literal(1),
|
|
157
|
-
name: z.string(),
|
|
158
|
-
links: z.record(z.string(), PlainObjectSchema),
|
|
159
|
-
}).strict();
|
|
160
|
-
const LocalStateSchema = z.object({
|
|
161
|
-
version: z.literal(1),
|
|
162
|
-
paths: z.record(z.string(), z.string()),
|
|
163
|
-
preferred_opener: z
|
|
88
|
+
const ContextStoreSelectorSchema = z.union([
|
|
89
|
+
z
|
|
164
90
|
.object({
|
|
165
|
-
kind: z.
|
|
91
|
+
kind: z.literal('registry'),
|
|
166
92
|
id: z.string(),
|
|
167
93
|
})
|
|
168
|
-
.strict()
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
171
114
|
.object({
|
|
172
|
-
|
|
173
|
-
last_applied_profile: z.enum(['core', 'custom']).optional(),
|
|
174
|
-
last_applied_delivery: z.enum(['both', 'skills', 'commands']).optional(),
|
|
175
|
-
last_applied_workflow_ids: z.array(z.string()).optional(),
|
|
176
|
-
last_applied_at: z.string().optional(),
|
|
115
|
+
id: z.string(),
|
|
177
116
|
})
|
|
178
|
-
.strict()
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
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({
|
|
182
138
|
version: z.literal(1),
|
|
183
|
-
|
|
184
|
-
|
|
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();
|
|
185
147
|
function formatZodIssues(error) {
|
|
186
148
|
return error.issues
|
|
187
149
|
.map((issue) => {
|
|
@@ -213,6 +175,15 @@ function assertValidMapKeys(keys, validator, label) {
|
|
|
213
175
|
function formatSupportedOpenerValues() {
|
|
214
176
|
return WORKSPACE_SUPPORTED_OPENER_VALUES.join(', ');
|
|
215
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
|
+
}
|
|
216
187
|
export function isWorkspaceAgentOpenerId(value) {
|
|
217
188
|
return WORKSPACE_AGENT_OPENER_IDS.includes(value);
|
|
218
189
|
}
|
|
@@ -226,10 +197,11 @@ export function parseWorkspacePreferredOpenerValue(value) {
|
|
|
226
197
|
id: 'vscode',
|
|
227
198
|
};
|
|
228
199
|
}
|
|
229
|
-
|
|
200
|
+
const agentId = normalizeWorkspaceAgentOpenerId(value);
|
|
201
|
+
if (agentId) {
|
|
230
202
|
return {
|
|
231
203
|
kind: 'agent',
|
|
232
|
-
id:
|
|
204
|
+
id: agentId,
|
|
233
205
|
};
|
|
234
206
|
}
|
|
235
207
|
throw new Error(`Unsupported workspace opener '${value}'. Supported values: ${formatSupportedOpenerValues()}`);
|
|
@@ -238,73 +210,73 @@ export function validateWorkspacePreferredOpener(opener) {
|
|
|
238
210
|
if (opener.kind === 'editor' && opener.id === 'vscode') {
|
|
239
211
|
return opener;
|
|
240
212
|
}
|
|
241
|
-
if (opener.kind === 'agent'
|
|
242
|
-
|
|
213
|
+
if (opener.kind === 'agent') {
|
|
214
|
+
const agentId = normalizeWorkspaceAgentOpenerId(opener.id);
|
|
215
|
+
if (agentId) {
|
|
216
|
+
return {
|
|
217
|
+
kind: 'agent',
|
|
218
|
+
id: agentId,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
243
221
|
}
|
|
244
222
|
throw new Error(`Unsupported workspace opener '${opener.kind}:${opener.id}'. Supported values: ${formatSupportedOpenerValues()}`);
|
|
245
223
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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.');
|
|
251
233
|
}
|
|
252
|
-
validateWorkspaceName(result.data.name);
|
|
253
|
-
assertValidMapKeys(Object.keys(result.data.links), validateWorkspaceLinkName, 'workspace link name');
|
|
254
234
|
return {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
235
|
+
kind: 'initiative',
|
|
236
|
+
store: normalizeContextStoreBinding(store),
|
|
237
|
+
initiative: {
|
|
238
|
+
id: initiativeId,
|
|
239
|
+
},
|
|
258
240
|
};
|
|
259
241
|
}
|
|
260
|
-
export function
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
|
263
254
|
if (!result.success) {
|
|
264
|
-
throw new Error(`Invalid workspace
|
|
255
|
+
throw new Error(`Invalid workspace state: ${formatZodIssues(result.error)}`);
|
|
265
256
|
}
|
|
266
|
-
|
|
257
|
+
validateWorkspaceName(result.data.name);
|
|
258
|
+
assertValidMapKeys(Object.keys(result.data.links), validateWorkspaceLinkName, 'workspace link name');
|
|
267
259
|
const preferredOpener = result.data.preferred_opener
|
|
268
260
|
? validateWorkspacePreferredOpener(result.data.preferred_opener)
|
|
269
261
|
: undefined;
|
|
270
262
|
return {
|
|
271
263
|
version: 1,
|
|
272
|
-
|
|
264
|
+
name: result.data.name,
|
|
265
|
+
context: normalizeOptionalWorkspaceContextState(result.data.context),
|
|
266
|
+
links: result.data.links,
|
|
273
267
|
...(preferredOpener ? { preferred_opener: preferredOpener } : {}),
|
|
274
|
-
...(result.data.
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const raw = parseYamlObject(content, 'workspace registry state');
|
|
279
|
-
const result = RegistryStateSchema.safeParse(raw);
|
|
280
|
-
if (!result.success) {
|
|
281
|
-
throw new Error(`Invalid workspace registry state: ${formatZodIssues(result.error)}`);
|
|
282
|
-
}
|
|
283
|
-
assertValidMapKeys(Object.keys(result.data.workspaces), validateWorkspaceName, 'workspace registry name');
|
|
284
|
-
return {
|
|
285
|
-
version: 1,
|
|
286
|
-
workspaces: result.data.workspaces,
|
|
268
|
+
...(result.data.tools ? { tools: result.data.tools } : {}),
|
|
269
|
+
...(result.data.workspace_skills
|
|
270
|
+
? { workspace_skills: result.data.workspace_skills }
|
|
271
|
+
: {}),
|
|
287
272
|
};
|
|
288
273
|
}
|
|
289
|
-
export function
|
|
274
|
+
export function serializeWorkspaceViewState(state) {
|
|
290
275
|
validateWorkspaceName(state.name);
|
|
291
276
|
assertValidMapKeys(Object.keys(state.links), validateWorkspaceLinkName, 'workspace link name');
|
|
292
|
-
for (const [linkName,
|
|
293
|
-
if (
|
|
294
|
-
throw new Error(`Invalid workspace link '${linkName}':
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return stringifyYaml({
|
|
298
|
-
version: 1,
|
|
299
|
-
name: state.name,
|
|
300
|
-
links: state.links,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
export function serializeWorkspaceLocalState(state) {
|
|
304
|
-
assertValidMapKeys(Object.keys(state.paths), validateWorkspaceLinkName, 'workspace local path name');
|
|
305
|
-
for (const [linkName, localPath] of Object.entries(state.paths)) {
|
|
306
|
-
if (typeof localPath !== 'string') {
|
|
307
|
-
throw new Error(`Invalid workspace local path '${linkName}': path must be a string`);
|
|
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`);
|
|
308
280
|
}
|
|
309
281
|
}
|
|
310
282
|
const preferredOpener = state.preferred_opener
|
|
@@ -312,68 +284,12 @@ export function serializeWorkspaceLocalState(state) {
|
|
|
312
284
|
: undefined;
|
|
313
285
|
return stringifyYaml({
|
|
314
286
|
version: 1,
|
|
315
|
-
|
|
287
|
+
name: state.name,
|
|
288
|
+
context: state.context ? normalizeWorkspaceContextState(state.context) : null,
|
|
289
|
+
links: state.links,
|
|
316
290
|
...(preferredOpener ? { preferred_opener: preferredOpener } : {}),
|
|
291
|
+
...(state.tools ? { tools: state.tools } : {}),
|
|
317
292
|
...(state.workspace_skills ? { workspace_skills: state.workspace_skills } : {}),
|
|
318
293
|
});
|
|
319
294
|
}
|
|
320
|
-
export function serializeWorkspaceRegistryState(state) {
|
|
321
|
-
assertValidMapKeys(Object.keys(state.workspaces), validateWorkspaceName, 'workspace registry name');
|
|
322
|
-
for (const [workspaceName, workspaceRoot] of Object.entries(state.workspaces)) {
|
|
323
|
-
if (typeof workspaceRoot !== 'string') {
|
|
324
|
-
throw new Error(`Invalid workspace registry entry '${workspaceName}': path must be a string`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return stringifyYaml({
|
|
328
|
-
version: 1,
|
|
329
|
-
workspaces: state.workspaces,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
export function listWorkspaceRegistryEntries(registry) {
|
|
333
|
-
return Object.entries(registry.workspaces)
|
|
334
|
-
.map(([name, workspaceRoot]) => ({ name, workspaceRoot }))
|
|
335
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
336
|
-
}
|
|
337
|
-
export async function readWorkspaceSharedState(workspaceRoot) {
|
|
338
|
-
return parseWorkspaceSharedState(await fs.readFile(getWorkspaceSharedStatePath(workspaceRoot), 'utf-8'));
|
|
339
|
-
}
|
|
340
|
-
export async function readWorkspaceLocalState(workspaceRoot) {
|
|
341
|
-
return parseWorkspaceLocalState(await fs.readFile(getWorkspaceLocalStatePath(workspaceRoot), 'utf-8'));
|
|
342
|
-
}
|
|
343
|
-
function isFileNotFoundError(error) {
|
|
344
|
-
return (typeof error === 'object' &&
|
|
345
|
-
error !== null &&
|
|
346
|
-
'code' in error &&
|
|
347
|
-
error.code === 'ENOENT');
|
|
348
|
-
}
|
|
349
|
-
export async function readOptionalWorkspaceLocalState(workspaceRoot) {
|
|
350
|
-
try {
|
|
351
|
-
return await readWorkspaceLocalState(workspaceRoot);
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
if (isFileNotFoundError(error)) {
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
throw error;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
export async function writeWorkspaceSharedState(workspaceRoot, state) {
|
|
361
|
-
await FileSystemUtils.writeFile(getWorkspaceSharedStatePath(workspaceRoot), serializeWorkspaceSharedState(state));
|
|
362
|
-
}
|
|
363
|
-
export async function writeWorkspaceLocalState(workspaceRoot, state) {
|
|
364
|
-
await FileSystemUtils.writeFile(getWorkspaceLocalStatePath(workspaceRoot), serializeWorkspaceLocalState(state));
|
|
365
|
-
}
|
|
366
|
-
export async function readWorkspaceRegistryState(options = {}) {
|
|
367
|
-
const registryPath = getWorkspaceRegistryPath(options);
|
|
368
|
-
if (!(await pathIsFile(registryPath))) {
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
return parseWorkspaceRegistryState(await fs.readFile(registryPath, 'utf-8'));
|
|
372
|
-
}
|
|
373
|
-
export async function writeWorkspaceRegistryState(state, options = {}) {
|
|
374
|
-
await FileSystemUtils.writeFile(getWorkspaceRegistryPath(options), serializeWorkspaceRegistryState(state));
|
|
375
|
-
}
|
|
376
|
-
export async function workspaceChangesDirExists(workspaceRoot) {
|
|
377
|
-
return pathIsDirectory(getWorkspaceChangesDir(workspaceRoot));
|
|
378
|
-
}
|
|
379
295
|
//# sourceMappingURL=foundation.js.map
|
|
@@ -2,5 +2,7 @@ export * from './foundation.js';
|
|
|
2
2
|
export * from './link-input.js';
|
|
3
3
|
export * from './openers.js';
|
|
4
4
|
export * from './open-surface.js';
|
|
5
|
+
export * from './registry.js';
|
|
5
6
|
export * from './skills.js';
|
|
7
|
+
export * from './state-io.js';
|
|
6
8
|
//# sourceMappingURL=index.d.ts.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
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { WORKSPACE_METADATA_DIR_NAME, WORKSPACE_VIEW_STATE_FILE_NAME, getWorkspaceMetadataDir, parseWorkspaceViewState, validateWorkspaceLinkName, validateWorkspaceName, validateWorkspacePreferredOpener, } from './foundation.js';
|
|
4
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
|
+
export const WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME = WORKSPACE_VIEW_STATE_FILE_NAME;
|
|
6
|
+
export const WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME = 'local.yaml';
|
|
7
|
+
export const WORKSPACE_LEGACY_LOCAL_STATE_IGNORE_PATTERN = `${WORKSPACE_METADATA_DIR_NAME}/${WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME}`;
|
|
8
|
+
function joinWorkspacePath(basePath, ...segments) {
|
|
9
|
+
return FileSystemUtils.joinPath(basePath, ...segments);
|
|
10
|
+
}
|
|
11
|
+
export function getWorkspaceLegacySharedStatePath(workspaceRoot) {
|
|
12
|
+
return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME);
|
|
13
|
+
}
|
|
14
|
+
export function getWorkspaceLegacyLocalStatePath(workspaceRoot) {
|
|
15
|
+
return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME);
|
|
16
|
+
}
|
|
17
|
+
function isPlainObject(value) {
|
|
18
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
const PlainObjectSchema = z.custom(isPlainObject, {
|
|
21
|
+
message: 'must be an object',
|
|
22
|
+
});
|
|
23
|
+
const PreferredOpenerSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
kind: z.enum(['agent', 'editor']),
|
|
26
|
+
id: z.string(),
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
const WorkspaceSkillStateSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
selected_agents: z.array(z.string()),
|
|
32
|
+
last_applied_profile: z.enum(['core', 'custom']).optional(),
|
|
33
|
+
last_applied_delivery: z.enum(['both', 'skills', 'commands']).optional(),
|
|
34
|
+
last_applied_workflow_ids: z.array(z.string()).optional(),
|
|
35
|
+
last_applied_at: z.string().optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
const SharedStateSchema = z.object({
|
|
39
|
+
version: z.literal(1),
|
|
40
|
+
name: z.string(),
|
|
41
|
+
context: z.unknown().optional(),
|
|
42
|
+
links: z.record(z.string(), PlainObjectSchema),
|
|
43
|
+
}).strict();
|
|
44
|
+
const LocalStateSchema = z.object({
|
|
45
|
+
version: z.literal(1),
|
|
46
|
+
paths: z.record(z.string(), z.string()),
|
|
47
|
+
preferred_opener: PreferredOpenerSchema.optional(),
|
|
48
|
+
tools: z.array(z.string()).optional(),
|
|
49
|
+
workspace_skills: WorkspaceSkillStateSchema.optional(),
|
|
50
|
+
}).strict();
|
|
51
|
+
function formatZodIssues(error) {
|
|
52
|
+
return error.issues
|
|
53
|
+
.map((issue) => {
|
|
54
|
+
const location = issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
55
|
+
return `${location}: ${issue.message}`;
|
|
56
|
+
})
|
|
57
|
+
.join('; ');
|
|
58
|
+
}
|
|
59
|
+
function parseYamlObject(content, label) {
|
|
60
|
+
try {
|
|
61
|
+
return parseYaml(content);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
+
throw new Error(`Invalid ${label}: ${message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function assertValidMapKeys(keys, validator, label) {
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
try {
|
|
71
|
+
validator(key);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
throw new Error(`Invalid ${label} '${key}': ${message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function normalizeLegacyWorkspaceContext(name, context) {
|
|
80
|
+
return parseWorkspaceViewState(stringifyYaml({
|
|
81
|
+
version: 1,
|
|
82
|
+
name,
|
|
83
|
+
context: context ?? null,
|
|
84
|
+
links: {},
|
|
85
|
+
})).context;
|
|
86
|
+
}
|
|
87
|
+
export function workspaceViewToSharedState(state) {
|
|
88
|
+
return {
|
|
89
|
+
version: 1,
|
|
90
|
+
name: state.name,
|
|
91
|
+
context: state.context,
|
|
92
|
+
links: Object.fromEntries(Object.keys(state.links).map((linkName) => [linkName, {}])),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function workspaceViewToLocalState(state) {
|
|
96
|
+
return {
|
|
97
|
+
version: 1,
|
|
98
|
+
paths: Object.fromEntries(Object.entries(state.links).filter((entry) => typeof entry[1] === 'string')),
|
|
99
|
+
...(state.preferred_opener ? { preferred_opener: state.preferred_opener } : {}),
|
|
100
|
+
...(state.tools ? { tools: state.tools } : {}),
|
|
101
|
+
...(state.workspace_skills ? { workspace_skills: state.workspace_skills } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function workspaceStatePartsToViewState(sharedState, localState) {
|
|
105
|
+
const linkNames = new Set([
|
|
106
|
+
...Object.keys(sharedState.links),
|
|
107
|
+
...Object.keys(localState?.paths ?? {}),
|
|
108
|
+
]);
|
|
109
|
+
const links = Object.fromEntries([...linkNames]
|
|
110
|
+
.sort((a, b) => a.localeCompare(b))
|
|
111
|
+
.map((linkName) => [linkName, localState?.paths[linkName] ?? null]));
|
|
112
|
+
return {
|
|
113
|
+
version: 1,
|
|
114
|
+
name: sharedState.name,
|
|
115
|
+
context: sharedState.context,
|
|
116
|
+
links,
|
|
117
|
+
...(localState?.preferred_opener ? { preferred_opener: localState.preferred_opener } : {}),
|
|
118
|
+
...(localState?.tools ? { tools: localState.tools } : {}),
|
|
119
|
+
...(localState?.workspace_skills ? { workspace_skills: localState.workspace_skills } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function parseWorkspaceSharedState(content) {
|
|
123
|
+
const raw = parseYamlObject(content, 'workspace shared state');
|
|
124
|
+
try {
|
|
125
|
+
return workspaceViewToSharedState(parseWorkspaceViewState(content));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Fall through to the legacy shared schema.
|
|
129
|
+
}
|
|
130
|
+
const result = SharedStateSchema.safeParse(raw);
|
|
131
|
+
if (!result.success) {
|
|
132
|
+
throw new Error(`Invalid workspace shared state: ${formatZodIssues(result.error)}`);
|
|
133
|
+
}
|
|
134
|
+
validateWorkspaceName(result.data.name);
|
|
135
|
+
assertValidMapKeys(Object.keys(result.data.links), validateWorkspaceLinkName, 'workspace link name');
|
|
136
|
+
return {
|
|
137
|
+
version: 1,
|
|
138
|
+
name: result.data.name,
|
|
139
|
+
context: normalizeLegacyWorkspaceContext(result.data.name, result.data.context),
|
|
140
|
+
links: result.data.links,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function parseWorkspaceLocalState(content) {
|
|
144
|
+
const raw = parseYamlObject(content, 'workspace local state');
|
|
145
|
+
try {
|
|
146
|
+
return workspaceViewToLocalState(parseWorkspaceViewState(content));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Fall through to the legacy local schema.
|
|
150
|
+
}
|
|
151
|
+
const result = LocalStateSchema.safeParse(raw);
|
|
152
|
+
if (!result.success) {
|
|
153
|
+
throw new Error(`Invalid workspace local state: ${formatZodIssues(result.error)}`);
|
|
154
|
+
}
|
|
155
|
+
assertValidMapKeys(Object.keys(result.data.paths), validateWorkspaceLinkName, 'workspace local path name');
|
|
156
|
+
const preferredOpener = result.data.preferred_opener
|
|
157
|
+
? validateWorkspacePreferredOpener(result.data.preferred_opener)
|
|
158
|
+
: undefined;
|
|
159
|
+
return {
|
|
160
|
+
version: 1,
|
|
161
|
+
paths: result.data.paths,
|
|
162
|
+
...(preferredOpener ? { preferred_opener: preferredOpener } : {}),
|
|
163
|
+
...(result.data.tools ? { tools: result.data.tools } : {}),
|
|
164
|
+
...(result.data.workspace_skills ? { workspace_skills: result.data.workspace_skills } : {}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export function serializeWorkspaceSharedState(state) {
|
|
168
|
+
validateWorkspaceName(state.name);
|
|
169
|
+
assertValidMapKeys(Object.keys(state.links), validateWorkspaceLinkName, 'workspace link name');
|
|
170
|
+
for (const [linkName, linkState] of Object.entries(state.links)) {
|
|
171
|
+
if (!isPlainObject(linkState)) {
|
|
172
|
+
throw new Error(`Invalid workspace link '${linkName}': link state must be an object`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return stringifyYaml({
|
|
176
|
+
version: 1,
|
|
177
|
+
name: state.name,
|
|
178
|
+
context: state.context,
|
|
179
|
+
links: state.links,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export function serializeWorkspaceLocalState(state) {
|
|
183
|
+
assertValidMapKeys(Object.keys(state.paths), validateWorkspaceLinkName, 'workspace local path name');
|
|
184
|
+
for (const [linkName, localPath] of Object.entries(state.paths)) {
|
|
185
|
+
if (typeof localPath !== 'string') {
|
|
186
|
+
throw new Error(`Invalid workspace local path '${linkName}': path must be a string`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const preferredOpener = state.preferred_opener
|
|
190
|
+
? validateWorkspacePreferredOpener(state.preferred_opener)
|
|
191
|
+
: undefined;
|
|
192
|
+
return stringifyYaml({
|
|
193
|
+
version: 1,
|
|
194
|
+
paths: state.paths,
|
|
195
|
+
...(preferredOpener ? { preferred_opener: preferredOpener } : {}),
|
|
196
|
+
...(state.tools ? { tools: state.tools } : {}),
|
|
197
|
+
...(state.workspace_skills ? { workspace_skills: state.workspace_skills } : {}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=legacy-state.js.map
|