gmc-openspec 1.4.3 → 1.4.5
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/dist/cli/index.js +23 -5
- package/dist/commands/workspace/context-status.js +1 -1
- package/dist/commands/workspace/open-view.js +1 -1
- package/dist/commands/workspace/operations.js +3 -3
- package/dist/commands/workspace/selection.d.ts +0 -1
- package/dist/commands/workspace/selection.js +0 -11
- package/dist/commands/workspace.d.ts +0 -1
- package/dist/commands/workspace.js +1 -14
- package/dist/core/jira/templates.js +8 -1
- package/dist/core/workspace/foundation.d.ts +1 -1
- package/dist/core/workspace/foundation.js +2 -2
- package/dist/core/workspace/legacy-state.js +2 -2
- package/dist/core/workspace/open-surface.d.ts +1 -1
- package/dist/core/workspace/open-surface.js +1 -1
- package/dist/core/workspace/state-io.js +4 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { promises as fs } from 'fs';
|
|
7
|
-
import { AI_TOOLS } from '../core/config.js';
|
|
7
|
+
import { AI_TOOLS, OPENSPEC_DIR_NAME } from '../core/config.js';
|
|
8
8
|
import { UpdateCommand } from '../core/update.js';
|
|
9
9
|
import { ListCommand } from '../core/list.js';
|
|
10
10
|
import { ArchiveCommand } from '../core/archive.js';
|
|
@@ -17,7 +17,7 @@ import { CompletionCommand } from '../commands/completion.js';
|
|
|
17
17
|
import { FeedbackCommand } from '../commands/feedback.js';
|
|
18
18
|
import { registerConfigCommand } from '../commands/config.js';
|
|
19
19
|
import { registerSchemaCommand } from '../commands/schema.js';
|
|
20
|
-
import { registerWorkspaceCommand
|
|
20
|
+
import { registerWorkspaceCommand } from '../commands/workspace.js';
|
|
21
21
|
import { registerContextStoreCommand } from '../commands/context-store.js';
|
|
22
22
|
import { registerInitiativeCommand } from '../commands/initiative.js';
|
|
23
23
|
import { findWorkspaceRoot } from '../core/workspace/index.js';
|
|
@@ -71,6 +71,21 @@ program.hook('postAction', async () => {
|
|
|
71
71
|
});
|
|
72
72
|
const availableToolIds = AI_TOOLS.filter((tool) => tool.skillsDir).map((tool) => tool.value);
|
|
73
73
|
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
|
|
74
|
+
async function hasRepoLocalOpenSpecProject(projectPath) {
|
|
75
|
+
try {
|
|
76
|
+
const stats = await fs.stat(path.join(projectPath, OPENSPEC_DIR_NAME));
|
|
77
|
+
return stats.isDirectory();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
81
|
+
? error.code
|
|
82
|
+
: undefined;
|
|
83
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
program
|
|
75
90
|
.command('init [path]')
|
|
76
91
|
.description('Initialize OpenSpec in your project')
|
|
@@ -142,12 +157,15 @@ program
|
|
|
142
157
|
.action(async (targetPath = '.', options) => {
|
|
143
158
|
try {
|
|
144
159
|
const resolvedPath = path.resolve(targetPath);
|
|
160
|
+
const updateCommand = new UpdateCommand({ force: options?.force });
|
|
161
|
+
if (await hasRepoLocalOpenSpecProject(resolvedPath)) {
|
|
162
|
+
await updateCommand.execute(resolvedPath);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
145
165
|
const workspaceRoot = await findWorkspaceRoot(resolvedPath);
|
|
146
166
|
if (workspaceRoot) {
|
|
147
|
-
|
|
148
|
-
return;
|
|
167
|
+
throw new Error('OpenSpec workspace detected. Run `openspec workspace update` to refresh workspace-local guidance and skills.');
|
|
149
168
|
}
|
|
150
|
-
const updateCommand = new UpdateCommand({ force: options?.force });
|
|
151
169
|
await updateCommand.execute(resolvedPath);
|
|
152
170
|
}
|
|
153
171
|
catch (error) {
|
|
@@ -25,7 +25,7 @@ export async function collectWorkspaceContextStatuses(context) {
|
|
|
25
25
|
target: 'workspace.context.store',
|
|
26
26
|
fix: context.store.selector.kind === 'registry'
|
|
27
27
|
? 'openspec context-store doctor'
|
|
28
|
-
: `Check the path in workspace.yaml or run openspec initiative show ${initiativeId} ${selector}`,
|
|
28
|
+
: `Check the path in .openspec-workspace/view.yaml or run openspec initiative show ${initiativeId} ${selector}`,
|
|
29
29
|
}),
|
|
30
30
|
];
|
|
31
31
|
}
|
|
@@ -84,7 +84,7 @@ async function resolveStoredWorkspaceInitiative(context) {
|
|
|
84
84
|
target: 'workspace.context.store',
|
|
85
85
|
fix: context.store.selector.kind === 'registry'
|
|
86
86
|
? 'openspec context-store doctor'
|
|
87
|
-
: 'Check the path in workspace.yaml.',
|
|
87
|
+
: 'Check the path in .openspec-workspace/view.yaml.',
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -114,7 +114,7 @@ export function validateLinkNameForCommand(name) {
|
|
|
114
114
|
function localStateInvalidStatus(error) {
|
|
115
115
|
return makeStatus('error', 'workspace_local_state_invalid', `Machine-local paths could not be read: ${asErrorMessage(error)}`, {
|
|
116
116
|
target: 'workspace.local_state',
|
|
117
|
-
fix: 'Repair workspace.yaml, then run openspec workspace relink <name> <path> for affected links.',
|
|
117
|
+
fix: 'Repair .openspec-workspace/view.yaml, then run openspec workspace relink <name> <path> for affected links.',
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
function workspaceSkillDriftStatus(workspaceName) {
|
|
@@ -279,7 +279,7 @@ export async function loadWorkspaceForDoctor(selected) {
|
|
|
279
279
|
status: [
|
|
280
280
|
makeStatus('error', 'workspace_state_invalid', `Workspace state could not be read: ${asErrorMessage(error)}`, {
|
|
281
281
|
target: 'workspace.root',
|
|
282
|
-
fix: 'Repair .openspec-workspace/
|
|
282
|
+
fix: 'Repair .openspec-workspace/view.yaml before using this workspace.',
|
|
283
283
|
}),
|
|
284
284
|
],
|
|
285
285
|
},
|
|
@@ -345,7 +345,7 @@ async function readWorkspaceViewForMutation(selected) {
|
|
|
345
345
|
catch (error) {
|
|
346
346
|
throw new WorkspaceCliError(`Workspace state could not be read: ${asErrorMessage(error)}`, 'workspace_state_invalid', {
|
|
347
347
|
target: 'workspace.state',
|
|
348
|
-
fix: 'Repair workspace.yaml before using this workspace.',
|
|
348
|
+
fix: 'Repair .openspec-workspace/view.yaml before using this workspace.',
|
|
349
349
|
});
|
|
350
350
|
}
|
|
351
351
|
}
|
|
@@ -2,7 +2,6 @@ import { type WorkspaceRegistryEntry } from '../../core/workspace/index.js';
|
|
|
2
2
|
import { SelectedWorkspace, WorkspaceSelectionOptions } from './types.js';
|
|
3
3
|
export declare function selectedWorkspaceFromEntry(entry: WorkspaceRegistryEntry): SelectedWorkspace;
|
|
4
4
|
export declare function selectedWorkspaceFromRoot(currentWorkspaceRoot: string, entries: WorkspaceRegistryEntry[]): Promise<SelectedWorkspace>;
|
|
5
|
-
export declare function selectWorkspaceRootForCommand(workspaceRoot: string): Promise<SelectedWorkspace>;
|
|
6
5
|
export declare function selectWorkspaceForCommand(options: WorkspaceSelectionOptions, commandName: string, selectionOptions?: {
|
|
7
6
|
preferPositionalName?: boolean;
|
|
8
7
|
}): Promise<SelectedWorkspace>;
|
|
@@ -44,17 +44,6 @@ export async function selectedWorkspaceFromRoot(currentWorkspaceRoot, entries) {
|
|
|
44
44
|
unregisteredCurrentWorkspace: !isKnown,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
-
export async function selectWorkspaceRootForCommand(workspaceRoot) {
|
|
48
|
-
const entries = await listKnownWorkspaceEntries();
|
|
49
|
-
const currentWorkspaceRoot = await findWorkspaceRoot(workspaceRoot);
|
|
50
|
-
if (!currentWorkspaceRoot) {
|
|
51
|
-
throw new WorkspaceCliError(`No OpenSpec workspace found at '${workspaceRoot}'.`, 'workspace_not_found', {
|
|
52
|
-
target: 'workspace.root',
|
|
53
|
-
fix: 'Pass a path inside an OpenSpec workspace.',
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return selectedWorkspaceFromRoot(currentWorkspaceRoot, entries);
|
|
57
|
-
}
|
|
58
47
|
export async function selectWorkspaceForCommand(options, commandName, selectionOptions = {}) {
|
|
59
48
|
const entries = await listKnownWorkspaceEntries();
|
|
60
49
|
if (options.workspace) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WorkspaceUpdateOptions } from './workspace/types.js';
|
|
3
3
|
export declare function runWorkspaceUpdate(positionalName: string | undefined, options?: WorkspaceUpdateOptions): Promise<void>;
|
|
4
|
-
export declare function runWorkspaceUpdateForRoot(workspaceRoot: string, options?: WorkspaceUpdateOptions): Promise<void>;
|
|
5
4
|
export declare function registerWorkspaceCommand(program: Command): void;
|
|
6
5
|
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { createWorkspaceSkillSkippedReport, generateWorkspaceAgentSkills, getWorkspaceSkillCapableTools, getWorkspaceSkillToolIds, getWorkspaceOpenerLabel, parseWorkspaceSkillToolsValue, updateWorkspaceAgentSkills, listKnownWorkspaceEntries, readWorkspaceViewState, syncWorkspaceOpenSurface, writeWorkspaceViewState, } from '../core/workspace/index.js';
|
|
3
3
|
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
4
4
|
import { addWorkspaceLink, createManagedWorkspace, loadWorkspaceForDoctor, loadWorkspaceForList, parseSetupLinks, readWorkspaceForMutation, updateWorkspaceLink, validateWorkspaceNameForSetup, } from './workspace/operations.js';
|
|
5
|
-
import { selectWorkspaceForCommand
|
|
5
|
+
import { selectWorkspaceForCommand } from './workspace/selection.js';
|
|
6
6
|
import { launchWorkspaceOpenCommand, } from './workspace/open.js';
|
|
7
7
|
import { buildWorkspaceOpenJsonPayload, prepareWorkspaceOpen, } from './workspace/open-view.js';
|
|
8
8
|
import { getPreferredWorkspaceSkillAgentId, parseSetupOpenerOption, promptPreferredOpener, } from './workspace/opener-selection.js';
|
|
@@ -484,15 +484,6 @@ class WorkspaceCommand {
|
|
|
484
484
|
this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
-
async updateRoot(workspaceRoot, options = {}) {
|
|
488
|
-
try {
|
|
489
|
-
const selected = await selectWorkspaceRootForCommand(workspaceRoot);
|
|
490
|
-
await this.updateSelected(selected, options);
|
|
491
|
-
}
|
|
492
|
-
catch (error) {
|
|
493
|
-
this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
487
|
async updateSelected(selected, options) {
|
|
497
488
|
const viewState = await readWorkspaceForMutation(selected);
|
|
498
489
|
await syncWorkspaceOpenSurface(selected.root, viewState);
|
|
@@ -577,10 +568,6 @@ export async function runWorkspaceUpdate(positionalName, options = {}) {
|
|
|
577
568
|
const workspaceCommand = new WorkspaceCommand();
|
|
578
569
|
await workspaceCommand.update(positionalName, options);
|
|
579
570
|
}
|
|
580
|
-
export async function runWorkspaceUpdateForRoot(workspaceRoot, options = {}) {
|
|
581
|
-
const workspaceCommand = new WorkspaceCommand();
|
|
582
|
-
await workspaceCommand.updateRoot(workspaceRoot, options);
|
|
583
|
-
}
|
|
584
571
|
export function registerWorkspaceCommand(program) {
|
|
585
572
|
registerWorkspaceCommandWith(program, new WorkspaceCommand());
|
|
586
573
|
}
|
|
@@ -107,11 +107,18 @@ ${SHARED_GUARDRAILS}
|
|
|
107
107
|
|
|
108
108
|
Each story must have independent review value, clear AC, scope, non-goals, dependencies, and a traceable requirement source. Avoid one giant change and avoid tiny implementation-only fragments.
|
|
109
109
|
|
|
110
|
+
## Story Title Format
|
|
111
|
+
|
|
112
|
+
- In \`stories.md\`, each story heading/title MUST include a split marker in \`[S-x]\` form, using the story sequence visible to reviewers, for example \`## [S-1] Add refund endpoint\`.
|
|
113
|
+
- If the affected repository is known from Jira fields, requirement text, workspace context, or existing mapping, include the short repository abbreviation in bracket form before the story marker, for example \`## [api] [S-1] Add refund endpoint\`.
|
|
114
|
+
- For multi-repo stories, include each known repository abbreviation as its own bracket, for example \`## [web] [api] [S-2] Wire checkout status\`.
|
|
115
|
+
- If the affected repository cannot be determined with confidence, omit the repo bracket instead of inventing one.
|
|
116
|
+
|
|
110
117
|
## Steps
|
|
111
118
|
|
|
112
119
|
1. Run \`openspec jira validate <JIRA-ID> --stage requirement --json\`.
|
|
113
120
|
2. Read \`intake.yaml\` to get \`documentLanguage\`, plus \`requirement.md\`, \`mapping.yaml\`, and existing OpenSpec specs if capability choices are unclear.
|
|
114
|
-
3. Write \`stories.md\` using \`documentLanguage\`, with stable English/code story ids, titles, scope, non-goals, AC, dependencies, risks, and suggested OpenSpec change names.
|
|
121
|
+
3. Write \`stories.md\` using \`documentLanguage\`, with stable English/code story ids, titles following the Story Title Format, scope, non-goals, AC, dependencies, risks, and suggested OpenSpec change names.
|
|
115
122
|
4. Update \`mapping.yaml\` so every Jira AC maps to at least one story.
|
|
116
123
|
5. Set \`intake.yaml\` status to \`split\` and ask the user to review the story split.
|
|
117
124
|
6. After explicit user approval, set status to \`stories-reviewed\` and run \`openspec jira validate <JIRA-ID> --stage stories\`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ContextStoreBinding, type ContextStoreSelector } from '../context-store/index.js';
|
|
2
2
|
export declare const WORKSPACE_METADATA_DIR_NAME = ".openspec-workspace";
|
|
3
|
-
export declare const WORKSPACE_VIEW_STATE_FILE_NAME = "
|
|
3
|
+
export declare const WORKSPACE_VIEW_STATE_FILE_NAME = "view.yaml";
|
|
4
4
|
export declare const WORKSPACE_CHANGES_DIR_NAME = "changes";
|
|
5
5
|
export declare const WORKSPACE_CODE_WORKSPACE_EXTENSION = ".code-workspace";
|
|
6
6
|
export declare const WORKSPACE_SUPPORTED_OPENER_VALUES: readonly ["codex-cli", "claude", "github-copilot", "editor"];
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { normalizeContextStoreBinding, } from '../context-store/index.js';
|
|
4
4
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
5
|
export const WORKSPACE_METADATA_DIR_NAME = '.openspec-workspace';
|
|
6
|
-
export const WORKSPACE_VIEW_STATE_FILE_NAME = '
|
|
6
|
+
export const WORKSPACE_VIEW_STATE_FILE_NAME = 'view.yaml';
|
|
7
7
|
export const WORKSPACE_CHANGES_DIR_NAME = 'changes';
|
|
8
8
|
export const WORKSPACE_CODE_WORKSPACE_EXTENSION = '.code-workspace';
|
|
9
9
|
export const WORKSPACE_SUPPORTED_OPENER_VALUES = [
|
|
@@ -25,7 +25,7 @@ export function getWorkspaceMetadataDir(workspaceRoot) {
|
|
|
25
25
|
return joinWorkspacePath(workspaceRoot, WORKSPACE_METADATA_DIR_NAME);
|
|
26
26
|
}
|
|
27
27
|
export function getWorkspaceViewStatePath(workspaceRoot) {
|
|
28
|
-
return joinWorkspacePath(workspaceRoot, WORKSPACE_VIEW_STATE_FILE_NAME);
|
|
28
|
+
return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_VIEW_STATE_FILE_NAME);
|
|
29
29
|
}
|
|
30
30
|
export function getWorkspaceChangesDir(workspaceRoot) {
|
|
31
31
|
return joinWorkspacePath(workspaceRoot, WORKSPACE_CHANGES_DIR_NAME);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { WORKSPACE_METADATA_DIR_NAME,
|
|
3
|
+
import { WORKSPACE_METADATA_DIR_NAME, getWorkspaceMetadataDir, parseWorkspaceViewState, validateWorkspaceLinkName, validateWorkspaceName, validateWorkspacePreferredOpener, } from './foundation.js';
|
|
4
4
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
|
-
export const WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME =
|
|
5
|
+
export const WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME = 'workspace.yaml';
|
|
6
6
|
export const WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME = 'local.yaml';
|
|
7
7
|
export const WORKSPACE_LEGACY_LOCAL_STATE_IGNORE_PATTERN = `${WORKSPACE_METADATA_DIR_NAME}/${WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME}`;
|
|
8
8
|
function joinWorkspacePath(basePath, ...segments) {
|
|
@@ -3,7 +3,7 @@ export declare const WORKSPACE_GUIDANCE_START_MARKER = "<!-- OPENSPEC:WORKSPACE-
|
|
|
3
3
|
export declare const WORKSPACE_GUIDANCE_END_MARKER = "<!-- OPENSPEC:WORKSPACE-GUIDANCE:END -->";
|
|
4
4
|
export declare const WORKSPACE_OPEN_ROOT_FOLDER_LABEL = "OpenSpec workspace";
|
|
5
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
|
|
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 `.openspec-workspace/view.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
7
|
export interface WorkspaceOpenResolvedContext {
|
|
8
8
|
contextStore: {
|
|
9
9
|
id: string;
|
|
@@ -16,7 +16,7 @@ This directory is an OpenSpec workspace: a local working view over context store
|
|
|
16
16
|
- Use repo-local OpenSpec changes for implementation plans owned by a repo or team.
|
|
17
17
|
- Use linked repos and folders to inspect context, understand ownership, and make edits in the place that owns the work.
|
|
18
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
|
|
19
|
+
- Use OpenSpec workspace commands instead of hand-editing \`.openspec-workspace/view.yaml\`.
|
|
20
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.`;
|
|
21
21
|
async function fileExists(filePath) {
|
|
22
22
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { getWorkspaceChangesDir, getWorkspaceViewStatePath, parseWorkspaceViewState, serializeWorkspaceViewState, } from './foundation.js';
|
|
4
|
+
import { getWorkspaceChangesDir, getWorkspaceMetadataDir, getWorkspaceViewStatePath, parseWorkspaceViewState, serializeWorkspaceViewState, } from './foundation.js';
|
|
5
5
|
import { getWorkspaceLegacyLocalStatePath, getWorkspaceLegacySharedStatePath, parseWorkspaceLocalState, parseWorkspaceSharedState, workspaceStatePartsToViewState, } from './legacy-state.js';
|
|
6
6
|
const fs = nodeFs.promises;
|
|
7
7
|
async function pathIsFile(filePath) {
|
|
@@ -111,7 +111,9 @@ export async function readOptionalWorkspaceViewState(workspaceRoot) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
export async function writeWorkspaceViewState(workspaceRoot, state) {
|
|
114
|
-
|
|
114
|
+
const content = serializeWorkspaceViewState(state);
|
|
115
|
+
await FileSystemUtils.createDirectory(getWorkspaceMetadataDir(workspaceRoot));
|
|
116
|
+
await FileSystemUtils.writeFile(getWorkspaceViewStatePath(workspaceRoot), content);
|
|
115
117
|
}
|
|
116
118
|
export async function workspaceChangesDirExists(workspaceRoot) {
|
|
117
119
|
return pathIsDirectory(getWorkspaceChangesDir(workspaceRoot));
|