gmc-openspec 1.0.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/openspec.js +3 -1
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.js +36 -2
- package/dist/commands/config.js +4 -4
- package/dist/commands/context-store.d.ts +3 -0
- package/dist/commands/context-store.js +475 -0
- package/dist/commands/initiative.d.ts +13 -0
- package/dist/commands/initiative.js +318 -0
- package/dist/commands/workflow/index.d.ts +2 -0
- package/dist/commands/workflow/index.js +1 -0
- package/dist/commands/workflow/initiative-link.d.ts +24 -0
- package/dist/commands/workflow/initiative-link.js +47 -0
- package/dist/commands/workflow/instructions.js +10 -2
- package/dist/commands/workflow/new-change.d.ts +4 -0
- package/dist/commands/workflow/new-change.js +72 -23
- package/dist/commands/workflow/set-change.d.ts +13 -0
- package/dist/commands/workflow/set-change.js +87 -0
- package/dist/commands/workflow/shared.d.ts +2 -0
- package/dist/commands/workflow/status.js +3 -0
- package/dist/commands/workspace/context-status.d.ts +4 -0
- package/dist/commands/workspace/context-status.js +59 -0
- package/dist/commands/workspace/open-target-selection.d.ts +13 -0
- package/dist/commands/workspace/open-target-selection.js +146 -0
- package/dist/commands/workspace/open-view.d.ts +62 -0
- package/dist/commands/workspace/open-view.js +249 -0
- package/dist/commands/workspace/open.d.ts +16 -8
- package/dist/commands/workspace/open.js +40 -14
- package/dist/commands/workspace/opener-selection.d.ts +11 -0
- package/dist/commands/workspace/opener-selection.js +98 -0
- package/dist/commands/workspace/operations.d.ts +14 -8
- package/dist/commands/workspace/operations.js +228 -160
- package/dist/commands/workspace/prompt-theme.d.ts +29 -0
- package/dist/commands/workspace/prompt-theme.js +24 -0
- package/dist/commands/workspace/registration.d.ts +13 -0
- package/dist/commands/workspace/registration.js +84 -0
- package/dist/commands/workspace/selection.d.ts +3 -0
- package/dist/commands/workspace/selection.js +42 -40
- package/dist/commands/workspace/setup-prompts.d.ts +13 -0
- package/dist/commands/workspace/setup-prompts.js +121 -0
- package/dist/commands/workspace/types.d.ts +15 -0
- package/dist/commands/workspace.js +59 -340
- package/dist/core/artifact-graph/index.d.ts +2 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +10 -23
- package/dist/core/artifact-graph/instruction-loader.js +28 -89
- package/dist/core/artifact-graph/types.d.ts +0 -7
- package/dist/core/artifact-graph/types.js +0 -19
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +18 -0
- package/dist/core/change-metadata/schema.js +28 -0
- package/dist/core/change-status-policy.d.ts +50 -0
- package/dist/core/change-status-policy.js +70 -0
- package/dist/core/collections/index.d.ts +3 -0
- package/dist/core/collections/index.js +3 -0
- package/dist/core/collections/initiatives/collection.d.ts +4 -0
- package/dist/core/collections/initiatives/collection.js +17 -0
- package/dist/core/collections/initiatives/index.d.ts +6 -0
- package/dist/core/collections/initiatives/index.js +6 -0
- package/dist/core/collections/initiatives/operations.d.ts +49 -0
- package/dist/core/collections/initiatives/operations.js +175 -0
- package/dist/core/collections/initiatives/resolution.d.ts +87 -0
- package/dist/core/collections/initiatives/resolution.js +374 -0
- package/dist/core/collections/initiatives/schema.d.ts +41 -0
- package/dist/core/collections/initiatives/schema.js +134 -0
- package/dist/core/collections/initiatives/templates.d.ts +12 -0
- package/dist/core/collections/initiatives/templates.js +90 -0
- package/dist/core/collections/runtime.d.ts +46 -0
- package/dist/core/collections/runtime.js +194 -0
- package/dist/core/completions/command-registry.d.ts +1 -5
- package/dist/core/completions/command-registry.js +475 -70
- package/dist/core/completions/shared-flags.d.ts +12 -0
- package/dist/core/completions/shared-flags.js +28 -0
- package/dist/core/config.js +2 -1
- package/dist/core/context-store/binding.d.ts +53 -0
- package/dist/core/context-store/binding.js +197 -0
- package/dist/core/context-store/errors.d.ts +20 -0
- package/dist/core/context-store/errors.js +22 -0
- package/dist/core/context-store/foundation.d.ts +55 -0
- package/dist/core/context-store/foundation.js +321 -0
- package/dist/core/context-store/index.d.ts +6 -0
- package/dist/core/context-store/index.js +6 -0
- package/dist/core/context-store/operations.d.ts +85 -0
- package/dist/core/context-store/operations.js +528 -0
- package/dist/core/context-store/registry.d.ts +45 -0
- package/dist/core/context-store/registry.js +229 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/planning-home.js +5 -21
- package/dist/core/validation/validator.d.ts +11 -0
- package/dist/core/validation/validator.js +19 -2
- package/dist/core/workspace/foundation.d.ts +28 -48
- package/dist/core/workspace/foundation.js +130 -214
- package/dist/core/workspace/index.d.ts +2 -0
- package/dist/core/workspace/index.js +2 -0
- package/dist/core/workspace/legacy-state.d.ts +28 -0
- package/dist/core/workspace/legacy-state.js +200 -0
- package/dist/core/workspace/open-surface.d.ts +29 -8
- package/dist/core/workspace/open-surface.js +122 -44
- package/dist/core/workspace/openers.js +11 -6
- package/dist/core/workspace/registry.d.ts +24 -0
- package/dist/core/workspace/registry.js +146 -0
- package/dist/core/workspace/skills.d.ts +4 -2
- package/dist/core/workspace/skills.js +2 -2
- package/dist/core/workspace/state-io.d.ts +10 -0
- package/dist/core/workspace/state-io.js +119 -0
- package/dist/utils/change-metadata.d.ts +5 -2
- package/dist/utils/change-metadata.js +6 -12
- package/dist/utils/change-utils.d.ts +2 -2
- package/package.json +17 -19
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { getContextStoreMetadataPath, getContextStoreMetadataDir, listContextStoreRegistryEntries, readContextStoreRegistryState, readOptionalContextStoreMetadataState, resolveGitContextStoreBackendConfig, updateContextStoreRegistryState, validateContextStoreId, writeContextStoreMetadataState, } from './foundation.js';
|
|
3
|
+
import { ContextStoreError } from './errors.js';
|
|
4
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
|
+
export function getStoreRootForBackend(backend) {
|
|
6
|
+
switch (backend.type) {
|
|
7
|
+
case 'git':
|
|
8
|
+
return backend.local_path;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function normalizePathForComparison(targetPath) {
|
|
12
|
+
try {
|
|
13
|
+
return FileSystemUtils.canonicalizeExistingPath(targetPath);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return targetPath;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function assertNoRegisteredStoreConflict(registry, id, backend) {
|
|
20
|
+
const nextPath = normalizePathForComparison(getStoreRootForBackend(backend));
|
|
21
|
+
for (const entry of listContextStoreRegistryEntries(registry ?? { version: 1, stores: {} })) {
|
|
22
|
+
const entryPath = normalizePathForComparison(getStoreRootForBackend(entry.backend));
|
|
23
|
+
if (entry.id === id && entryPath === nextPath) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (entry.id === id) {
|
|
27
|
+
throw new ContextStoreError(`Context store '${id}' is already registered at ${getStoreRootForBackend(entry.backend)}.`, 'context_store_id_conflict', {
|
|
28
|
+
target: 'context_store.id',
|
|
29
|
+
fix: 'Use the existing registration or choose a different context store id.',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (entryPath === nextPath) {
|
|
33
|
+
throw new ContextStoreError(`Context store path is already registered as '${entry.id}'.`, 'context_store_path_conflict', {
|
|
34
|
+
target: 'context_store.root',
|
|
35
|
+
fix: `Use the existing '${entry.id}' registration or choose a different path.`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function withRegisteredStore(registry, id, backend) {
|
|
41
|
+
assertNoRegisteredStoreConflict(registry, id, backend);
|
|
42
|
+
const stores = {
|
|
43
|
+
...(registry?.stores ?? {}),
|
|
44
|
+
[id]: {
|
|
45
|
+
backend,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
version: 1,
|
|
50
|
+
stores: Object.fromEntries(Object.entries(stores).sort(([leftId], [rightId]) => leftId.localeCompare(rightId))),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function getRegisteredStoreOrThrow(registry, id) {
|
|
54
|
+
const entry = registry?.stores[id];
|
|
55
|
+
if (!entry) {
|
|
56
|
+
throw new ContextStoreError(`Unknown context store '${id}'`, 'context_store_not_found', {
|
|
57
|
+
target: 'context_store.id',
|
|
58
|
+
fix: 'Run openspec context-store list to see registered stores.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
backend: entry.backend,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function contextStoreBackendsMatch(actual, expected) {
|
|
67
|
+
return (actual.type === expected.type &&
|
|
68
|
+
normalizePathForComparison(actual.local_path) ===
|
|
69
|
+
normalizePathForComparison(expected.local_path) &&
|
|
70
|
+
actual.remote === expected.remote &&
|
|
71
|
+
actual.branch === expected.branch);
|
|
72
|
+
}
|
|
73
|
+
function assertExpectedRegisteredBackend(id, actual, expected) {
|
|
74
|
+
if (!expected || contextStoreBackendsMatch(actual, expected))
|
|
75
|
+
return;
|
|
76
|
+
throw new ContextStoreError(`Context store '${id}' changed before cleanup completed.`, 'context_store_registry_changed', {
|
|
77
|
+
target: 'context_store.registry',
|
|
78
|
+
fix: 'Retry the cleanup command after reviewing the current context-store registration.',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function withoutRegisteredStore(registry, id, expectedBackend) {
|
|
82
|
+
const removed = getRegisteredStoreOrThrow(registry, id);
|
|
83
|
+
assertExpectedRegisteredBackend(id, removed.backend, expectedBackend);
|
|
84
|
+
const stores = { ...(registry?.stores ?? {}) };
|
|
85
|
+
delete stores[id];
|
|
86
|
+
return {
|
|
87
|
+
removed,
|
|
88
|
+
next: {
|
|
89
|
+
version: 1,
|
|
90
|
+
stores: Object.fromEntries(Object.entries(stores).sort(([leftId], [rightId]) => leftId.localeCompare(rightId))),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function ensureStoreMetadata(storeRoot, id, options) {
|
|
95
|
+
const metadata = await readOptionalContextStoreMetadataState(storeRoot);
|
|
96
|
+
if (!metadata) {
|
|
97
|
+
if (!options.writeIfMissing) {
|
|
98
|
+
throw new ContextStoreError(`Registered context store '${id}' is missing metadata at ${getContextStoreMetadataPath(storeRoot)}`, 'context_store_metadata_missing', {
|
|
99
|
+
target: 'context_store.metadata',
|
|
100
|
+
fix: `Create ${getContextStoreMetadataPath(storeRoot)} or rerun context-store register.`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
await writeContextStoreMetadataState(storeRoot, {
|
|
104
|
+
version: 1,
|
|
105
|
+
id,
|
|
106
|
+
});
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (metadata.id !== id) {
|
|
110
|
+
throw new ContextStoreError(`Context store metadata id '${metadata.id}' does not match registered id '${id}'`, 'context_store_metadata_id_mismatch', {
|
|
111
|
+
target: 'context_store.metadata',
|
|
112
|
+
fix: 'Repair the local registry or store metadata so the ids match.',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
export async function commitContextStoreRegistration(input) {
|
|
118
|
+
const id = validateContextStoreId(input.id);
|
|
119
|
+
const backend = input.backend;
|
|
120
|
+
const storeRoot = getStoreRootForBackend(backend);
|
|
121
|
+
let metadataCreated = false;
|
|
122
|
+
try {
|
|
123
|
+
metadataCreated = await ensureStoreMetadata(storeRoot, id, {
|
|
124
|
+
writeIfMissing: input.writeMetadataIfMissing,
|
|
125
|
+
});
|
|
126
|
+
await updateContextStoreRegistryState((registry) => withRegisteredStore(registry, id, backend), { globalDataDir: input.globalDataDir });
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (metadataCreated) {
|
|
130
|
+
await fs.rm(getContextStoreMetadataPath(storeRoot), { force: true });
|
|
131
|
+
await fs.rmdir(getContextStoreMetadataDir(storeRoot)).catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
id,
|
|
137
|
+
storeRoot,
|
|
138
|
+
backend,
|
|
139
|
+
metadataCreated,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export async function registerContextStore(input) {
|
|
143
|
+
const id = validateContextStoreId(input.id);
|
|
144
|
+
const backend = await resolveGitContextStoreBackendConfig({
|
|
145
|
+
localPath: input.localPath,
|
|
146
|
+
...(input.remote !== undefined ? { remote: input.remote } : {}),
|
|
147
|
+
...(input.branch !== undefined ? { branch: input.branch } : {}),
|
|
148
|
+
}, input.cwd);
|
|
149
|
+
const storeRoot = getStoreRootForBackend(backend);
|
|
150
|
+
const committed = await commitContextStoreRegistration({
|
|
151
|
+
id,
|
|
152
|
+
backend,
|
|
153
|
+
writeMetadataIfMissing: true,
|
|
154
|
+
...(input.globalDataDir ? { globalDataDir: input.globalDataDir } : {}),
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
id: committed.id,
|
|
158
|
+
storeRoot: committed.storeRoot,
|
|
159
|
+
backend: committed.backend,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export async function listRegisteredContextStores(options = {}) {
|
|
163
|
+
const registry = await readContextStoreRegistryState(options);
|
|
164
|
+
if (!registry) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
return listContextStoreRegistryEntries(registry).map((entry) => ({
|
|
168
|
+
...entry,
|
|
169
|
+
storeRoot: getStoreRootForBackend(entry.backend),
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
export async function getRegisteredContextStore(input) {
|
|
173
|
+
const id = validateContextStoreId(input.id);
|
|
174
|
+
const registry = await readContextStoreRegistryState({
|
|
175
|
+
globalDataDir: input.globalDataDir,
|
|
176
|
+
});
|
|
177
|
+
const entry = getRegisteredStoreOrThrow(registry, id);
|
|
178
|
+
assertExpectedRegisteredBackend(id, entry.backend, input.expectedBackend);
|
|
179
|
+
return {
|
|
180
|
+
...entry,
|
|
181
|
+
storeRoot: getStoreRootForBackend(entry.backend),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export async function unregisterContextStoreRegistration(input) {
|
|
185
|
+
const id = validateContextStoreId(input.id);
|
|
186
|
+
let removed;
|
|
187
|
+
await updateContextStoreRegistryState(async (registry) => {
|
|
188
|
+
const result = withoutRegisteredStore(registry, id, input.expectedBackend);
|
|
189
|
+
const removedEntry = {
|
|
190
|
+
...result.removed,
|
|
191
|
+
storeRoot: getStoreRootForBackend(result.removed.backend),
|
|
192
|
+
};
|
|
193
|
+
await input.beforeCommit?.(removedEntry);
|
|
194
|
+
removed = result.removed;
|
|
195
|
+
return result.next;
|
|
196
|
+
}, { globalDataDir: input.globalDataDir });
|
|
197
|
+
if (!removed) {
|
|
198
|
+
throw new ContextStoreError(`Unknown context store '${id}'`, 'context_store_not_found', {
|
|
199
|
+
target: 'context_store.id',
|
|
200
|
+
fix: 'Run openspec context-store list to see registered stores.',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
...removed,
|
|
205
|
+
storeRoot: getStoreRootForBackend(removed.backend),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
export async function resolveRegisteredContextStore(input) {
|
|
209
|
+
const id = validateContextStoreId(input.id);
|
|
210
|
+
const registry = await readContextStoreRegistryState({
|
|
211
|
+
globalDataDir: input.globalDataDir,
|
|
212
|
+
});
|
|
213
|
+
if (!registry) {
|
|
214
|
+
throw new ContextStoreError('No context store registry found', 'no_context_store_registry', {
|
|
215
|
+
target: 'context_store.id',
|
|
216
|
+
fix: 'Register a context store before using --store, or pass --store-path <path>.',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const entry = getRegisteredStoreOrThrow(registry, id);
|
|
220
|
+
const backend = entry.backend;
|
|
221
|
+
const storeRoot = getStoreRootForBackend(backend);
|
|
222
|
+
await ensureStoreMetadata(storeRoot, id, { writeIfMissing: false });
|
|
223
|
+
return {
|
|
224
|
+
id,
|
|
225
|
+
storeRoot,
|
|
226
|
+
backend,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=registry.js.map
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, GLOBAL_DATA_DIR_NAME, type GlobalDataDirOptions, type GlobalConfig, getGlobalConfigDir, getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, getGlobalDataDir } from './global-config.js';
|
|
2
2
|
export * from './workspace/index.js';
|
|
3
3
|
export * from './jira/index.js';
|
|
4
|
+
export * from './context-store/index.js';
|
|
5
|
+
export * from './collections/index.js';
|
|
4
6
|
export * from './planning-home.js';
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.js
CHANGED
|
@@ -2,5 +2,7 @@
|
|
|
2
2
|
export { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, GLOBAL_DATA_DIR_NAME, getGlobalConfigDir, getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, getGlobalDataDir } from './global-config.js';
|
|
3
3
|
export * from './workspace/index.js';
|
|
4
4
|
export * from './jira/index.js';
|
|
5
|
+
export * from './context-store/index.js';
|
|
6
|
+
export * from './collections/index.js';
|
|
5
7
|
export * from './planning-home.js';
|
|
6
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
-
import { getWorkspaceChangesDir,
|
|
3
|
+
import { getWorkspaceChangesDir, readWorkspaceViewStateSync, workspaceStateFileExistsSync, } from './workspace/index.js';
|
|
4
4
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
5
5
|
const REPO_DEFAULT_SCHEMA = 'spec-driven';
|
|
6
6
|
const WORKSPACE_DEFAULT_SCHEMA = 'workspace-planning';
|
|
@@ -12,14 +12,6 @@ function pathExistsAsDirectory(candidatePath) {
|
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
function pathExistsAsFile(candidatePath) {
|
|
16
|
-
try {
|
|
17
|
-
return fs.statSync(candidatePath).isFile();
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
15
|
function getSearchStartDirectory(startPath) {
|
|
24
16
|
const resolved = path.resolve(startPath);
|
|
25
17
|
try {
|
|
@@ -45,7 +37,7 @@ function findNearestAncestor(startPath, predicate) {
|
|
|
45
37
|
}
|
|
46
38
|
}
|
|
47
39
|
export function findWorkspacePlanningRootSync(startPath = process.cwd()) {
|
|
48
|
-
return findNearestAncestor(startPath,
|
|
40
|
+
return findNearestAncestor(startPath, workspaceStateFileExistsSync);
|
|
49
41
|
}
|
|
50
42
|
export function findRepoPlanningRootSync(startPath = process.cwd()) {
|
|
51
43
|
return findNearestAncestor(startPath, (dirPath) => pathExistsAsDirectory(path.join(dirPath, 'openspec')));
|
|
@@ -66,24 +58,16 @@ function relativePlanningPath(fromPath, toPath) {
|
|
|
66
58
|
}
|
|
67
59
|
return path.posix.relative(fromPath.replace(/\\/g, '/'), toPath.replace(/\\/g, '/'));
|
|
68
60
|
}
|
|
69
|
-
function readWorkspaceSharedStateSync(workspaceRoot) {
|
|
70
|
-
try {
|
|
71
|
-
return parseWorkspaceSharedState(fs.readFileSync(getWorkspaceSharedStatePath(workspaceRoot), 'utf-8'));
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
61
|
function workspacePlanningHome(workspaceRoot) {
|
|
78
|
-
const
|
|
62
|
+
const viewState = readWorkspaceViewStateSync(workspaceRoot);
|
|
79
63
|
return {
|
|
80
64
|
kind: 'workspace',
|
|
81
65
|
root: workspaceRoot,
|
|
82
66
|
changesDir: getWorkspaceChangesDir(workspaceRoot),
|
|
83
67
|
defaultSchema: WORKSPACE_DEFAULT_SCHEMA,
|
|
84
68
|
workspace: {
|
|
85
|
-
name:
|
|
86
|
-
links: Object.keys(
|
|
69
|
+
name: viewState?.name ?? path.basename(workspaceRoot),
|
|
70
|
+
links: Object.keys(viewState?.links ?? {}).sort((a, b) => a.localeCompare(b)),
|
|
87
71
|
},
|
|
88
72
|
};
|
|
89
73
|
}
|
|
@@ -27,6 +27,17 @@ export declare class Validator {
|
|
|
27
27
|
isValid(report: ValidationReport): boolean;
|
|
28
28
|
private extractRequirementText;
|
|
29
29
|
private containsShallOrMust;
|
|
30
|
+
/**
|
|
31
|
+
* Build an error message for a requirement block whose body lacks SHALL/MUST.
|
|
32
|
+
*
|
|
33
|
+
* When the SHALL/MUST keyword already appears in the requirement header (e.g.
|
|
34
|
+
* `### Requirement: The system SHALL ...`) the original generic error
|
|
35
|
+
* ("must contain SHALL or MUST") is confusing because the keyword is visibly
|
|
36
|
+
* present in the spec. Per the OpenSpec conventions the keyword has to live
|
|
37
|
+
* on the requirement body line (the line right after the header), so we point
|
|
38
|
+
* the author at that exact fix when the keyword is found in the header only.
|
|
39
|
+
*/
|
|
40
|
+
private buildMissingShallOrMustMessage;
|
|
30
41
|
private countScenarios;
|
|
31
42
|
private formatSectionList;
|
|
32
43
|
}
|
|
@@ -150,7 +150,7 @@ export class Validator {
|
|
|
150
150
|
issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" is missing requirement text` });
|
|
151
151
|
}
|
|
152
152
|
else if (!this.containsShallOrMust(requirementText)) {
|
|
153
|
-
issues.push({ level: 'ERROR', path: entryPath, message:
|
|
153
|
+
issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage('ADDED', block.name) });
|
|
154
154
|
}
|
|
155
155
|
const scenarioCount = this.countScenarios(block.raw);
|
|
156
156
|
if (scenarioCount < 1) {
|
|
@@ -172,7 +172,7 @@ export class Validator {
|
|
|
172
172
|
issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" is missing requirement text` });
|
|
173
173
|
}
|
|
174
174
|
else if (!this.containsShallOrMust(requirementText)) {
|
|
175
|
-
issues.push({ level: 'ERROR', path: entryPath, message:
|
|
175
|
+
issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage('MODIFIED', block.name) });
|
|
176
176
|
}
|
|
177
177
|
const scenarioCount = this.countScenarios(block.raw);
|
|
178
178
|
if (scenarioCount < 1) {
|
|
@@ -401,6 +401,23 @@ export class Validator {
|
|
|
401
401
|
containsShallOrMust(text) {
|
|
402
402
|
return /\b(SHALL|MUST)\b/.test(text);
|
|
403
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Build an error message for a requirement block whose body lacks SHALL/MUST.
|
|
406
|
+
*
|
|
407
|
+
* When the SHALL/MUST keyword already appears in the requirement header (e.g.
|
|
408
|
+
* `### Requirement: The system SHALL ...`) the original generic error
|
|
409
|
+
* ("must contain SHALL or MUST") is confusing because the keyword is visibly
|
|
410
|
+
* present in the spec. Per the OpenSpec conventions the keyword has to live
|
|
411
|
+
* on the requirement body line (the line right after the header), so we point
|
|
412
|
+
* the author at that exact fix when the keyword is found in the header only.
|
|
413
|
+
*/
|
|
414
|
+
buildMissingShallOrMustMessage(action, blockName) {
|
|
415
|
+
const base = `${action} "${blockName}" must contain SHALL or MUST`;
|
|
416
|
+
if (this.containsShallOrMust(blockName)) {
|
|
417
|
+
return `${base} in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the "### Requirement: ..." header.`;
|
|
418
|
+
}
|
|
419
|
+
return base;
|
|
420
|
+
}
|
|
404
421
|
countScenarios(blockRaw) {
|
|
405
422
|
const matches = blockRaw.match(/^####\s+/gm);
|
|
406
423
|
return matches ? matches.length : 0;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { type ContextStoreBinding, type ContextStoreSelector } from '../context-store/index.js';
|
|
1
2
|
export declare const WORKSPACE_METADATA_DIR_NAME = ".openspec-workspace";
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const WORKSPACE_LOCAL_STATE_FILE_NAME = "local.yaml";
|
|
3
|
+
export declare const WORKSPACE_VIEW_STATE_FILE_NAME = "workspace.yaml";
|
|
4
4
|
export declare const WORKSPACE_CHANGES_DIR_NAME = "changes";
|
|
5
|
-
export declare const MANAGED_WORKSPACES_DIR_NAME = "workspaces";
|
|
6
|
-
export declare const WORKSPACE_REGISTRY_FILE_NAME = "registry.yaml";
|
|
7
|
-
export declare const WORKSPACE_LOCAL_STATE_IGNORE_PATTERN = ".openspec-workspace/local.yaml";
|
|
8
5
|
export declare const WORKSPACE_CODE_WORKSPACE_EXTENSION = ".code-workspace";
|
|
9
|
-
export declare const WORKSPACE_SUPPORTED_OPENER_VALUES: readonly ["codex", "claude", "github-copilot", "editor"];
|
|
10
|
-
export declare const WORKSPACE_AGENT_OPENER_IDS: readonly ["codex", "claude", "github-copilot"];
|
|
6
|
+
export declare const WORKSPACE_SUPPORTED_OPENER_VALUES: readonly ["codex-cli", "claude", "github-copilot", "editor"];
|
|
7
|
+
export declare const WORKSPACE_AGENT_OPENER_IDS: readonly ["codex-cli", "claude", "github-copilot"];
|
|
11
8
|
export declare const WORKSPACE_EDITOR_OPENER_IDS: readonly ["vscode"];
|
|
12
9
|
export type WorkspaceSupportedOpenerValue = typeof WORKSPACE_SUPPORTED_OPENER_VALUES[number];
|
|
13
10
|
export type WorkspaceAgentOpenerId = typeof WORKSPACE_AGENT_OPENER_IDS[number];
|
|
@@ -19,16 +16,20 @@ export type WorkspacePreferredOpener = {
|
|
|
19
16
|
kind: 'editor';
|
|
20
17
|
id: WorkspaceEditorOpenerId;
|
|
21
18
|
};
|
|
22
|
-
export interface
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
export interface WorkspaceContextState {
|
|
20
|
+
kind: 'initiative';
|
|
21
|
+
store: ContextStoreBinding;
|
|
22
|
+
initiative: {
|
|
23
|
+
id: string;
|
|
24
|
+
};
|
|
26
25
|
}
|
|
27
|
-
export
|
|
28
|
-
export interface WorkspaceLocalState {
|
|
26
|
+
export interface WorkspaceViewState {
|
|
29
27
|
version: 1;
|
|
30
|
-
|
|
28
|
+
name: string;
|
|
29
|
+
context: WorkspaceContextState | null;
|
|
30
|
+
links: Record<string, string | null>;
|
|
31
31
|
preferred_opener?: WorkspacePreferredOpener;
|
|
32
|
+
tools?: string[];
|
|
32
33
|
workspace_skills?: WorkspaceSkillState;
|
|
33
34
|
}
|
|
34
35
|
export interface WorkspaceSkillState {
|
|
@@ -38,50 +39,29 @@ export interface WorkspaceSkillState {
|
|
|
38
39
|
last_applied_workflow_ids?: string[];
|
|
39
40
|
last_applied_at?: string;
|
|
40
41
|
}
|
|
41
|
-
export interface WorkspaceRegistryState {
|
|
42
|
-
version: 1;
|
|
43
|
-
workspaces: Record<string, string>;
|
|
44
|
-
}
|
|
45
|
-
export interface WorkspaceRegistryEntry {
|
|
46
|
-
name: string;
|
|
47
|
-
workspaceRoot: string;
|
|
48
|
-
}
|
|
49
|
-
export interface WorkspacePathOptions {
|
|
50
|
-
globalDataDir?: string;
|
|
51
|
-
}
|
|
52
42
|
export declare function getWorkspaceMetadataDir(workspaceRoot: string): string;
|
|
53
|
-
export declare function
|
|
54
|
-
export declare function getWorkspaceLocalStatePath(workspaceRoot: string): string;
|
|
43
|
+
export declare function getWorkspaceViewStatePath(workspaceRoot: string): string;
|
|
55
44
|
export declare function getWorkspaceChangesDir(workspaceRoot: string): string;
|
|
56
|
-
export declare function getManagedWorkspacesDir(options?: WorkspacePathOptions): string;
|
|
57
|
-
export declare function getManagedWorkspaceRoot(workspaceName: string, options?: WorkspacePathOptions): string;
|
|
58
|
-
export declare function getWorkspaceRegistryPath(options?: WorkspacePathOptions): string;
|
|
59
45
|
export declare function getWorkspaceCodeWorkspaceFileName(workspaceName: string): string;
|
|
60
46
|
export declare function getWorkspaceCodeWorkspacePath(workspaceRoot: string, workspaceName: string): string;
|
|
61
|
-
|
|
47
|
+
/**
|
|
48
|
+
* @deprecated Managed workspaces no longer create portable ignore rules.
|
|
49
|
+
* This compatibility shim remains for callers that still ask which ignore
|
|
50
|
+
* patterns OpenSpec owns for workspace-local generated files.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getWorkspacePortableIgnorePatterns(_workspaceName?: string): string[];
|
|
62
53
|
export declare function validateWorkspaceName(name: string): string;
|
|
63
54
|
export declare function validateWorkspaceLinkName(name: string): string;
|
|
64
55
|
export declare function isValidWorkspaceName(name: string): boolean;
|
|
65
56
|
export declare function isValidWorkspaceLinkName(name: string): boolean;
|
|
66
|
-
export declare function isWorkspaceRoot(candidateRoot: string): Promise<boolean>;
|
|
67
|
-
export declare function findWorkspaceRoot(startPath?: string): Promise<string | null>;
|
|
68
57
|
export declare function isWorkspaceAgentOpenerId(value: string): value is WorkspaceAgentOpenerId;
|
|
69
58
|
export declare function isWorkspaceSupportedOpenerValue(value: string): value is WorkspaceSupportedOpenerValue;
|
|
70
59
|
export declare function parseWorkspacePreferredOpenerValue(value: string): WorkspacePreferredOpener;
|
|
71
60
|
export declare function validateWorkspacePreferredOpener(opener: WorkspacePreferredOpener): WorkspacePreferredOpener;
|
|
72
|
-
export declare function
|
|
73
|
-
export declare function
|
|
74
|
-
export declare function
|
|
75
|
-
export declare function
|
|
76
|
-
export declare function
|
|
77
|
-
export declare function
|
|
78
|
-
export declare function listWorkspaceRegistryEntries(registry: WorkspaceRegistryState): WorkspaceRegistryEntry[];
|
|
79
|
-
export declare function readWorkspaceSharedState(workspaceRoot: string): Promise<WorkspaceSharedState>;
|
|
80
|
-
export declare function readWorkspaceLocalState(workspaceRoot: string): Promise<WorkspaceLocalState>;
|
|
81
|
-
export declare function readOptionalWorkspaceLocalState(workspaceRoot: string): Promise<WorkspaceLocalState | null>;
|
|
82
|
-
export declare function writeWorkspaceSharedState(workspaceRoot: string, state: WorkspaceSharedState): Promise<void>;
|
|
83
|
-
export declare function writeWorkspaceLocalState(workspaceRoot: string, state: WorkspaceLocalState): Promise<void>;
|
|
84
|
-
export declare function readWorkspaceRegistryState(options?: WorkspacePathOptions): Promise<WorkspaceRegistryState | null>;
|
|
85
|
-
export declare function writeWorkspaceRegistryState(state: WorkspaceRegistryState, options?: WorkspacePathOptions): Promise<void>;
|
|
86
|
-
export declare function workspaceChangesDirExists(workspaceRoot: string): Promise<boolean>;
|
|
61
|
+
export declare function createWorkspaceInitiativeContext(store: ContextStoreBinding, initiativeId: string): WorkspaceContextState;
|
|
62
|
+
export declare function getWorkspaceContextStoreId(context: WorkspaceContextState): string;
|
|
63
|
+
export declare function getWorkspaceContextStoreSelector(context: WorkspaceContextState): ContextStoreSelector;
|
|
64
|
+
export declare function getWorkspaceContextInitiativeId(context: WorkspaceContextState): string;
|
|
65
|
+
export declare function parseWorkspaceViewState(content: string): WorkspaceViewState;
|
|
66
|
+
export declare function serializeWorkspaceViewState(state: WorkspaceViewState): string;
|
|
87
67
|
//# sourceMappingURL=foundation.d.ts.map
|