gmc-openspec 1.1.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +2 -2
  2. package/bin/openspec.js +3 -1
  3. package/dist/cli/index.d.ts +4 -1
  4. package/dist/cli/index.js +36 -2
  5. package/dist/commands/config.js +4 -4
  6. package/dist/commands/context-store.d.ts +3 -0
  7. package/dist/commands/context-store.js +475 -0
  8. package/dist/commands/initiative.d.ts +13 -0
  9. package/dist/commands/initiative.js +318 -0
  10. package/dist/commands/workflow/index.d.ts +2 -0
  11. package/dist/commands/workflow/index.js +1 -0
  12. package/dist/commands/workflow/initiative-link.d.ts +24 -0
  13. package/dist/commands/workflow/initiative-link.js +47 -0
  14. package/dist/commands/workflow/instructions.js +10 -2
  15. package/dist/commands/workflow/new-change.d.ts +4 -0
  16. package/dist/commands/workflow/new-change.js +72 -23
  17. package/dist/commands/workflow/set-change.d.ts +13 -0
  18. package/dist/commands/workflow/set-change.js +87 -0
  19. package/dist/commands/workflow/shared.d.ts +2 -0
  20. package/dist/commands/workflow/status.js +3 -0
  21. package/dist/commands/workspace/context-status.d.ts +4 -0
  22. package/dist/commands/workspace/context-status.js +59 -0
  23. package/dist/commands/workspace/open-target-selection.d.ts +13 -0
  24. package/dist/commands/workspace/open-target-selection.js +146 -0
  25. package/dist/commands/workspace/open-view.d.ts +62 -0
  26. package/dist/commands/workspace/open-view.js +249 -0
  27. package/dist/commands/workspace/open.d.ts +16 -8
  28. package/dist/commands/workspace/open.js +40 -14
  29. package/dist/commands/workspace/opener-selection.d.ts +11 -0
  30. package/dist/commands/workspace/opener-selection.js +98 -0
  31. package/dist/commands/workspace/operations.d.ts +14 -8
  32. package/dist/commands/workspace/operations.js +228 -160
  33. package/dist/commands/workspace/prompt-theme.d.ts +29 -0
  34. package/dist/commands/workspace/prompt-theme.js +24 -0
  35. package/dist/commands/workspace/registration.d.ts +13 -0
  36. package/dist/commands/workspace/registration.js +84 -0
  37. package/dist/commands/workspace/selection.d.ts +3 -0
  38. package/dist/commands/workspace/selection.js +42 -40
  39. package/dist/commands/workspace/setup-prompts.d.ts +13 -0
  40. package/dist/commands/workspace/setup-prompts.js +121 -0
  41. package/dist/commands/workspace/types.d.ts +15 -0
  42. package/dist/commands/workspace.js +59 -340
  43. package/dist/core/artifact-graph/index.d.ts +2 -1
  44. package/dist/core/artifact-graph/instruction-loader.d.ts +10 -23
  45. package/dist/core/artifact-graph/instruction-loader.js +28 -89
  46. package/dist/core/artifact-graph/types.d.ts +0 -7
  47. package/dist/core/artifact-graph/types.js +0 -19
  48. package/dist/core/change-metadata/index.d.ts +2 -0
  49. package/dist/core/change-metadata/index.js +2 -0
  50. package/dist/core/change-metadata/schema.d.ts +18 -0
  51. package/dist/core/change-metadata/schema.js +28 -0
  52. package/dist/core/change-status-policy.d.ts +50 -0
  53. package/dist/core/change-status-policy.js +70 -0
  54. package/dist/core/collections/index.d.ts +3 -0
  55. package/dist/core/collections/index.js +3 -0
  56. package/dist/core/collections/initiatives/collection.d.ts +4 -0
  57. package/dist/core/collections/initiatives/collection.js +17 -0
  58. package/dist/core/collections/initiatives/index.d.ts +6 -0
  59. package/dist/core/collections/initiatives/index.js +6 -0
  60. package/dist/core/collections/initiatives/operations.d.ts +49 -0
  61. package/dist/core/collections/initiatives/operations.js +175 -0
  62. package/dist/core/collections/initiatives/resolution.d.ts +87 -0
  63. package/dist/core/collections/initiatives/resolution.js +374 -0
  64. package/dist/core/collections/initiatives/schema.d.ts +41 -0
  65. package/dist/core/collections/initiatives/schema.js +134 -0
  66. package/dist/core/collections/initiatives/templates.d.ts +12 -0
  67. package/dist/core/collections/initiatives/templates.js +90 -0
  68. package/dist/core/collections/runtime.d.ts +46 -0
  69. package/dist/core/collections/runtime.js +194 -0
  70. package/dist/core/completions/command-registry.d.ts +1 -5
  71. package/dist/core/completions/command-registry.js +475 -70
  72. package/dist/core/completions/shared-flags.d.ts +12 -0
  73. package/dist/core/completions/shared-flags.js +28 -0
  74. package/dist/core/config.js +2 -1
  75. package/dist/core/context-store/binding.d.ts +53 -0
  76. package/dist/core/context-store/binding.js +197 -0
  77. package/dist/core/context-store/errors.d.ts +20 -0
  78. package/dist/core/context-store/errors.js +22 -0
  79. package/dist/core/context-store/foundation.d.ts +55 -0
  80. package/dist/core/context-store/foundation.js +321 -0
  81. package/dist/core/context-store/index.d.ts +6 -0
  82. package/dist/core/context-store/index.js +6 -0
  83. package/dist/core/context-store/operations.d.ts +85 -0
  84. package/dist/core/context-store/operations.js +528 -0
  85. package/dist/core/context-store/registry.d.ts +45 -0
  86. package/dist/core/context-store/registry.js +229 -0
  87. package/dist/core/index.d.ts +2 -0
  88. package/dist/core/index.js +2 -0
  89. package/dist/core/jira/templates.js +14 -5
  90. package/dist/core/planning-home.js +5 -21
  91. package/dist/core/validation/validator.d.ts +11 -0
  92. package/dist/core/validation/validator.js +19 -2
  93. package/dist/core/workspace/foundation.d.ts +28 -48
  94. package/dist/core/workspace/foundation.js +130 -214
  95. package/dist/core/workspace/index.d.ts +2 -0
  96. package/dist/core/workspace/index.js +2 -0
  97. package/dist/core/workspace/legacy-state.d.ts +28 -0
  98. package/dist/core/workspace/legacy-state.js +200 -0
  99. package/dist/core/workspace/open-surface.d.ts +29 -8
  100. package/dist/core/workspace/open-surface.js +122 -44
  101. package/dist/core/workspace/openers.js +11 -6
  102. package/dist/core/workspace/registry.d.ts +24 -0
  103. package/dist/core/workspace/registry.js +146 -0
  104. package/dist/core/workspace/skills.d.ts +4 -2
  105. package/dist/core/workspace/skills.js +2 -2
  106. package/dist/core/workspace/state-io.d.ts +10 -0
  107. package/dist/core/workspace/state-io.js +119 -0
  108. package/dist/utils/change-metadata.d.ts +5 -2
  109. package/dist/utils/change-metadata.js +6 -12
  110. package/dist/utils/change-utils.d.ts +2 -2
  111. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Set Change Command
3
+ *
4
+ * Mutates checked-in repo-local change metadata.
5
+ */
6
+ import path from 'node:path';
7
+ import { getChangeDir, resolveCurrentPlanningHomeSync, } from '../../core/planning-home.js';
8
+ import { readChangeMetadata, resolveSchemaForChange, writeChangeMetadata, } from '../../utils/change-metadata.js';
9
+ import { validateChangeExists } from './shared.js';
10
+ import { resolveInitiativeLinkReference, } from '../../core/collections/initiatives/index.js';
11
+ import { assertInitiativeReference, assertRepoLocalInitiativeLinkPlanningHome, formatInitiativeLink, printJson, sameInitiativeLink, statusFromError, } from './initiative-link.js';
12
+ function outputForSetChange(id, changeDir, schema, initiative, updated) {
13
+ return {
14
+ change: {
15
+ id,
16
+ path: changeDir,
17
+ metadataPath: path.join(changeDir, '.openspec.yaml'),
18
+ schema,
19
+ },
20
+ initiative,
21
+ updated,
22
+ };
23
+ }
24
+ function printSetChangeHuman(payload) {
25
+ if (!payload.change || !payload.initiative) {
26
+ return;
27
+ }
28
+ const verb = payload.updated ? 'Linked' : 'Change already linked';
29
+ console.log(`${verb}: ${payload.change.id}`);
30
+ console.log(`Initiative: ${formatInitiativeLink(payload.initiative)}`);
31
+ console.log(`Metadata: ${payload.change.metadataPath}`);
32
+ }
33
+ export async function setChangeCommand(name, options) {
34
+ try {
35
+ if (!name) {
36
+ throw new Error('Missing required argument <name>');
37
+ }
38
+ assertInitiativeReference(options.initiative);
39
+ const planningHome = resolveCurrentPlanningHomeSync();
40
+ assertRepoLocalInitiativeLinkPlanningHome(planningHome);
41
+ const projectRoot = planningHome.root;
42
+ const changeName = await validateChangeExists(name, projectRoot, planningHome.changesDir);
43
+ const changeDir = getChangeDir(planningHome, changeName);
44
+ const initiative = await resolveInitiativeLinkReference(options.initiative, {
45
+ store: options.store,
46
+ storePath: options.storePath,
47
+ });
48
+ const existingMetadata = readChangeMetadata(changeDir, projectRoot);
49
+ const metadata = existingMetadata ?? {
50
+ schema: resolveSchemaForChange(changeDir, undefined, projectRoot, { metadata: null }),
51
+ };
52
+ if (sameInitiativeLink(metadata.initiative, initiative)) {
53
+ const payload = outputForSetChange(changeName, changeDir, metadata.schema, initiative, false);
54
+ if (options.json) {
55
+ printJson(payload);
56
+ return;
57
+ }
58
+ printSetChangeHuman(payload);
59
+ return;
60
+ }
61
+ if (metadata.initiative) {
62
+ throw new Error(`Change '${changeName}' is already linked to initiative ${formatInitiativeLink(metadata.initiative)}.`);
63
+ }
64
+ writeChangeMetadata(changeDir, {
65
+ ...metadata,
66
+ initiative,
67
+ }, projectRoot);
68
+ const payload = outputForSetChange(changeName, changeDir, metadata.schema, initiative, true);
69
+ if (options.json) {
70
+ printJson(payload);
71
+ return;
72
+ }
73
+ printSetChangeHuman(payload);
74
+ }
75
+ catch (error) {
76
+ if (options.json) {
77
+ printJson({
78
+ change: null,
79
+ status: [statusFromError(error)],
80
+ });
81
+ process.exitCode = 1;
82
+ return;
83
+ }
84
+ throw error;
85
+ }
86
+ }
87
+ //# sourceMappingURL=set-change.js.map
@@ -4,6 +4,7 @@
4
4
  * This module contains types, constants, and validation helpers used across
5
5
  * multiple artifact workflow commands.
6
6
  */
7
+ import type { InitiativeLink } from '../../core/change-metadata/index.js';
7
8
  export interface TaskItem {
8
9
  id: string;
9
10
  description: string;
@@ -13,6 +14,7 @@ export interface ApplyInstructions {
13
14
  changeName: string;
14
15
  changeDir: string;
15
16
  schemaName: string;
17
+ initiative?: InitiativeLink;
16
18
  contextFiles: Record<string, string[]>;
17
19
  progress: {
18
20
  total: number;
@@ -61,6 +61,9 @@ export function printStatusText(status) {
61
61
  const total = status.artifacts.length;
62
62
  console.log(`Change: ${status.changeName}`);
63
63
  console.log(`Schema: ${status.schemaName}`);
64
+ if (status.initiative) {
65
+ console.log(`Initiative: ${status.initiative.store}/${status.initiative.id}`);
66
+ }
64
67
  if (status.planningHome) {
65
68
  const label = status.planningHome.kind === 'workspace'
66
69
  ? `workspace${status.planningHome.workspaceName ? ` (${status.planningHome.workspaceName})` : ''}`
@@ -0,0 +1,4 @@
1
+ import { type WorkspaceContextState } from '../../core/workspace/index.js';
2
+ import { WorkspaceStatus } from './types.js';
3
+ export declare function collectWorkspaceContextStatuses(context: WorkspaceContextState | null): Promise<WorkspaceStatus[]>;
4
+ //# sourceMappingURL=context-status.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { mountInitiativesCollection, readInitiative, } from '../../core/collections/initiatives/index.js';
2
+ import { formatContextStoreBinding, formatContextStoreBindingSelector, resolveContextStoreBinding, } from '../../core/context-store/index.js';
3
+ import { getWorkspaceContextInitiativeId, } from '../../core/workspace/index.js';
4
+ import { asErrorMessage, makeStatus } from './types.js';
5
+ function contextStoreBindingWarningToStatus(warning) {
6
+ return makeStatus('warning', warning.code, warning.message, {
7
+ target: warning.target ? `workspace.context.store.${warning.target}` : 'workspace.context.store',
8
+ ...(warning.fix ? { fix: warning.fix } : {}),
9
+ });
10
+ }
11
+ export async function collectWorkspaceContextStatuses(context) {
12
+ if (!context) {
13
+ return [];
14
+ }
15
+ const initiativeId = getWorkspaceContextInitiativeId(context);
16
+ const contextStoreLabel = formatContextStoreBinding(context.store);
17
+ const selector = formatContextStoreBindingSelector(context.store);
18
+ let resolvedStore;
19
+ try {
20
+ resolvedStore = await resolveContextStoreBinding(context.store);
21
+ }
22
+ catch (error) {
23
+ return [
24
+ makeStatus('error', 'workspace_context_store_unavailable', `Workspace context store '${contextStoreLabel}' could not be read: ${asErrorMessage(error)}`, {
25
+ target: 'workspace.context.store',
26
+ fix: context.store.selector.kind === 'registry'
27
+ ? 'openspec context-store doctor'
28
+ : `Check the path in workspace.yaml or run openspec initiative show ${initiativeId} ${selector}`,
29
+ }),
30
+ ];
31
+ }
32
+ const statuses = resolvedStore.warnings.map(contextStoreBindingWarningToStatus);
33
+ try {
34
+ const initiative = await readInitiative({
35
+ collection: mountInitiativesCollection(resolvedStore.root),
36
+ id: initiativeId,
37
+ });
38
+ if (!initiative) {
39
+ return [
40
+ ...statuses,
41
+ makeStatus('error', 'workspace_initiative_missing', `Workspace initiative '${contextStoreLabel}/${initiativeId}' was not found.`, {
42
+ target: 'workspace.context.initiative',
43
+ fix: `openspec initiative show ${initiativeId} ${selector}`,
44
+ }),
45
+ ];
46
+ }
47
+ return statuses;
48
+ }
49
+ catch (error) {
50
+ return [
51
+ ...statuses,
52
+ makeStatus('error', 'workspace_initiative_unavailable', `Workspace initiative '${contextStoreLabel}/${initiativeId}' could not be read: ${asErrorMessage(error)}`, {
53
+ target: 'workspace.context.initiative',
54
+ fix: `openspec initiative show ${initiativeId} ${selector}`,
55
+ }),
56
+ ];
57
+ }
58
+ }
59
+ //# sourceMappingURL=context-status.js.map
@@ -0,0 +1,13 @@
1
+ import { type InitiativeViewReference } from '../../core/collections/initiatives/index.js';
2
+ import { type SelectedWorkspace, type WorkspaceOpenOptions, type WorkspaceStatus } from './types.js';
3
+ export type WorkspaceOpenTarget = {
4
+ kind: 'workspace';
5
+ selected: SelectedWorkspace;
6
+ status: WorkspaceStatus[];
7
+ } | {
8
+ kind: 'initiative';
9
+ initiative: InitiativeViewReference;
10
+ status: WorkspaceStatus[];
11
+ };
12
+ export declare function selectWorkspaceOpenTarget(workspaceName: string | undefined, options: WorkspaceOpenOptions): Promise<WorkspaceOpenTarget>;
13
+ //# sourceMappingURL=open-target-selection.d.ts.map
@@ -0,0 +1,146 @@
1
+ import { InitiativeResolutionError, initiativeDiagnosticFromError, listInitiativeViewReferences, } from '../../core/collections/initiatives/index.js';
2
+ import { createRegisteredContextStoreBinding, sameContextStoreBinding, } from '../../core/context-store/index.js';
3
+ import { findWorkspaceRoot, getWorkspaceContextInitiativeId, listKnownWorkspaceEntries, readWorkspaceViewState, } from '../../core/workspace/index.js';
4
+ import { isInteractive, resolveNoInteractive } from '../../utils/interactive.js';
5
+ import { selectWorkspaceForCommand, selectedWorkspaceFromEntry, selectedWorkspaceFromRoot, } from './selection.js';
6
+ import { WorkspaceCliError, } from './types.js';
7
+ async function readKnownWorkspaceContexts(entries) {
8
+ return Promise.all(entries.map(async (entry) => {
9
+ try {
10
+ return (await readWorkspaceViewState(entry.workspaceRoot)).context;
11
+ }
12
+ catch {
13
+ // Broken workspaces are surfaced by list/doctor; open target selection
14
+ // should not hide otherwise openable initiatives behind unreadable views.
15
+ return null;
16
+ }
17
+ }));
18
+ }
19
+ function workspaceContextMatchesInitiative(context, initiative) {
20
+ return (context !== null &&
21
+ sameContextStoreBinding(context.store, createRegisteredContextStoreBinding(initiative.store)) &&
22
+ getWorkspaceContextInitiativeId(context) === initiative.id);
23
+ }
24
+ function initiativeHasKnownWorkspace(contexts, initiative) {
25
+ return contexts.some((context) => workspaceContextMatchesInitiative(context, initiative));
26
+ }
27
+ function initiativeDiagnosticToWorkspaceStatus(diagnostic) {
28
+ return {
29
+ severity: diagnostic.severity,
30
+ code: diagnostic.code,
31
+ message: diagnostic.message,
32
+ target: diagnostic.target,
33
+ fix: diagnostic.fix,
34
+ details: diagnostic.details,
35
+ };
36
+ }
37
+ async function listOpenableInitiatives(entries) {
38
+ try {
39
+ const [result, contexts] = await Promise.all([
40
+ listInitiativeViewReferences(),
41
+ readKnownWorkspaceContexts(entries),
42
+ ]);
43
+ const initiatives = [];
44
+ for (const initiative of result.initiatives) {
45
+ if (!initiativeHasKnownWorkspace(contexts, initiative)) {
46
+ initiatives.push(initiative);
47
+ }
48
+ }
49
+ return {
50
+ kind: 'listed',
51
+ initiatives,
52
+ status: result.status.map(initiativeDiagnosticToWorkspaceStatus),
53
+ };
54
+ }
55
+ catch (error) {
56
+ const diagnostic = error instanceof InitiativeResolutionError
57
+ ? initiativeDiagnosticFromError(error)
58
+ : {
59
+ severity: 'error',
60
+ code: 'initiative_discovery_failed',
61
+ message: error instanceof Error ? error.message : String(error),
62
+ target: 'initiative',
63
+ fix: 'openspec context-store doctor',
64
+ };
65
+ return {
66
+ kind: 'unavailable',
67
+ initiatives: [],
68
+ status: [initiativeDiagnosticToWorkspaceStatus(diagnostic)],
69
+ error: new WorkspaceCliError(diagnostic.message, diagnostic.code, {
70
+ target: diagnostic.target,
71
+ fix: diagnostic.fix,
72
+ details: diagnostic.details,
73
+ }),
74
+ };
75
+ }
76
+ }
77
+ export async function selectWorkspaceOpenTarget(workspaceName, options) {
78
+ if (workspaceName ||
79
+ options.json ||
80
+ resolveNoInteractive(options) ||
81
+ !isInteractive(options)) {
82
+ return {
83
+ kind: 'workspace',
84
+ selected: await selectWorkspaceForCommand({
85
+ ...options,
86
+ workspace: workspaceName,
87
+ }, 'open', { preferPositionalName: true }),
88
+ status: [],
89
+ };
90
+ }
91
+ const entries = await listKnownWorkspaceEntries();
92
+ const currentWorkspaceRoot = await findWorkspaceRoot(process.cwd());
93
+ if (currentWorkspaceRoot) {
94
+ return {
95
+ kind: 'workspace',
96
+ selected: await selectedWorkspaceFromRoot(currentWorkspaceRoot, entries),
97
+ status: [],
98
+ };
99
+ }
100
+ const listed = await listOpenableInitiatives(entries);
101
+ if (listed.initiatives.length === 0) {
102
+ if (listed.kind === 'unavailable' && entries.length === 0) {
103
+ throw listed.error;
104
+ }
105
+ return {
106
+ kind: 'workspace',
107
+ selected: await selectWorkspaceForCommand(options, 'open', {
108
+ preferPositionalName: true,
109
+ }),
110
+ status: listed.status,
111
+ };
112
+ }
113
+ const { select } = await import('@inquirer/prompts');
114
+ const selected = await select({
115
+ message: 'Select workspace or initiative:',
116
+ choices: [
117
+ ...entries.map((entry) => ({
118
+ name: `Workspace: ${entry.name} (${entry.workspaceRoot})`,
119
+ value: {
120
+ kind: 'workspace',
121
+ entry,
122
+ },
123
+ })),
124
+ ...listed.initiatives.map((initiative) => ({
125
+ name: `Initiative: ${initiative.store}/${initiative.id} - ${initiative.title} (create local workspace view)`,
126
+ value: {
127
+ kind: 'initiative',
128
+ initiative,
129
+ },
130
+ })),
131
+ ],
132
+ });
133
+ if (selected.kind === 'workspace') {
134
+ return {
135
+ kind: 'workspace',
136
+ selected: selectedWorkspaceFromEntry(selected.entry),
137
+ status: listed.status,
138
+ };
139
+ }
140
+ return {
141
+ kind: 'initiative',
142
+ initiative: selected.initiative,
143
+ status: listed.status,
144
+ };
145
+ }
146
+ //# sourceMappingURL=open-target-selection.js.map
@@ -0,0 +1,62 @@
1
+ import { InitiativeViewReference } from '../../core/collections/initiatives/index.js';
2
+ import { type ContextStoreBinding } from '../../core/context-store/index.js';
3
+ import { WorkspaceContextState, WorkspacePreferredOpener } from '../../core/workspace/index.js';
4
+ import { type WorkspaceOpenCommandBuildResult } from './open.js';
5
+ import { SelectedWorkspace, WorkspaceOpenOptions, WorkspaceStatus } from './types.js';
6
+ export interface PreparedWorkspaceOpen extends WorkspaceOpenCommandBuildResult {
7
+ selected: SelectedWorkspace;
8
+ opener: WorkspacePreferredOpener;
9
+ initiative: InitiativeViewReference | null;
10
+ workspaceContext: WorkspaceContextState | null;
11
+ warnings: WorkspaceStatus[];
12
+ }
13
+ export interface WorkspaceOpenJsonPayload {
14
+ schema_version: 1;
15
+ workspace: {
16
+ name: string;
17
+ root: string;
18
+ };
19
+ context: {
20
+ context_store: {
21
+ id: string;
22
+ root: string;
23
+ selector?: ContextStoreBinding['selector'];
24
+ };
25
+ initiative: {
26
+ id: string;
27
+ title: string;
28
+ root: string;
29
+ metadata_path: string;
30
+ store_path: string;
31
+ };
32
+ } | null;
33
+ generated_files: {
34
+ agents: string;
35
+ code_workspace: string;
36
+ };
37
+ opened_roots: PreparedWorkspaceOpen['openedRoots'];
38
+ skipped_roots: Array<{
39
+ kind: 'link';
40
+ name: string;
41
+ path: string | null;
42
+ reason: PreparedWorkspaceOpen['skipped'][number]['reason'];
43
+ }>;
44
+ advisory_edit_boundaries: {
45
+ allowed_edit_roots: string[];
46
+ coordination_roots: string[];
47
+ enforcement: 'advisory';
48
+ };
49
+ opener: PreparedWorkspaceOpen['opener'] & {
50
+ label: string;
51
+ };
52
+ launch: {
53
+ attempted: true;
54
+ status: 'succeeded';
55
+ };
56
+ warnings: WorkspaceStatus[];
57
+ status: WorkspaceStatus[];
58
+ }
59
+ export declare function assertWorkspaceOpenSupportedOptions(options: WorkspaceOpenOptions): void;
60
+ export declare function prepareWorkspaceOpen(positionalName: string | undefined, options: WorkspaceOpenOptions): Promise<PreparedWorkspaceOpen>;
61
+ export declare function buildWorkspaceOpenJsonPayload(prepared: PreparedWorkspaceOpen): WorkspaceOpenJsonPayload;
62
+ //# sourceMappingURL=open-view.d.ts.map
@@ -0,0 +1,249 @@
1
+ import { InitiativeResolutionError, resolveInitiativeViewReference, resolveSelectedInitiativeViewReference, } from '../../core/collections/initiatives/index.js';
2
+ import { createPathContextStoreBinding, createRegisteredContextStoreBinding, formatContextStoreBinding, resolveContextStoreBinding, } from '../../core/context-store/index.js';
3
+ import { createWorkspaceInitiativeContext, getWorkspaceContextInitiativeId, getWorkspaceOpenerLabel, } from '../../core/workspace/index.js';
4
+ import { isInteractive, resolveNoInteractive } from '../../utils/interactive.js';
5
+ import { assertWorkspaceOpenerAvailable, buildWorkspaceOpenCommandForState, readWorkspaceOpenState, } from './open.js';
6
+ import { selectOrCreateWorkspaceForInitiativeOpen, } from './operations.js';
7
+ import { selectWorkspaceOpenTarget } from './open-target-selection.js';
8
+ import { WorkspaceCliError, asErrorMessage, } from './types.js';
9
+ import { resolveWorkspaceOpenOpener, resolveWorkspaceOpenOpenerOverride, } from './opener-selection.js';
10
+ import { promptSetupLinks } from './setup-prompts.js';
11
+ export function assertWorkspaceOpenSupportedOptions(options) {
12
+ if (!options.initiative && (options.store || options.storePath)) {
13
+ throw new WorkspaceCliError('workspace open accepts --store or --store-path only with --initiative.', 'workspace_open_store_without_initiative', {
14
+ target: 'workspace.initiative',
15
+ fix: 'Use openspec workspace open --initiative <id> --store <store>.',
16
+ });
17
+ }
18
+ if (options.prepareOnly) {
19
+ throw new WorkspaceCliError('workspace open supports launching through a selected opener; preview output is reserved for a future context/query surface.', 'workspace_open_prepare_only_unsupported', {
20
+ target: 'workspace.open',
21
+ fix: 'Run openspec workspace open with --agent <tool> or --editor.',
22
+ });
23
+ }
24
+ if (options.change) {
25
+ throw new WorkspaceCliError('workspace open currently supports root workspace open only; change-scoped open belongs to future workspace change planning.', 'workspace_open_change_unsupported', {
26
+ target: 'workspace.change',
27
+ fix: 'Open the root workspace, then start implementation from an explicit change workflow.',
28
+ });
29
+ }
30
+ }
31
+ function resolveOpenWorkspaceName(positionalName, options) {
32
+ if (positionalName && options.workspace && positionalName !== options.workspace) {
33
+ throw new WorkspaceCliError(`Conflicting workspace selectors: positional '${positionalName}' and --workspace '${options.workspace}'.`, 'workspace_selection_conflict', {
34
+ target: 'workspace.name',
35
+ fix: 'Use either the positional workspace name or --workspace with the same value.',
36
+ });
37
+ }
38
+ return positionalName ?? options.workspace;
39
+ }
40
+ function initiativeErrorAsWorkspaceError(error) {
41
+ if (error instanceof InitiativeResolutionError) {
42
+ return new WorkspaceCliError(error.message, error.code, {
43
+ target: error.target,
44
+ fix: error.fix,
45
+ details: error.details,
46
+ });
47
+ }
48
+ return new WorkspaceCliError(asErrorMessage(error), 'initiative_error');
49
+ }
50
+ async function resolveWorkspaceOpenInitiative(options) {
51
+ if (!options.initiative) {
52
+ return null;
53
+ }
54
+ try {
55
+ return await resolveInitiativeViewReference(options.initiative, {
56
+ store: options.store,
57
+ storePath: options.storePath,
58
+ });
59
+ }
60
+ catch (error) {
61
+ throw initiativeErrorAsWorkspaceError(error);
62
+ }
63
+ }
64
+ async function resolveStoredWorkspaceInitiative(context) {
65
+ const initiativeId = getWorkspaceContextInitiativeId(context);
66
+ try {
67
+ const resolvedStore = await resolveContextStoreBinding(context.store);
68
+ const selected = {
69
+ id: resolvedStore.id,
70
+ root: resolvedStore.root,
71
+ source: resolvedStore.source,
72
+ };
73
+ const initiative = await resolveSelectedInitiativeViewReference(selected, initiativeId);
74
+ return {
75
+ initiative,
76
+ warnings: resolvedStore.warnings.map(contextStoreBindingWarningToStatus),
77
+ };
78
+ }
79
+ catch (error) {
80
+ if (error instanceof InitiativeResolutionError) {
81
+ throw initiativeErrorAsWorkspaceError(error);
82
+ }
83
+ throw new WorkspaceCliError(`Workspace context store '${formatContextStoreBinding(context.store)}' could not be read: ${asErrorMessage(error)}`, 'workspace_context_store_unavailable', {
84
+ target: 'workspace.context.store',
85
+ fix: context.store.selector.kind === 'registry'
86
+ ? 'openspec context-store doctor'
87
+ : 'Check the path in workspace.yaml.',
88
+ });
89
+ }
90
+ }
91
+ function contextStoreBindingWarningToStatus(warning) {
92
+ return {
93
+ severity: 'warning',
94
+ code: warning.code,
95
+ message: warning.message,
96
+ target: warning.target ? `workspace.context.store.${warning.target}` : 'workspace.context.store',
97
+ ...(warning.fix ? { fix: warning.fix } : {}),
98
+ };
99
+ }
100
+ function contextStoreBindingFromInitiative(initiative) {
101
+ return initiative.storeSource === 'path'
102
+ ? createPathContextStoreBinding({
103
+ id: initiative.store,
104
+ path: initiative.storeRoot,
105
+ })
106
+ : createRegisteredContextStoreBinding(initiative.store);
107
+ }
108
+ function toWorkspaceOpenResolvedContext(initiative) {
109
+ return {
110
+ contextStore: {
111
+ id: initiative.store,
112
+ root: initiative.storeRoot,
113
+ },
114
+ initiative: {
115
+ id: initiative.id,
116
+ title: initiative.title,
117
+ root: initiative.root,
118
+ metadataPath: initiative.metadataPath,
119
+ storePath: initiative.storePath,
120
+ },
121
+ };
122
+ }
123
+ function buildSkippedRootWarnings(skipped) {
124
+ return skipped.map((link) => {
125
+ const location = link.path ?? '(no local path recorded)';
126
+ return {
127
+ severity: 'warning',
128
+ code: 'workspace_open_link_skipped',
129
+ message: `Skipped linked repo or folder '${link.name}' because ${location} is not available.`,
130
+ target: `links.${link.name}.path`,
131
+ fix: `openspec workspace relink ${link.name} /path/to/${link.name}`,
132
+ };
133
+ });
134
+ }
135
+ export async function prepareWorkspaceOpen(positionalName, options) {
136
+ assertWorkspaceOpenSupportedOptions(options);
137
+ const workspaceName = resolveOpenWorkspaceName(positionalName, options);
138
+ const openerOverride = resolveWorkspaceOpenOpenerOverride(options);
139
+ const requestedInitiative = await resolveWorkspaceOpenInitiative(options);
140
+ const target = requestedInitiative
141
+ ? { kind: 'initiative', initiative: requestedInitiative, status: [] }
142
+ : await selectWorkspaceOpenTarget(workspaceName, options);
143
+ const interactiveCreate = target.kind === 'initiative'
144
+ && !options.json
145
+ && !resolveNoInteractive(options)
146
+ && isInteractive(options);
147
+ const baseSelected = target.kind === 'initiative'
148
+ ? (await selectOrCreateWorkspaceForInitiativeOpen({
149
+ workspaceName,
150
+ context: createWorkspaceInitiativeContext(contextStoreBindingFromInitiative(target.initiative), target.initiative.id),
151
+ preferredOpener: openerOverride,
152
+ linksForNewWorkspace: interactiveCreate
153
+ ? () => promptSetupLinks({
154
+ heading: 'Link repos or folders for this workspace',
155
+ intro: 'Choose local repos or folders to include when opening this initiative, or create the view without links for now.',
156
+ allowEmpty: true,
157
+ emptyName: 'Create without linked repos',
158
+ emptyShort: 'Create without links',
159
+ emptyDescription: 'Create the local workspace view and add repos or folders later',
160
+ finishName: 'Create and open workspace',
161
+ finishShort: 'Create and open',
162
+ finishDescription: 'Create the local workspace view and continue opening it',
163
+ })
164
+ : undefined,
165
+ })).selected
166
+ : target.selected;
167
+ const selected = {
168
+ ...baseSelected,
169
+ status: [...baseSelected.status, ...target.status],
170
+ };
171
+ const state = await readWorkspaceOpenState(selected);
172
+ const stored = target.kind === 'workspace' && state.viewState.context
173
+ ? await resolveStoredWorkspaceInitiative(state.viewState.context)
174
+ : null;
175
+ const initiative = target.kind === 'initiative' ? target.initiative : stored?.initiative ?? null;
176
+ const resolvedContext = initiative ? toWorkspaceOpenResolvedContext(initiative) : null;
177
+ const opener = await resolveWorkspaceOpenOpener(state.viewState, options);
178
+ assertWorkspaceOpenerAvailable(opener, state.codeWorkspacePath);
179
+ const buildResult = await buildWorkspaceOpenCommandForState(opener, selected.root, state, resolvedContext);
180
+ return {
181
+ ...buildResult,
182
+ selected,
183
+ opener,
184
+ initiative,
185
+ workspaceContext: state.viewState.context,
186
+ warnings: [
187
+ ...selected.status,
188
+ ...(stored?.warnings ?? []),
189
+ ...buildSkippedRootWarnings(buildResult.skipped),
190
+ ],
191
+ };
192
+ }
193
+ export function buildWorkspaceOpenJsonPayload(prepared) {
194
+ const linkedEditRoots = prepared.openedRoots
195
+ .filter((root) => root.kind === 'link')
196
+ .map((root) => root.path);
197
+ return {
198
+ schema_version: 1,
199
+ workspace: {
200
+ name: prepared.selected.name,
201
+ root: prepared.selected.root,
202
+ },
203
+ context: prepared.initiative
204
+ ? {
205
+ context_store: {
206
+ id: prepared.initiative.store,
207
+ root: prepared.initiative.storeRoot,
208
+ ...(prepared.workspaceContext
209
+ ? { selector: prepared.workspaceContext.store.selector }
210
+ : {}),
211
+ },
212
+ initiative: {
213
+ id: prepared.initiative.id,
214
+ title: prepared.initiative.title,
215
+ root: prepared.initiative.root,
216
+ metadata_path: prepared.initiative.metadataPath,
217
+ store_path: prepared.initiative.storePath,
218
+ },
219
+ }
220
+ : null,
221
+ generated_files: {
222
+ agents: prepared.generated.agentsPath,
223
+ code_workspace: prepared.generated.codeWorkspacePath,
224
+ },
225
+ opened_roots: prepared.openedRoots,
226
+ skipped_roots: prepared.skipped.map((link) => ({
227
+ kind: 'link',
228
+ name: link.name,
229
+ path: link.path,
230
+ reason: link.reason,
231
+ })),
232
+ advisory_edit_boundaries: {
233
+ allowed_edit_roots: linkedEditRoots,
234
+ coordination_roots: prepared.initiative ? [prepared.initiative.root] : [],
235
+ enforcement: 'advisory',
236
+ },
237
+ opener: {
238
+ ...prepared.opener,
239
+ label: getWorkspaceOpenerLabel(prepared.opener),
240
+ },
241
+ launch: {
242
+ attempted: true,
243
+ status: 'succeeded',
244
+ },
245
+ warnings: prepared.warnings,
246
+ status: [],
247
+ };
248
+ }
249
+ //# sourceMappingURL=open-view.js.map