gmc-openspec 1.1.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 +1 -1
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WorkspaceViewState } from './foundation.js';
|
|
2
2
|
export declare const WORKSPACE_GUIDANCE_START_MARKER = "<!-- OPENSPEC:WORKSPACE-GUIDANCE:START -->";
|
|
3
3
|
export declare const WORKSPACE_GUIDANCE_END_MARKER = "<!-- OPENSPEC:WORKSPACE-GUIDANCE:END -->";
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const WORKSPACE_OPEN_ROOT_FOLDER_LABEL = "OpenSpec workspace";
|
|
5
|
+
export declare const WORKSPACE_OPEN_INITIATIVE_FOLDER_LABEL = "Initiative context";
|
|
6
|
+
export declare const WORKSPACE_GUIDANCE_BODY = "# OpenSpec Workspace Guidance\n\nThis directory is an OpenSpec workspace: a local working view over context stores, initiatives, repos, and folders.\n\n- Use this workspace to open the local view of coordinated work.\n- Use initiatives for durable cross-team or cross-repo intent, decisions, requirements, and coordination context.\n- Use repo-local OpenSpec changes for implementation plans owned by a repo or team.\n- Use linked repos and folders to inspect context, understand ownership, and make edits in the place that owns the work.\n- Keep workspace-local files focused on local paths, opener state, agent setup, and other machine-specific view state.\n- Use OpenSpec workspace commands instead of hand-editing `workspace.yaml`.\n- If this workspace contains legacy or beta workspace-level planning files, treat them as compatibility context unless the user explicitly asks to use that beta flow.";
|
|
7
|
+
export interface WorkspaceOpenResolvedContext {
|
|
8
|
+
contextStore: {
|
|
9
|
+
id: string;
|
|
10
|
+
root: string;
|
|
11
|
+
};
|
|
12
|
+
initiative: {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
root: string;
|
|
16
|
+
metadataPath: string;
|
|
17
|
+
storePath: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
5
20
|
export interface WorkspaceOpenLink {
|
|
6
21
|
name: string;
|
|
7
22
|
path: string;
|
|
@@ -15,10 +30,16 @@ export interface WorkspaceOpenSurfaceLinks {
|
|
|
15
30
|
links: WorkspaceOpenLink[];
|
|
16
31
|
skipped: WorkspaceSkippedOpenLink[];
|
|
17
32
|
}
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export declare function
|
|
23
|
-
export declare function
|
|
33
|
+
export interface WorkspaceOpenSurfaceGeneration {
|
|
34
|
+
agentsPath: string;
|
|
35
|
+
codeWorkspacePath: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function buildWorkspaceGuidanceBlock(viewState?: WorkspaceViewState, resolvedContext?: WorkspaceOpenResolvedContext | null): string;
|
|
38
|
+
export declare function applyWorkspaceGuidanceBlock(existingContent: string, viewState?: WorkspaceViewState, resolvedContext?: WorkspaceOpenResolvedContext | null): string;
|
|
39
|
+
export declare function buildWorkspaceCodeWorkspaceContent(links: WorkspaceOpenLink[], resolvedContext?: WorkspaceOpenResolvedContext | null): string;
|
|
40
|
+
export declare function writeWorkspaceCodeWorkspaceFile(codeWorkspacePath: string, links: WorkspaceOpenLink[], resolvedContext?: WorkspaceOpenResolvedContext | null): Promise<void>;
|
|
41
|
+
export declare function resolveWorkspaceOpenLinks(viewState: WorkspaceViewState): Promise<WorkspaceOpenSurfaceLinks>;
|
|
42
|
+
export declare function syncWorkspaceOpenSurface(workspaceRoot: string, viewState: WorkspaceViewState, resolvedContext?: WorkspaceOpenResolvedContext | null): Promise<WorkspaceOpenSurfaceLinks & {
|
|
43
|
+
generated: WorkspaceOpenSurfaceGeneration;
|
|
44
|
+
}>;
|
|
24
45
|
//# sourceMappingURL=open-surface.d.ts.map
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import * as nodeFs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
4
|
-
import { getWorkspaceCodeWorkspacePath,
|
|
4
|
+
import { getWorkspaceContextInitiativeId, getWorkspaceCodeWorkspacePath, getWorkspaceCodeWorkspaceFileName, } from './foundation.js';
|
|
5
5
|
const fs = nodeFs.promises;
|
|
6
6
|
export const WORKSPACE_GUIDANCE_START_MARKER = '<!-- OPENSPEC:WORKSPACE-GUIDANCE:START -->';
|
|
7
7
|
export const WORKSPACE_GUIDANCE_END_MARKER = '<!-- OPENSPEC:WORKSPACE-GUIDANCE:END -->';
|
|
8
|
+
export const WORKSPACE_OPEN_ROOT_FOLDER_LABEL = 'OpenSpec workspace';
|
|
9
|
+
export const WORKSPACE_OPEN_INITIATIVE_FOLDER_LABEL = 'Initiative context';
|
|
8
10
|
export const WORKSPACE_GUIDANCE_BODY = `# OpenSpec Workspace Guidance
|
|
9
11
|
|
|
10
|
-
This directory is an OpenSpec workspace
|
|
12
|
+
This directory is an OpenSpec workspace: a local working view over context stores, initiatives, repos, and folders.
|
|
11
13
|
|
|
12
|
-
- Use
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- Use OpenSpec workspace commands instead of hand-editing
|
|
14
|
+
- Use this workspace to open the local view of coordinated work.
|
|
15
|
+
- Use initiatives for durable cross-team or cross-repo intent, decisions, requirements, and coordination context.
|
|
16
|
+
- Use repo-local OpenSpec changes for implementation plans owned by a repo or team.
|
|
17
|
+
- Use linked repos and folders to inspect context, understand ownership, and make edits in the place that owns the work.
|
|
18
|
+
- Keep workspace-local files focused on local paths, opener state, agent setup, and other machine-specific view state.
|
|
19
|
+
- Use OpenSpec workspace commands instead of hand-editing \`workspace.yaml\`.
|
|
20
|
+
- If this workspace contains legacy or beta workspace-level planning files, treat them as compatibility context unless the user explicitly asks to use that beta flow.`;
|
|
18
21
|
async function fileExists(filePath) {
|
|
19
22
|
try {
|
|
20
23
|
return (await fs.stat(filePath)).isFile();
|
|
@@ -31,13 +34,72 @@ async function directoryExists(dirPath) {
|
|
|
31
34
|
return false;
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
|
-
|
|
37
|
+
function formatGuidancePathList(items) {
|
|
38
|
+
if (items.length === 0) {
|
|
39
|
+
return '- None selected yet.';
|
|
40
|
+
}
|
|
41
|
+
return items.map((item) => `- ${item.label}: ${item.path}`).join('\n');
|
|
42
|
+
}
|
|
43
|
+
function buildWorkspaceContextGuidance(viewState, resolvedContext) {
|
|
44
|
+
const linkedRoots = Object.entries(viewState.links)
|
|
45
|
+
.filter((entry) => typeof entry[1] === 'string')
|
|
46
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
47
|
+
.map(([name, linkPath]) => ({ label: name, path: linkPath }));
|
|
48
|
+
if (!viewState.context) {
|
|
49
|
+
return `## Local View
|
|
50
|
+
|
|
51
|
+
This workspace is not bound to an initiative. It is still a first-class local view over selected repos or folders.
|
|
52
|
+
|
|
53
|
+
## Linked Implementation Context
|
|
54
|
+
|
|
55
|
+
${formatGuidancePathList(linkedRoots)}`;
|
|
56
|
+
}
|
|
57
|
+
const storedContextSelector = viewState.context.store.selector;
|
|
58
|
+
const storedContextStore = viewState.context
|
|
59
|
+
? storedContextSelector?.kind === 'path'
|
|
60
|
+
? `${viewState.context.store.id} via ${storedContextSelector.path}`
|
|
61
|
+
: viewState.context.store.id
|
|
62
|
+
: null;
|
|
63
|
+
const storedInitiativeId = viewState.context
|
|
64
|
+
? getWorkspaceContextInitiativeId(viewState.context)
|
|
65
|
+
: null;
|
|
66
|
+
const contextLines = resolvedContext
|
|
67
|
+
? [
|
|
68
|
+
`- Context store: ${resolvedContext.contextStore.id} (${resolvedContext.contextStore.root})`,
|
|
69
|
+
`- Initiative: ${resolvedContext.initiative.id} (${resolvedContext.initiative.root})`,
|
|
70
|
+
`- Initiative title: ${resolvedContext.initiative.title}`,
|
|
71
|
+
`- Initiative metadata: ${resolvedContext.initiative.metadataPath}`,
|
|
72
|
+
'- Broader context may exist in the context store, but this workspace opens the selected initiative by default.',
|
|
73
|
+
].join('\n')
|
|
74
|
+
: [
|
|
75
|
+
`- Context store: ${storedContextStore}`,
|
|
76
|
+
`- Initiative: ${storedInitiativeId}`,
|
|
77
|
+
'- Run `openspec workspace open --json` to refresh resolved local paths for this view.',
|
|
78
|
+
].join('\n');
|
|
79
|
+
return `## Selected Initiative Context
|
|
80
|
+
|
|
81
|
+
${contextLines}
|
|
82
|
+
|
|
83
|
+
## Advisory Edit Boundaries
|
|
84
|
+
|
|
85
|
+
- Treat initiative and context-store files as shared coordination context.
|
|
86
|
+
- Treat linked repos and folders as local implementation context when the user has selected them.
|
|
87
|
+
- These boundaries are advisory in this OpenSpec version; use judgment and repo ownership when editing.
|
|
88
|
+
|
|
89
|
+
## Linked Implementation Context
|
|
90
|
+
|
|
91
|
+
${formatGuidancePathList(linkedRoots)}`;
|
|
92
|
+
}
|
|
93
|
+
export function buildWorkspaceGuidanceBlock(viewState, resolvedContext) {
|
|
94
|
+
const contextGuidance = viewState
|
|
95
|
+
? `\n\n${buildWorkspaceContextGuidance(viewState, resolvedContext)}`
|
|
96
|
+
: '';
|
|
35
97
|
return `${WORKSPACE_GUIDANCE_START_MARKER}
|
|
36
|
-
${WORKSPACE_GUIDANCE_BODY}
|
|
98
|
+
${WORKSPACE_GUIDANCE_BODY}${contextGuidance}
|
|
37
99
|
${WORKSPACE_GUIDANCE_END_MARKER}`;
|
|
38
100
|
}
|
|
39
|
-
export function applyWorkspaceGuidanceBlock(existingContent) {
|
|
40
|
-
const block = buildWorkspaceGuidanceBlock();
|
|
101
|
+
export function applyWorkspaceGuidanceBlock(existingContent, viewState, resolvedContext) {
|
|
102
|
+
const block = buildWorkspaceGuidanceBlock(viewState, resolvedContext);
|
|
41
103
|
const startIndex = existingContent.indexOf(WORKSPACE_GUIDANCE_START_MARKER);
|
|
42
104
|
const endIndex = existingContent.indexOf(WORKSPACE_GUIDANCE_END_MARKER);
|
|
43
105
|
if (startIndex !== -1 || endIndex !== -1) {
|
|
@@ -57,26 +119,35 @@ export function applyWorkspaceGuidanceBlock(existingContent) {
|
|
|
57
119
|
}
|
|
58
120
|
return `${existingContent.trimEnd()}\n\n${block}\n`;
|
|
59
121
|
}
|
|
60
|
-
export function buildWorkspaceCodeWorkspaceContent(links) {
|
|
122
|
+
export function buildWorkspaceCodeWorkspaceContent(links, resolvedContext) {
|
|
61
123
|
const folders = [
|
|
62
|
-
{
|
|
63
|
-
path: '.',
|
|
64
|
-
},
|
|
65
124
|
...links.map((link) => ({
|
|
66
125
|
name: link.name,
|
|
67
126
|
path: link.path,
|
|
68
127
|
})),
|
|
128
|
+
...(resolvedContext
|
|
129
|
+
? [
|
|
130
|
+
{
|
|
131
|
+
name: WORKSPACE_OPEN_INITIATIVE_FOLDER_LABEL,
|
|
132
|
+
path: resolvedContext.initiative.root,
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
: []),
|
|
136
|
+
{
|
|
137
|
+
name: WORKSPACE_OPEN_ROOT_FOLDER_LABEL,
|
|
138
|
+
path: '.',
|
|
139
|
+
},
|
|
69
140
|
];
|
|
70
141
|
return `${JSON.stringify({ folders }, null, 2)}\n`;
|
|
71
142
|
}
|
|
72
|
-
export async function writeWorkspaceCodeWorkspaceFile(codeWorkspacePath, links) {
|
|
73
|
-
await FileSystemUtils.writeFile(codeWorkspacePath, buildWorkspaceCodeWorkspaceContent(links));
|
|
143
|
+
export async function writeWorkspaceCodeWorkspaceFile(codeWorkspacePath, links, resolvedContext) {
|
|
144
|
+
await FileSystemUtils.writeFile(codeWorkspacePath, buildWorkspaceCodeWorkspaceContent(links, resolvedContext));
|
|
74
145
|
}
|
|
75
|
-
export async function resolveWorkspaceOpenLinks(
|
|
146
|
+
export async function resolveWorkspaceOpenLinks(viewState) {
|
|
76
147
|
const links = [];
|
|
77
148
|
const skipped = [];
|
|
78
|
-
for (const linkName of Object.keys(
|
|
79
|
-
const localPath =
|
|
149
|
+
for (const linkName of Object.keys(viewState.links).sort((a, b) => a.localeCompare(b))) {
|
|
150
|
+
const localPath = viewState.links[linkName] ?? null;
|
|
80
151
|
if (!localPath) {
|
|
81
152
|
skipped.push({
|
|
82
153
|
name: linkName,
|
|
@@ -100,38 +171,45 @@ export async function resolveWorkspaceOpenLinks(sharedState, localState) {
|
|
|
100
171
|
}
|
|
101
172
|
return { links, skipped };
|
|
102
173
|
}
|
|
103
|
-
async function syncWorkspaceGuidance(workspaceRoot) {
|
|
174
|
+
async function syncWorkspaceGuidance(workspaceRoot, viewState, resolvedContext) {
|
|
104
175
|
const agentsPath = path.join(workspaceRoot, 'AGENTS.md');
|
|
105
176
|
const existingContent = (await fileExists(agentsPath))
|
|
106
177
|
? await fs.readFile(agentsPath, 'utf-8')
|
|
107
178
|
: '';
|
|
108
|
-
await FileSystemUtils.writeFile(agentsPath, applyWorkspaceGuidanceBlock(existingContent));
|
|
179
|
+
await FileSystemUtils.writeFile(agentsPath, applyWorkspaceGuidanceBlock(existingContent, viewState, resolvedContext));
|
|
180
|
+
return agentsPath;
|
|
109
181
|
}
|
|
110
|
-
async function syncWorkspaceCodeWorkspace(workspaceRoot,
|
|
111
|
-
|
|
182
|
+
async function syncWorkspaceCodeWorkspace(workspaceRoot, viewState, links, resolvedContext) {
|
|
183
|
+
const codeWorkspacePath = getWorkspaceCodeWorkspacePath(workspaceRoot, viewState.name);
|
|
184
|
+
await writeWorkspaceCodeWorkspaceFile(codeWorkspacePath, links, resolvedContext);
|
|
185
|
+
return codeWorkspacePath;
|
|
112
186
|
}
|
|
113
|
-
async function
|
|
187
|
+
async function cleanupLegacyWorkspaceIgnoreRules(workspaceRoot, workspaceName) {
|
|
114
188
|
const gitignorePath = path.join(workspaceRoot, '.gitignore');
|
|
115
|
-
|
|
116
|
-
const existingContent = (await fileExists(gitignorePath))
|
|
117
|
-
? await fs.readFile(gitignorePath, 'utf-8')
|
|
118
|
-
: '';
|
|
119
|
-
const existingLines = new Set(existingContent
|
|
120
|
-
.split(/\r?\n/u)
|
|
121
|
-
.map((line) => line.trim())
|
|
122
|
-
.filter((line) => line.length > 0));
|
|
123
|
-
const missingPatterns = patterns.filter((pattern) => !existingLines.has(pattern));
|
|
124
|
-
if (missingPatterns.length === 0) {
|
|
189
|
+
if (!(await fileExists(gitignorePath))) {
|
|
125
190
|
return;
|
|
126
191
|
}
|
|
127
|
-
const
|
|
128
|
-
await
|
|
192
|
+
const legacyGeneratedPattern = getWorkspaceCodeWorkspaceFileName(workspaceName);
|
|
193
|
+
const existingContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
194
|
+
const existingLines = existingContent.split(/\r?\n/u);
|
|
195
|
+
const nonEmptyLines = existingLines.filter((line) => line.trim().length > 0);
|
|
196
|
+
const isPureLegacyGeneratedFile = nonEmptyLines.length === 1 && nonEmptyLines[0]?.trim() === legacyGeneratedPattern;
|
|
197
|
+
if (!isPureLegacyGeneratedFile) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await fs.rm(gitignorePath, { force: true });
|
|
129
201
|
}
|
|
130
|
-
export async function syncWorkspaceOpenSurface(workspaceRoot,
|
|
131
|
-
const openLinks = await resolveWorkspaceOpenLinks(
|
|
132
|
-
await syncWorkspaceGuidance(workspaceRoot);
|
|
133
|
-
await syncWorkspaceCodeWorkspace(workspaceRoot,
|
|
134
|
-
await
|
|
135
|
-
return
|
|
202
|
+
export async function syncWorkspaceOpenSurface(workspaceRoot, viewState, resolvedContext) {
|
|
203
|
+
const openLinks = await resolveWorkspaceOpenLinks(viewState);
|
|
204
|
+
const agentsPath = await syncWorkspaceGuidance(workspaceRoot, viewState, resolvedContext);
|
|
205
|
+
const codeWorkspacePath = await syncWorkspaceCodeWorkspace(workspaceRoot, viewState, openLinks.links, resolvedContext);
|
|
206
|
+
await cleanupLegacyWorkspaceIgnoreRules(workspaceRoot, viewState.name);
|
|
207
|
+
return {
|
|
208
|
+
...openLinks,
|
|
209
|
+
generated: {
|
|
210
|
+
agentsPath,
|
|
211
|
+
codeWorkspacePath,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
136
214
|
}
|
|
137
215
|
//# sourceMappingURL=open-surface.js.map
|
|
@@ -9,8 +9,8 @@ const WORKSPACE_OPENER_CHOICE_DEFINITIONS = [
|
|
|
9
9
|
executable: 'code',
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
|
-
value: 'codex',
|
|
13
|
-
label: '
|
|
12
|
+
value: 'codex-cli',
|
|
13
|
+
label: 'codex-cli',
|
|
14
14
|
executable: 'codex',
|
|
15
15
|
},
|
|
16
16
|
{
|
|
@@ -74,23 +74,28 @@ export function isWorkspaceExecutableAvailable(executable, options = {}) {
|
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
export function getWorkspaceOpenerExecutable(opener) {
|
|
77
|
+
const openerId = opener.id;
|
|
77
78
|
if (opener.kind === 'editor') {
|
|
78
79
|
return 'code';
|
|
79
80
|
}
|
|
80
|
-
if (
|
|
81
|
+
if (openerId === 'github-copilot') {
|
|
81
82
|
return 'code';
|
|
82
83
|
}
|
|
84
|
+
if (openerId === 'codex-cli' || openerId === 'codex') {
|
|
85
|
+
return 'codex';
|
|
86
|
+
}
|
|
83
87
|
return opener.id;
|
|
84
88
|
}
|
|
85
89
|
export function getWorkspaceOpenerLabel(opener) {
|
|
90
|
+
const openerId = opener.id;
|
|
86
91
|
if (opener.kind === 'editor') {
|
|
87
92
|
return 'VS Code editor';
|
|
88
93
|
}
|
|
89
|
-
if (
|
|
94
|
+
if (openerId === 'github-copilot') {
|
|
90
95
|
return 'GitHub Copilot in VS Code';
|
|
91
96
|
}
|
|
92
|
-
if (
|
|
93
|
-
return '
|
|
97
|
+
if (openerId === 'codex-cli' || openerId === 'codex') {
|
|
98
|
+
return 'codex-cli';
|
|
94
99
|
}
|
|
95
100
|
return 'Claude';
|
|
96
101
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const MANAGED_WORKSPACES_DIR_NAME = "workspaces";
|
|
2
|
+
export declare const WORKSPACE_REGISTRY_FILE_NAME = "registry.yaml";
|
|
3
|
+
export interface WorkspaceRegistryState {
|
|
4
|
+
version: 1;
|
|
5
|
+
workspaces: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export interface WorkspaceRegistryEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
workspaceRoot: string;
|
|
10
|
+
}
|
|
11
|
+
export interface WorkspacePathOptions {
|
|
12
|
+
globalDataDir?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function getManagedWorkspacesDir(options?: WorkspacePathOptions): string;
|
|
15
|
+
export declare function getManagedWorkspaceRoot(workspaceName: string, options?: WorkspacePathOptions): string;
|
|
16
|
+
export declare function getWorkspaceRegistryPath(options?: WorkspacePathOptions): string;
|
|
17
|
+
export declare function parseWorkspaceRegistryState(content: string): WorkspaceRegistryState;
|
|
18
|
+
export declare function serializeWorkspaceRegistryState(state: WorkspaceRegistryState): string;
|
|
19
|
+
export declare function listWorkspaceRegistryEntries(registry: WorkspaceRegistryState): WorkspaceRegistryEntry[];
|
|
20
|
+
export declare function listKnownWorkspaceEntries(options?: WorkspacePathOptions): Promise<WorkspaceRegistryEntry[]>;
|
|
21
|
+
export declare function listManagedWorkspaceEntries(options?: WorkspacePathOptions): Promise<WorkspaceRegistryEntry[]>;
|
|
22
|
+
export declare function readWorkspaceRegistryState(options?: WorkspacePathOptions): Promise<WorkspaceRegistryState | null>;
|
|
23
|
+
export declare function writeWorkspaceRegistryState(state: WorkspaceRegistryState, options?: WorkspacePathOptions): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
4
|
+
import { getGlobalDataDir } from '../global-config.js';
|
|
5
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
6
|
+
import { validateWorkspaceName } from './foundation.js';
|
|
7
|
+
import { isWorkspaceRoot, readWorkspaceViewState } from './state-io.js';
|
|
8
|
+
const fs = nodeFs.promises;
|
|
9
|
+
export const MANAGED_WORKSPACES_DIR_NAME = 'workspaces';
|
|
10
|
+
export const WORKSPACE_REGISTRY_FILE_NAME = 'registry.yaml';
|
|
11
|
+
function joinWorkspacePath(basePath, ...segments) {
|
|
12
|
+
return FileSystemUtils.joinPath(basePath, ...segments);
|
|
13
|
+
}
|
|
14
|
+
async function pathIsFile(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
return (await fs.stat(filePath)).isFile();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function pathIsDirectory(dirPath) {
|
|
23
|
+
try {
|
|
24
|
+
return (await fs.stat(dirPath)).isDirectory();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function formatZodIssues(error) {
|
|
31
|
+
return error.issues
|
|
32
|
+
.map((issue) => {
|
|
33
|
+
const location = issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
34
|
+
return `${location}: ${issue.message}`;
|
|
35
|
+
})
|
|
36
|
+
.join('; ');
|
|
37
|
+
}
|
|
38
|
+
function parseYamlObject(content, label) {
|
|
39
|
+
try {
|
|
40
|
+
return parseYaml(content);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
throw new Error(`Invalid ${label}: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function assertValidMapKeys(keys, validator, label) {
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
try {
|
|
50
|
+
validator(key);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
+
throw new Error(`Invalid ${label} '${key}': ${message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const RegistryStateSchema = z.object({
|
|
59
|
+
version: z.literal(1),
|
|
60
|
+
workspaces: z.record(z.string(), z.string()),
|
|
61
|
+
}).strict();
|
|
62
|
+
export function getManagedWorkspacesDir(options = {}) {
|
|
63
|
+
return joinWorkspacePath(options.globalDataDir ?? getGlobalDataDir(), MANAGED_WORKSPACES_DIR_NAME);
|
|
64
|
+
}
|
|
65
|
+
export function getManagedWorkspaceRoot(workspaceName, options = {}) {
|
|
66
|
+
validateWorkspaceName(workspaceName);
|
|
67
|
+
return joinWorkspacePath(getManagedWorkspacesDir(options), workspaceName);
|
|
68
|
+
}
|
|
69
|
+
export function getWorkspaceRegistryPath(options = {}) {
|
|
70
|
+
return joinWorkspacePath(getManagedWorkspacesDir(options), WORKSPACE_REGISTRY_FILE_NAME);
|
|
71
|
+
}
|
|
72
|
+
export function parseWorkspaceRegistryState(content) {
|
|
73
|
+
const raw = parseYamlObject(content, 'workspace registry state');
|
|
74
|
+
const result = RegistryStateSchema.safeParse(raw);
|
|
75
|
+
if (!result.success) {
|
|
76
|
+
throw new Error(`Invalid workspace registry state: ${formatZodIssues(result.error)}`);
|
|
77
|
+
}
|
|
78
|
+
assertValidMapKeys(Object.keys(result.data.workspaces), validateWorkspaceName, 'workspace registry name');
|
|
79
|
+
return {
|
|
80
|
+
version: 1,
|
|
81
|
+
workspaces: result.data.workspaces,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function serializeWorkspaceRegistryState(state) {
|
|
85
|
+
assertValidMapKeys(Object.keys(state.workspaces), validateWorkspaceName, 'workspace registry name');
|
|
86
|
+
for (const [workspaceName, workspaceRoot] of Object.entries(state.workspaces)) {
|
|
87
|
+
if (typeof workspaceRoot !== 'string') {
|
|
88
|
+
throw new Error(`Invalid workspace registry entry '${workspaceName}': path must be a string`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return stringifyYaml({
|
|
92
|
+
version: 1,
|
|
93
|
+
workspaces: state.workspaces,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export function listWorkspaceRegistryEntries(registry) {
|
|
97
|
+
return Object.entries(registry.workspaces)
|
|
98
|
+
.map(([name, workspaceRoot]) => ({ name, workspaceRoot }))
|
|
99
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
100
|
+
}
|
|
101
|
+
export async function listKnownWorkspaceEntries(options = {}) {
|
|
102
|
+
const legacyRegistry = await readWorkspaceRegistryState(options);
|
|
103
|
+
const workspaces = new Map(Object.entries(legacyRegistry?.workspaces ?? {}));
|
|
104
|
+
for (const entry of await listManagedWorkspaceEntries(options)) {
|
|
105
|
+
workspaces.set(entry.name, entry.workspaceRoot);
|
|
106
|
+
}
|
|
107
|
+
return [...workspaces.entries()]
|
|
108
|
+
.map(([name, workspaceRoot]) => ({ name, workspaceRoot }))
|
|
109
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
110
|
+
}
|
|
111
|
+
export async function listManagedWorkspaceEntries(options = {}) {
|
|
112
|
+
const workspacesDir = getManagedWorkspacesDir(options);
|
|
113
|
+
if (!(await pathIsDirectory(workspacesDir))) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
const entries = await fs.readdir(workspacesDir, { withFileTypes: true });
|
|
117
|
+
const workspaces = [];
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
if (!entry.isDirectory()) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const workspaceRoot = FileSystemUtils.canonicalizeExistingPath(joinWorkspacePath(workspacesDir, entry.name));
|
|
123
|
+
if (!(await isWorkspaceRoot(workspaceRoot))) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const state = await readWorkspaceViewState(workspaceRoot);
|
|
128
|
+
workspaces.push({ name: state.name, workspaceRoot });
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
workspaces.push({ name: entry.name, workspaceRoot });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return workspaces.sort((a, b) => a.name.localeCompare(b.name));
|
|
135
|
+
}
|
|
136
|
+
export async function readWorkspaceRegistryState(options = {}) {
|
|
137
|
+
const registryPath = getWorkspaceRegistryPath(options);
|
|
138
|
+
if (!(await pathIsFile(registryPath))) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return parseWorkspaceRegistryState(await fs.readFile(registryPath, 'utf-8'));
|
|
142
|
+
}
|
|
143
|
+
export async function writeWorkspaceRegistryState(state, options = {}) {
|
|
144
|
+
await FileSystemUtils.writeFile(getWorkspaceRegistryPath(options), serializeWorkspaceRegistryState(state));
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AIToolOption } from '../config.js';
|
|
2
2
|
import { type Delivery, type Profile } from '../global-config.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { WorkspaceSkillState } from './foundation.js';
|
|
4
4
|
export interface WorkspaceSkillAgentResult {
|
|
5
5
|
tool_id: string;
|
|
6
6
|
name: string;
|
|
@@ -43,7 +43,9 @@ export declare function getCurrentWorkspaceSkillProfileSelection(): {
|
|
|
43
43
|
delivery: Delivery;
|
|
44
44
|
workflow_ids: string[];
|
|
45
45
|
};
|
|
46
|
-
export declare function hasWorkspaceSkillProfileDrift(
|
|
46
|
+
export declare function hasWorkspaceSkillProfileDrift(state: {
|
|
47
|
+
workspace_skills?: WorkspaceSkillState;
|
|
48
|
+
} | null | undefined): boolean;
|
|
47
49
|
export declare function getWorkspaceSkillCapableTools(): WorkspaceSkillCapableTool[];
|
|
48
50
|
export declare function getWorkspaceSkillToolIds(): string[];
|
|
49
51
|
export declare function parseWorkspaceSkillToolsValue(rawTools: string): string[];
|
|
@@ -44,8 +44,8 @@ function arraysEqual(left, right) {
|
|
|
44
44
|
}
|
|
45
45
|
return [...leftSet].every((value) => rightSet.has(value));
|
|
46
46
|
}
|
|
47
|
-
export function hasWorkspaceSkillProfileDrift(
|
|
48
|
-
const workspaceSkills =
|
|
47
|
+
export function hasWorkspaceSkillProfileDrift(state) {
|
|
48
|
+
const workspaceSkills = state?.workspace_skills;
|
|
49
49
|
if (!workspaceSkills) {
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type WorkspaceViewState } from './foundation.js';
|
|
2
|
+
export declare function isWorkspaceRoot(candidateRoot: string): Promise<boolean>;
|
|
3
|
+
export declare function findWorkspaceRoot(startPath?: string): Promise<string | null>;
|
|
4
|
+
export declare function workspaceStateFileExistsSync(workspaceRoot: string): boolean;
|
|
5
|
+
export declare function readWorkspaceViewState(workspaceRoot: string): Promise<WorkspaceViewState>;
|
|
6
|
+
export declare function readWorkspaceViewStateSync(workspaceRoot: string): WorkspaceViewState | null;
|
|
7
|
+
export declare function readOptionalWorkspaceViewState(workspaceRoot: string): Promise<WorkspaceViewState | null>;
|
|
8
|
+
export declare function writeWorkspaceViewState(workspaceRoot: string, state: WorkspaceViewState): Promise<void>;
|
|
9
|
+
export declare function workspaceChangesDirExists(workspaceRoot: string): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=state-io.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
4
|
+
import { getWorkspaceChangesDir, getWorkspaceViewStatePath, parseWorkspaceViewState, serializeWorkspaceViewState, } from './foundation.js';
|
|
5
|
+
import { getWorkspaceLegacyLocalStatePath, getWorkspaceLegacySharedStatePath, parseWorkspaceLocalState, parseWorkspaceSharedState, workspaceStatePartsToViewState, } from './legacy-state.js';
|
|
6
|
+
const fs = nodeFs.promises;
|
|
7
|
+
async function pathIsFile(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
return (await fs.stat(filePath)).isFile();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function pathIsDirectory(dirPath) {
|
|
16
|
+
try {
|
|
17
|
+
return (await fs.stat(dirPath)).isDirectory();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function pathExistsAsFile(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
return nodeFs.statSync(filePath).isFile();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isFileNotFoundError(error) {
|
|
32
|
+
return (typeof error === 'object' &&
|
|
33
|
+
error !== null &&
|
|
34
|
+
'code' in error &&
|
|
35
|
+
error.code === 'ENOENT');
|
|
36
|
+
}
|
|
37
|
+
async function getSearchStartDirectory(startPath) {
|
|
38
|
+
const resolvedStart = path.resolve(startPath);
|
|
39
|
+
try {
|
|
40
|
+
const stats = await fs.stat(resolvedStart);
|
|
41
|
+
const searchStart = stats.isDirectory() ? resolvedStart : path.dirname(resolvedStart);
|
|
42
|
+
return FileSystemUtils.canonicalizeExistingPath(searchStart);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return resolvedStart;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function isWorkspaceRoot(candidateRoot) {
|
|
49
|
+
return ((await pathIsFile(getWorkspaceViewStatePath(candidateRoot))) ||
|
|
50
|
+
(await pathIsFile(getWorkspaceLegacySharedStatePath(candidateRoot))));
|
|
51
|
+
}
|
|
52
|
+
export async function findWorkspaceRoot(startPath = process.cwd()) {
|
|
53
|
+
let currentDir = await getSearchStartDirectory(startPath);
|
|
54
|
+
while (true) {
|
|
55
|
+
if (await isWorkspaceRoot(currentDir)) {
|
|
56
|
+
return FileSystemUtils.canonicalizeExistingPath(currentDir);
|
|
57
|
+
}
|
|
58
|
+
const parentDir = path.dirname(currentDir);
|
|
59
|
+
if (parentDir === currentDir) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
currentDir = parentDir;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function workspaceStateFileExistsSync(workspaceRoot) {
|
|
66
|
+
return (pathExistsAsFile(getWorkspaceViewStatePath(workspaceRoot)) ||
|
|
67
|
+
pathExistsAsFile(getWorkspaceLegacySharedStatePath(workspaceRoot)));
|
|
68
|
+
}
|
|
69
|
+
export async function readWorkspaceViewState(workspaceRoot) {
|
|
70
|
+
const viewStatePath = getWorkspaceViewStatePath(workspaceRoot);
|
|
71
|
+
if (await pathIsFile(viewStatePath)) {
|
|
72
|
+
return parseWorkspaceViewState(await fs.readFile(viewStatePath, 'utf-8'));
|
|
73
|
+
}
|
|
74
|
+
const legacySharedState = parseWorkspaceSharedState(await fs.readFile(getWorkspaceLegacySharedStatePath(workspaceRoot), 'utf-8'));
|
|
75
|
+
let legacyLocalState = null;
|
|
76
|
+
try {
|
|
77
|
+
legacyLocalState = parseWorkspaceLocalState(await fs.readFile(getWorkspaceLegacyLocalStatePath(workspaceRoot), 'utf-8'));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!isFileNotFoundError(error)) {
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return workspaceStatePartsToViewState(legacySharedState, legacyLocalState);
|
|
85
|
+
}
|
|
86
|
+
export function readWorkspaceViewStateSync(workspaceRoot) {
|
|
87
|
+
const viewStatePath = getWorkspaceViewStatePath(workspaceRoot);
|
|
88
|
+
if (pathExistsAsFile(viewStatePath)) {
|
|
89
|
+
return parseWorkspaceViewState(nodeFs.readFileSync(viewStatePath, 'utf-8'));
|
|
90
|
+
}
|
|
91
|
+
const legacySharedPath = getWorkspaceLegacySharedStatePath(workspaceRoot);
|
|
92
|
+
if (!pathExistsAsFile(legacySharedPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const legacySharedState = parseWorkspaceSharedState(nodeFs.readFileSync(legacySharedPath, 'utf-8'));
|
|
96
|
+
const legacyLocalPath = getWorkspaceLegacyLocalStatePath(workspaceRoot);
|
|
97
|
+
const legacyLocalState = pathExistsAsFile(legacyLocalPath)
|
|
98
|
+
? parseWorkspaceLocalState(nodeFs.readFileSync(legacyLocalPath, 'utf-8'))
|
|
99
|
+
: null;
|
|
100
|
+
return workspaceStatePartsToViewState(legacySharedState, legacyLocalState);
|
|
101
|
+
}
|
|
102
|
+
export async function readOptionalWorkspaceViewState(workspaceRoot) {
|
|
103
|
+
try {
|
|
104
|
+
return await readWorkspaceViewState(workspaceRoot);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (isFileNotFoundError(error)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function writeWorkspaceViewState(workspaceRoot, state) {
|
|
114
|
+
await FileSystemUtils.writeFile(getWorkspaceViewStatePath(workspaceRoot), serializeWorkspaceViewState(state));
|
|
115
|
+
}
|
|
116
|
+
export async function workspaceChangesDirExists(workspaceRoot) {
|
|
117
|
+
return pathIsDirectory(getWorkspaceChangesDir(workspaceRoot));
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=state-io.js.map
|