gmc-openspec 1.4.3 → 1.4.4

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 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, runWorkspaceUpdateForRoot, } from '../commands/workspace.js';
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
- await runWorkspaceUpdateForRoot(workspaceRoot, { force: options?.force });
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/workspace.yaml before using this 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, selectWorkspaceRootForCommand, } from './workspace/selection.js';
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
  }
@@ -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 = "workspace.yaml";
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 = 'workspace.yaml';
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, WORKSPACE_VIEW_STATE_FILE_NAME, getWorkspaceMetadataDir, parseWorkspaceViewState, validateWorkspaceLinkName, validateWorkspaceName, validateWorkspacePreferredOpener, } from './foundation.js';
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 = WORKSPACE_VIEW_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 `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.";
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 \`workspace.yaml\`.
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
- await FileSystemUtils.writeFile(getWorkspaceViewStatePath(workspaceRoot), serializeWorkspaceViewState(state));
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gmc-openspec",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",