@fission-ai/openspec 1.4.1 → 1.5.0

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 (206) hide show
  1. package/README.md +24 -3
  2. package/dist/cli/index.d.ts +5 -0
  3. package/dist/cli/index.js +95 -95
  4. package/dist/commands/change.d.ts +5 -1
  5. package/dist/commands/change.js +17 -15
  6. package/dist/commands/config.js +6 -65
  7. package/dist/commands/context.d.ts +3 -0
  8. package/dist/commands/context.js +155 -0
  9. package/dist/commands/doctor.d.ts +8 -0
  10. package/dist/commands/doctor.js +163 -0
  11. package/dist/commands/shared-gather.d.ts +14 -0
  12. package/dist/commands/shared-gather.js +31 -0
  13. package/dist/commands/shared-output.d.ts +18 -0
  14. package/dist/commands/shared-output.js +61 -0
  15. package/dist/commands/show.d.ts +11 -6
  16. package/dist/commands/show.js +70 -25
  17. package/dist/commands/spec.d.ts +5 -1
  18. package/dist/commands/spec.js +15 -4
  19. package/dist/commands/store.d.ts +3 -0
  20. package/dist/commands/store.js +547 -0
  21. package/dist/commands/validate.d.ts +2 -0
  22. package/dist/commands/validate.js +75 -39
  23. package/dist/commands/workflow/index.d.ts +0 -2
  24. package/dist/commands/workflow/index.js +0 -1
  25. package/dist/commands/workflow/instructions.d.ts +11 -1
  26. package/dist/commands/workflow/instructions.js +71 -22
  27. package/dist/commands/workflow/new-change.d.ts +6 -3
  28. package/dist/commands/workflow/new-change.js +42 -77
  29. package/dist/commands/workflow/shared.d.ts +15 -3
  30. package/dist/commands/workflow/shared.js +20 -3
  31. package/dist/commands/workflow/status.d.ts +2 -0
  32. package/dist/commands/workflow/status.js +19 -17
  33. package/dist/commands/workset-input.d.ts +19 -0
  34. package/dist/commands/workset-input.js +112 -0
  35. package/dist/commands/workset-prompts.d.ts +12 -0
  36. package/dist/commands/workset-prompts.js +143 -0
  37. package/dist/commands/workset.d.ts +25 -0
  38. package/dist/commands/workset.js +446 -0
  39. package/dist/core/archive.d.ts +17 -6
  40. package/dist/core/archive.js +209 -56
  41. package/dist/core/artifact-graph/index.d.ts +1 -1
  42. package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
  43. package/dist/core/artifact-graph/instruction-loader.js +8 -31
  44. package/dist/core/change-metadata/schema.d.ts +1 -0
  45. package/dist/core/change-metadata/schema.js +4 -2
  46. package/dist/core/change-status-policy.d.ts +5 -18
  47. package/dist/core/change-status-policy.js +2 -37
  48. package/dist/core/command-generation/adapters/bob.js +1 -14
  49. package/dist/core/command-generation/adapters/claude.js +1 -14
  50. package/dist/core/command-generation/adapters/cursor.js +1 -14
  51. package/dist/core/command-generation/adapters/pi.js +1 -14
  52. package/dist/core/command-generation/adapters/windsurf.js +1 -14
  53. package/dist/core/command-generation/yaml.d.ts +22 -0
  54. package/dist/core/command-generation/yaml.js +38 -0
  55. package/dist/core/completions/command-registry.js +87 -289
  56. package/dist/core/completions/generators/zsh-generator.js +3 -1
  57. package/dist/core/completions/installers/bash-installer.js +3 -0
  58. package/dist/core/completions/installers/fish-installer.js +8 -0
  59. package/dist/core/completions/installers/powershell-installer.js +30 -2
  60. package/dist/core/completions/installers/zsh-installer.js +3 -0
  61. package/dist/core/completions/shared-flags.d.ts +1 -0
  62. package/dist/core/completions/shared-flags.js +5 -0
  63. package/dist/core/config-schema.d.ts +2 -1
  64. package/dist/core/config-schema.js +26 -0
  65. package/dist/core/file-state.d.ts +36 -0
  66. package/dist/core/file-state.js +112 -0
  67. package/dist/core/global-config.d.ts +2 -0
  68. package/dist/core/id.d.ts +17 -0
  69. package/dist/core/id.js +30 -0
  70. package/dist/core/index.d.ts +3 -3
  71. package/dist/core/index.js +3 -3
  72. package/dist/core/init.js +24 -4
  73. package/dist/core/list.d.ts +2 -0
  74. package/dist/core/list.js +19 -5
  75. package/dist/core/openers.d.ts +77 -0
  76. package/dist/core/openers.js +251 -0
  77. package/dist/core/openspec-root.d.ts +45 -0
  78. package/dist/core/openspec-root.js +192 -0
  79. package/dist/core/planning-home.d.ts +1 -6
  80. package/dist/core/planning-home.js +2 -43
  81. package/dist/core/project-config.d.ts +44 -1
  82. package/dist/core/project-config.js +160 -10
  83. package/dist/core/references.d.ts +63 -0
  84. package/dist/core/references.js +310 -0
  85. package/dist/core/relationship-health.d.ts +65 -0
  86. package/dist/core/relationship-health.js +64 -0
  87. package/dist/core/root-selection.d.ts +122 -0
  88. package/dist/core/root-selection.js +337 -0
  89. package/dist/core/specs-apply.d.ts +6 -1
  90. package/dist/core/specs-apply.js +6 -4
  91. package/dist/core/store/errors.d.ts +20 -0
  92. package/dist/core/{context-store → store}/errors.js +3 -3
  93. package/dist/core/store/foundation.d.ts +56 -0
  94. package/dist/core/store/foundation.js +251 -0
  95. package/dist/core/store/git.d.ts +23 -0
  96. package/dist/core/store/git.js +137 -0
  97. package/dist/core/{context-store → store}/index.d.ts +0 -1
  98. package/dist/core/{context-store → store}/index.js +0 -1
  99. package/dist/core/store/operations.d.ts +114 -0
  100. package/dist/core/store/operations.js +783 -0
  101. package/dist/core/store/registry.d.ts +58 -0
  102. package/dist/core/store/registry.js +275 -0
  103. package/dist/core/templates/workflows/apply-change.js +5 -4
  104. package/dist/core/templates/workflows/archive-change.js +5 -4
  105. package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
  106. package/dist/core/templates/workflows/continue-change.js +7 -2
  107. package/dist/core/templates/workflows/explore.js +5 -0
  108. package/dist/core/templates/workflows/ff-change.js +5 -0
  109. package/dist/core/templates/workflows/new-change.js +5 -0
  110. package/dist/core/templates/workflows/onboard.js +4 -1
  111. package/dist/core/templates/workflows/propose.js +5 -0
  112. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  113. package/dist/core/templates/workflows/store-selection.js +8 -0
  114. package/dist/core/templates/workflows/sync-specs.js +5 -4
  115. package/dist/core/templates/workflows/verify-change.js +5 -4
  116. package/dist/core/working-set.d.ts +47 -0
  117. package/dist/core/working-set.js +43 -0
  118. package/dist/core/worksets.d.ts +75 -0
  119. package/dist/core/worksets.js +245 -0
  120. package/dist/core/zod-issues.d.ts +4 -0
  121. package/dist/core/zod-issues.js +10 -0
  122. package/dist/utils/change-metadata.d.ts +1 -0
  123. package/dist/utils/change-metadata.js +1 -1
  124. package/dist/utils/change-utils.js +15 -0
  125. package/dist/utils/file-system.js +36 -17
  126. package/package.json +6 -6
  127. package/dist/commands/context-store.d.ts +0 -3
  128. package/dist/commands/context-store.js +0 -475
  129. package/dist/commands/initiative.d.ts +0 -13
  130. package/dist/commands/initiative.js +0 -318
  131. package/dist/commands/workflow/initiative-link.d.ts +0 -24
  132. package/dist/commands/workflow/initiative-link.js +0 -47
  133. package/dist/commands/workflow/set-change.d.ts +0 -13
  134. package/dist/commands/workflow/set-change.js +0 -87
  135. package/dist/commands/workspace/context-status.d.ts +0 -4
  136. package/dist/commands/workspace/context-status.js +0 -59
  137. package/dist/commands/workspace/open-target-selection.d.ts +0 -13
  138. package/dist/commands/workspace/open-target-selection.js +0 -146
  139. package/dist/commands/workspace/open-view.d.ts +0 -62
  140. package/dist/commands/workspace/open-view.js +0 -249
  141. package/dist/commands/workspace/open.d.ts +0 -37
  142. package/dist/commands/workspace/open.js +0 -110
  143. package/dist/commands/workspace/opener-selection.d.ts +0 -11
  144. package/dist/commands/workspace/opener-selection.js +0 -98
  145. package/dist/commands/workspace/operations.d.ts +0 -29
  146. package/dist/commands/workspace/operations.js +0 -543
  147. package/dist/commands/workspace/prompt-theme.d.ts +0 -29
  148. package/dist/commands/workspace/prompt-theme.js +0 -24
  149. package/dist/commands/workspace/registration.d.ts +0 -13
  150. package/dist/commands/workspace/registration.js +0 -84
  151. package/dist/commands/workspace/selection.d.ts +0 -8
  152. package/dist/commands/workspace/selection.js +0 -104
  153. package/dist/commands/workspace/setup-prompts.d.ts +0 -13
  154. package/dist/commands/workspace/setup-prompts.js +0 -121
  155. package/dist/commands/workspace/types.d.ts +0 -103
  156. package/dist/commands/workspace/types.js +0 -36
  157. package/dist/commands/workspace.d.ts +0 -5
  158. package/dist/commands/workspace.js +0 -574
  159. package/dist/core/collections/index.d.ts +0 -3
  160. package/dist/core/collections/index.js +0 -3
  161. package/dist/core/collections/initiatives/collection.d.ts +0 -4
  162. package/dist/core/collections/initiatives/collection.js +0 -17
  163. package/dist/core/collections/initiatives/index.d.ts +0 -6
  164. package/dist/core/collections/initiatives/index.js +0 -6
  165. package/dist/core/collections/initiatives/operations.d.ts +0 -49
  166. package/dist/core/collections/initiatives/operations.js +0 -175
  167. package/dist/core/collections/initiatives/resolution.d.ts +0 -87
  168. package/dist/core/collections/initiatives/resolution.js +0 -374
  169. package/dist/core/collections/initiatives/schema.d.ts +0 -41
  170. package/dist/core/collections/initiatives/schema.js +0 -134
  171. package/dist/core/collections/initiatives/templates.d.ts +0 -12
  172. package/dist/core/collections/initiatives/templates.js +0 -90
  173. package/dist/core/collections/runtime.d.ts +0 -46
  174. package/dist/core/collections/runtime.js +0 -194
  175. package/dist/core/context-store/binding.d.ts +0 -53
  176. package/dist/core/context-store/binding.js +0 -197
  177. package/dist/core/context-store/errors.d.ts +0 -20
  178. package/dist/core/context-store/foundation.d.ts +0 -55
  179. package/dist/core/context-store/foundation.js +0 -321
  180. package/dist/core/context-store/operations.d.ts +0 -85
  181. package/dist/core/context-store/operations.js +0 -528
  182. package/dist/core/context-store/registry.d.ts +0 -45
  183. package/dist/core/context-store/registry.js +0 -229
  184. package/dist/core/workspace/foundation.d.ts +0 -67
  185. package/dist/core/workspace/foundation.js +0 -295
  186. package/dist/core/workspace/index.d.ts +0 -8
  187. package/dist/core/workspace/index.js +0 -8
  188. package/dist/core/workspace/legacy-state.d.ts +0 -28
  189. package/dist/core/workspace/legacy-state.js +0 -200
  190. package/dist/core/workspace/link-input.d.ts +0 -9
  191. package/dist/core/workspace/link-input.js +0 -32
  192. package/dist/core/workspace/open-surface.d.ts +0 -45
  193. package/dist/core/workspace/open-surface.js +0 -215
  194. package/dist/core/workspace/openers.d.ts +0 -21
  195. package/dist/core/workspace/openers.js +0 -124
  196. package/dist/core/workspace/registry.d.ts +0 -24
  197. package/dist/core/workspace/registry.js +0 -146
  198. package/dist/core/workspace/skills.d.ts +0 -57
  199. package/dist/core/workspace/skills.js +0 -334
  200. package/dist/core/workspace/state-io.d.ts +0 -10
  201. package/dist/core/workspace/state-io.js +0 -121
  202. package/schemas/workspace-planning/schema.yaml +0 -72
  203. package/schemas/workspace-planning/templates/design.md +0 -33
  204. package/schemas/workspace-planning/templates/proposal.md +0 -28
  205. package/schemas/workspace-planning/templates/spec.md +0 -9
  206. package/schemas/workspace-planning/templates/tasks.md +0 -15
@@ -0,0 +1,58 @@
1
+ import { type StoreBackendConfig, type StoreGitBackendConfig, type StorePathOptions, type StoreRegistryEntry, type StoreRegistryState } from './foundation.js';
2
+ export interface RegisterStoreInput extends StorePathOptions {
3
+ id: string;
4
+ localPath: string;
5
+ remote?: string;
6
+ branch?: string;
7
+ cwd?: string;
8
+ }
9
+ export interface ResolveRegisteredStoreInput extends StorePathOptions {
10
+ id: string;
11
+ }
12
+ export interface GetRegisteredStoreInput extends ResolveRegisteredStoreInput {
13
+ expectedBackend?: StoreGitBackendConfig;
14
+ }
15
+ export interface UnregisterStoreInput extends StorePathOptions {
16
+ id: string;
17
+ expectedBackend?: StoreGitBackendConfig;
18
+ beforeCommit?: (entry: RegisteredStoreEntry) => Promise<void>;
19
+ }
20
+ export type ListRegisteredStoresOptions = StorePathOptions;
21
+ export interface RegisteredStoreEntry extends StoreRegistryEntry {
22
+ storeRoot: string;
23
+ }
24
+ export interface ResolvedStore {
25
+ id: string;
26
+ storeRoot: string;
27
+ backend: StoreGitBackendConfig;
28
+ }
29
+ export interface StoreRegistrationCommit extends ResolvedStore {
30
+ metadataCreated: boolean;
31
+ registryUpdated: boolean;
32
+ alreadyRegistered: boolean;
33
+ }
34
+ export interface CommitStoreRegistrationInput extends StorePathOptions {
35
+ id: string;
36
+ backend: StoreGitBackendConfig;
37
+ writeMetadataIfMissing: boolean;
38
+ }
39
+ export declare function getStoreRootForBackend(backend: StoreBackendConfig): string;
40
+ export declare function assertNoRegisteredStoreConflict(registry: StoreRegistryState | null, id: string, backend: StoreGitBackendConfig): void;
41
+ export declare function commitStoreRegistration(input: CommitStoreRegistrationInput): Promise<StoreRegistrationCommit>;
42
+ export declare function registerStore(input: RegisterStoreInput): Promise<ResolvedStore>;
43
+ export interface RegistrySnapshot {
44
+ /** null = the registry is unreadable; [] = empty or absent. */
45
+ entries: StoreRegistryEntry[] | null;
46
+ unreadable: boolean;
47
+ }
48
+ /**
49
+ * One registry read serving every consumer in a command.
50
+ */
51
+ export declare function readRegistrySnapshot(options?: {
52
+ globalDataDir?: string;
53
+ }): Promise<RegistrySnapshot>;
54
+ export declare function listRegisteredStores(options?: ListRegisteredStoresOptions): Promise<RegisteredStoreEntry[]>;
55
+ export declare function getRegisteredStore(input: GetRegisteredStoreInput): Promise<RegisteredStoreEntry>;
56
+ export declare function unregisterStoreRegistration(input: UnregisterStoreInput): Promise<RegisteredStoreEntry>;
57
+ export declare function resolveRegisteredStore(input: ResolveRegisteredStoreInput): Promise<ResolvedStore>;
58
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,275 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import { getStoreMetadataPath, getStoreMetadataDir, listStoreRegistryEntries, readStoreRegistryState, readOptionalStoreMetadataState, resolveGitStoreBackendConfig, updateStoreRegistryState, validateStoreId, writeStoreMetadataState, } from './foundation.js';
3
+ import { StoreError } from './errors.js';
4
+ import * as path from 'node:path';
5
+ import { FileSystemUtils } from '../../utils/file-system.js';
6
+ export function getStoreRootForBackend(backend) {
7
+ switch (backend.type) {
8
+ case 'git':
9
+ return backend.local_path;
10
+ }
11
+ }
12
+ function normalizePathForComparison(targetPath) {
13
+ try {
14
+ return FileSystemUtils.canonicalizeExistingPath(targetPath);
15
+ }
16
+ catch {
17
+ // Nonexistent (e.g. stale) paths still deserve a resolved compare;
18
+ // aligns with the operations.ts sibling fallback.
19
+ return path.resolve(targetPath);
20
+ }
21
+ }
22
+ export function assertNoRegisteredStoreConflict(registry, id, backend) {
23
+ const nextPath = normalizePathForComparison(getStoreRootForBackend(backend));
24
+ for (const entry of listStoreRegistryEntries(registry ?? { version: 1, stores: {} })) {
25
+ const entryPath = normalizePathForComparison(getStoreRootForBackend(entry.backend));
26
+ if (entry.id === id && entryPath === nextPath) {
27
+ continue;
28
+ }
29
+ if (entry.id === id) {
30
+ throw new StoreError(`Store '${id}' is already registered at ${getStoreRootForBackend(entry.backend)}. One checkout per store id is supported on this machine.`, 'store_id_conflict', {
31
+ target: 'store.id',
32
+ fix: `Use the existing registration, or run openspec store unregister ${id} first to switch this id to a different checkout.`,
33
+ });
34
+ }
35
+ if (entryPath === nextPath) {
36
+ throw new StoreError(`Store path is already registered as '${entry.id}'.`, 'store_path_conflict', {
37
+ target: 'store.root',
38
+ fix: `Use the existing '${entry.id}' registration or choose a different path.`,
39
+ });
40
+ }
41
+ }
42
+ }
43
+ function withRegisteredStore(registry, id, backend) {
44
+ assertNoRegisteredStoreConflict(registry, id, backend);
45
+ const stores = {
46
+ ...(registry?.stores ?? {}),
47
+ [id]: {
48
+ backend,
49
+ },
50
+ };
51
+ return {
52
+ version: 1,
53
+ stores: Object.fromEntries(Object.entries(stores).sort(([leftId], [rightId]) => leftId.localeCompare(rightId))),
54
+ };
55
+ }
56
+ function getRegisteredStoreOrThrow(registry, id) {
57
+ const entry = registry?.stores[id];
58
+ if (!entry) {
59
+ throw new StoreError(`Unknown store '${id}'`, 'store_not_found', {
60
+ target: 'store.id',
61
+ fix: 'Run openspec store list to see registered stores.',
62
+ });
63
+ }
64
+ return {
65
+ id,
66
+ backend: entry.backend,
67
+ };
68
+ }
69
+ /** Same checkout: type, canonical path, and branch — remote excluded. */
70
+ function sameCheckout(actual, expected) {
71
+ return (actual.type === expected.type &&
72
+ normalizePathForComparison(actual.local_path) ===
73
+ normalizePathForComparison(expected.local_path) &&
74
+ actual.branch === expected.branch);
75
+ }
76
+ function storeBackendsMatch(actual, expected) {
77
+ return sameCheckout(actual, expected) && actual.remote === expected.remote;
78
+ }
79
+ function assertExpectedRegisteredBackend(id, actual, expected) {
80
+ if (!expected || storeBackendsMatch(actual, expected))
81
+ return;
82
+ throw new StoreError(`Store '${id}' changed before cleanup completed.`, 'store_registry_changed', {
83
+ target: 'store.registry',
84
+ fix: 'Retry the cleanup command after reviewing the current store registration.',
85
+ });
86
+ }
87
+ function withoutRegisteredStore(registry, id, expectedBackend) {
88
+ const removed = getRegisteredStoreOrThrow(registry, id);
89
+ assertExpectedRegisteredBackend(id, removed.backend, expectedBackend);
90
+ const stores = { ...(registry?.stores ?? {}) };
91
+ delete stores[id];
92
+ return {
93
+ removed,
94
+ next: {
95
+ version: 1,
96
+ stores: Object.fromEntries(Object.entries(stores).sort(([leftId], [rightId]) => leftId.localeCompare(rightId))),
97
+ },
98
+ };
99
+ }
100
+ async function ensureStoreMetadata(storeRoot, id, options) {
101
+ const metadata = await readOptionalStoreMetadataState(storeRoot);
102
+ if (!metadata) {
103
+ if (!options.writeIfMissing) {
104
+ throw new StoreError(`Registered store '${id}' is missing metadata at ${getStoreMetadataPath(storeRoot)}`, 'store_metadata_missing', {
105
+ target: 'store.metadata',
106
+ fix: `Create ${getStoreMetadataPath(storeRoot)} or rerun "openspec store register <path>".`,
107
+ });
108
+ }
109
+ await writeStoreMetadataState(storeRoot, {
110
+ version: 1,
111
+ id,
112
+ });
113
+ return true;
114
+ }
115
+ if (metadata.id !== id) {
116
+ throw new StoreError(`Store metadata id '${metadata.id}' does not match registered id '${id}'`, 'store_metadata_id_mismatch', {
117
+ target: 'store.metadata',
118
+ fix: 'Repair the local registry or store metadata so the ids match.',
119
+ });
120
+ }
121
+ return false;
122
+ }
123
+ export async function commitStoreRegistration(input) {
124
+ const id = validateStoreId(input.id);
125
+ const backend = input.backend;
126
+ const storeRoot = getStoreRootForBackend(backend);
127
+ let metadataCreated = false;
128
+ let isRerun = false;
129
+ let registryUpdated = false;
130
+ try {
131
+ metadataCreated = await ensureStoreMetadata(storeRoot, id, {
132
+ writeIfMissing: input.writeMetadataIfMissing,
133
+ });
134
+ const registry = await readStoreRegistryState({
135
+ globalDataDir: input.globalDataDir,
136
+ });
137
+ const existing = registry?.stores[id];
138
+ const existingBackend = existing?.backend;
139
+ // Same checkout = a rerun for an already-registered store (the 1.3
140
+ // reporting contract), whether or not the observed remote changed;
141
+ // only a remote change needs the registry write (the refresh).
142
+ isRerun = existingBackend !== undefined && sameCheckout(existingBackend, backend);
143
+ const upToDate = isRerun && existingBackend !== undefined && storeBackendsMatch(existingBackend, backend);
144
+ if (!upToDate) {
145
+ await updateStoreRegistryState((registry) => withRegisteredStore(registry, id, backend), { globalDataDir: input.globalDataDir });
146
+ registryUpdated = true;
147
+ }
148
+ }
149
+ catch (error) {
150
+ if (metadataCreated) {
151
+ // A concurrent registration may have read our metadata as
152
+ // pre-existing and committed against it - never delete metadata a
153
+ // committed registry entry depends on.
154
+ const current = await readStoreRegistryState({
155
+ globalDataDir: input.globalDataDir,
156
+ }).catch(() => null);
157
+ if (!current?.stores[id]) {
158
+ await fs.rm(getStoreMetadataPath(storeRoot), { force: true });
159
+ await fs.rmdir(getStoreMetadataDir(storeRoot)).catch(() => undefined);
160
+ }
161
+ }
162
+ throw error;
163
+ }
164
+ return {
165
+ id,
166
+ storeRoot,
167
+ backend,
168
+ metadataCreated,
169
+ registryUpdated,
170
+ alreadyRegistered: isRerun,
171
+ };
172
+ }
173
+ export async function registerStore(input) {
174
+ const id = validateStoreId(input.id);
175
+ const backend = await resolveGitStoreBackendConfig({
176
+ localPath: input.localPath,
177
+ ...(input.remote !== undefined ? { remote: input.remote } : {}),
178
+ ...(input.branch !== undefined ? { branch: input.branch } : {}),
179
+ }, input.cwd);
180
+ const storeRoot = getStoreRootForBackend(backend);
181
+ const committed = await commitStoreRegistration({
182
+ id,
183
+ backend,
184
+ writeMetadataIfMissing: true,
185
+ ...(input.globalDataDir ? { globalDataDir: input.globalDataDir } : {}),
186
+ });
187
+ return {
188
+ id: committed.id,
189
+ storeRoot: committed.storeRoot,
190
+ backend: committed.backend,
191
+ };
192
+ }
193
+ /**
194
+ * One registry read serving every consumer in a command.
195
+ */
196
+ export async function readRegistrySnapshot(options = {}) {
197
+ try {
198
+ const registry = await readStoreRegistryState(options);
199
+ return {
200
+ entries: registry ? listStoreRegistryEntries(registry) : [],
201
+ unreadable: false,
202
+ };
203
+ }
204
+ catch {
205
+ return { entries: null, unreadable: true };
206
+ }
207
+ }
208
+ export async function listRegisteredStores(options = {}) {
209
+ const registry = await readStoreRegistryState(options);
210
+ if (!registry) {
211
+ return [];
212
+ }
213
+ return listStoreRegistryEntries(registry).map((entry) => ({
214
+ ...entry,
215
+ storeRoot: getStoreRootForBackend(entry.backend),
216
+ }));
217
+ }
218
+ export async function getRegisteredStore(input) {
219
+ const id = validateStoreId(input.id);
220
+ const registry = await readStoreRegistryState({
221
+ globalDataDir: input.globalDataDir,
222
+ });
223
+ const entry = getRegisteredStoreOrThrow(registry, id);
224
+ assertExpectedRegisteredBackend(id, entry.backend, input.expectedBackend);
225
+ return {
226
+ ...entry,
227
+ storeRoot: getStoreRootForBackend(entry.backend),
228
+ };
229
+ }
230
+ export async function unregisterStoreRegistration(input) {
231
+ const id = validateStoreId(input.id);
232
+ let removed;
233
+ await updateStoreRegistryState(async (registry) => {
234
+ const result = withoutRegisteredStore(registry, id, input.expectedBackend);
235
+ const removedEntry = {
236
+ ...result.removed,
237
+ storeRoot: getStoreRootForBackend(result.removed.backend),
238
+ };
239
+ await input.beforeCommit?.(removedEntry);
240
+ removed = result.removed;
241
+ return result.next;
242
+ }, { globalDataDir: input.globalDataDir });
243
+ if (!removed) {
244
+ throw new StoreError(`Unknown store '${id}'`, 'store_not_found', {
245
+ target: 'store.id',
246
+ fix: 'Run openspec store list to see registered stores.',
247
+ });
248
+ }
249
+ return {
250
+ ...removed,
251
+ storeRoot: getStoreRootForBackend(removed.backend),
252
+ };
253
+ }
254
+ export async function resolveRegisteredStore(input) {
255
+ const id = validateStoreId(input.id);
256
+ const registry = await readStoreRegistryState({
257
+ globalDataDir: input.globalDataDir,
258
+ });
259
+ if (!registry) {
260
+ throw new StoreError('No store registry found', 'no_store_registry', {
261
+ target: 'store.id',
262
+ fix: 'Register a store with openspec store register <path>, then select it with --store <id>.',
263
+ });
264
+ }
265
+ const entry = getRegisteredStoreOrThrow(registry, id);
266
+ const backend = entry.backend;
267
+ const storeRoot = getStoreRootForBackend(backend);
268
+ await ensureStoreMetadata(storeRoot, id, { writeIfMissing: false });
269
+ return {
270
+ id,
271
+ storeRoot,
272
+ backend,
273
+ };
274
+ }
275
+ //# sourceMappingURL=registry.js.map
@@ -1,9 +1,12 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getApplyChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-apply-change',
4
5
  description: 'Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.',
5
6
  instructions: `Implement tasks from an OpenSpec change.
6
7
 
8
+ ${STORE_SELECTION_GUIDANCE}
9
+
7
10
  **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
8
11
 
9
12
  **Steps**
@@ -43,8 +46,6 @@ export function getApplyChangeSkillTemplate() {
43
46
  - If \`state: "all_done"\`: congratulate, suggest archive
44
47
  - Otherwise: proceed to implementation
45
48
 
46
- **Workspace guard:** If status JSON reports \`actionContext.mode: "workspace-planning"\` and \`allowedEditRoots\` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files.
47
-
48
49
  4. **Read context files**
49
50
 
50
51
  Read every file path listed under \`contextFiles\` from the apply instructions output.
@@ -163,6 +164,8 @@ export function getOpsxApplyCommandTemplate() {
163
164
  tags: ['workflow', 'artifacts', 'experimental'],
164
165
  content: `Implement tasks from an OpenSpec change.
165
166
 
167
+ ${STORE_SELECTION_GUIDANCE}
168
+
166
169
  **Input**: Optionally specify a change name (e.g., \`/opsx:apply add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
167
170
 
168
171
  **Steps**
@@ -202,8 +205,6 @@ export function getOpsxApplyCommandTemplate() {
202
205
  - If \`state: "all_done"\`: congratulate, suggest archive
203
206
  - Otherwise: proceed to implementation
204
207
 
205
- **Workspace guard:** If status JSON reports \`actionContext.mode: "workspace-planning"\` and \`allowedEditRoots\` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files.
206
-
207
208
  4. **Read context files**
208
209
 
209
210
  Read every file path listed under \`contextFiles\` from the apply instructions output.
@@ -1,9 +1,12 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getArchiveChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-archive-change',
4
5
  description: 'Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.',
5
6
  instructions: `Archive a completed change in the experimental workflow.
6
7
 
8
+ ${STORE_SELECTION_GUIDANCE}
9
+
7
10
  **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
8
11
 
9
12
  **Steps**
@@ -26,8 +29,6 @@ export function getArchiveChangeSkillTemplate() {
26
29
  - \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`: path and scope context
27
30
  - \`artifacts\`: List of artifacts with their status (\`done\` or other)
28
31
 
29
- If status reports \`actionContext.mode: "workspace-planning"\`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos.
30
-
31
32
  **If any artifacts are not \`done\`:**
32
33
  - Display warning listing incomplete artifacts
33
34
  - Use **AskUserQuestion tool** to confirm user wants to proceed
@@ -121,6 +122,8 @@ export function getOpsxArchiveCommandTemplate() {
121
122
  tags: ['workflow', 'archive', 'experimental'],
122
123
  content: `Archive a completed change in the experimental workflow.
123
124
 
125
+ ${STORE_SELECTION_GUIDANCE}
126
+
124
127
  **Input**: Optionally specify a change name after \`/opsx:archive\` (e.g., \`/opsx:archive add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
125
128
 
126
129
  **Steps**
@@ -143,8 +146,6 @@ export function getOpsxArchiveCommandTemplate() {
143
146
  - \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`: path and scope context
144
147
  - \`artifacts\`: List of artifacts with their status (\`done\` or other)
145
148
 
146
- If status reports \`actionContext.mode: "workspace-planning"\`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos.
147
-
148
149
  **If any artifacts are not \`done\`:**
149
150
  - Display warning listing incomplete artifacts
150
151
  - Prompt user for confirmation to continue
@@ -1,3 +1,4 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getBulkArchiveChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-bulk-archive-change',
@@ -6,6 +7,8 @@ export function getBulkArchiveChangeSkillTemplate() {
6
7
 
7
8
  This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
8
9
 
10
+ ${STORE_SELECTION_GUIDANCE}
11
+
9
12
  **Input**: None required (prompts for selection)
10
13
 
11
14
  **Steps**
@@ -33,8 +36,6 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig
33
36
  - Parse \`schemaName\`, \`artifacts\`, \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`
34
37
  - Note which artifacts are \`done\` vs other states
35
38
 
36
- If any selected change reports \`actionContext.mode: "workspace-planning"\`, explain that workspace bulk archive is not supported in this slice and STOP before syncing specs or moving changes. Do not fall back to repo-local paths or edit linked repos.
37
-
38
39
  b. **Task completion** - Read \`artifactPaths.tasks.existingOutputPaths\` from status JSON
39
40
  - Count \`- [ ]\` (incomplete) vs \`- [x]\` (complete)
40
41
  - If no tasks file exists, note as "No tasks"
@@ -254,6 +255,8 @@ export function getOpsxBulkArchiveCommandTemplate() {
254
255
 
255
256
  This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
256
257
 
258
+ ${STORE_SELECTION_GUIDANCE}
259
+
257
260
  **Input**: None required (prompts for selection)
258
261
 
259
262
  **Steps**
@@ -281,8 +284,6 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig
281
284
  - Parse \`schemaName\`, \`artifacts\`, \`planningHome\`, \`changeRoot\`, \`artifactPaths\`, and \`actionContext\`
282
285
  - Note which artifacts are \`done\` vs other states
283
286
 
284
- If any selected change reports \`actionContext.mode: "workspace-planning"\`, explain that workspace bulk archive is not supported in this slice and STOP before syncing specs or moving changes. Do not fall back to repo-local paths or edit linked repos.
285
-
286
287
  b. **Task completion** - Read \`artifactPaths.tasks.existingOutputPaths\` from status JSON
287
288
  - Count \`- [ ]\` (incomplete) vs \`- [x]\` (complete)
288
289
  - If no tasks file exists, note as "No tasks"
@@ -1,9 +1,12 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getContinueChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-continue-change',
4
5
  description: 'Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow.',
5
6
  instructions: `Continue working on a change by creating the next artifact.
6
7
 
8
+ ${STORE_SELECTION_GUIDANCE}
9
+
7
10
  **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
8
11
 
9
12
  **Steps**
@@ -61,7 +64,7 @@ export function getContinueChangeSkillTemplate() {
61
64
  - Read any completed dependency files for context
62
65
  - Use \`template\` as the structure - fill in its sections
63
66
  - Apply \`context\` and \`rules\` as constraints when writing - but do NOT copy them into the file
64
- - Write to the \`resolvedOutputPath\` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and workspace planning context
67
+ - Write to the \`resolvedOutputPath\` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and the change's context
65
68
  - Show what was created and what's now unlocked
66
69
  - STOP after creating ONE artifact
67
70
 
@@ -123,6 +126,8 @@ export function getOpsxContinueCommandTemplate() {
123
126
  tags: ['workflow', 'artifacts', 'experimental'],
124
127
  content: `Continue working on a change by creating the next artifact.
125
128
 
129
+ ${STORE_SELECTION_GUIDANCE}
130
+
126
131
  **Input**: Optionally specify a change name after \`/opsx:continue\` (e.g., \`/opsx:continue add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
127
132
 
128
133
  **Steps**
@@ -180,7 +185,7 @@ export function getOpsxContinueCommandTemplate() {
180
185
  - Read any completed dependency files for context
181
186
  - Use \`template\` as the structure - fill in its sections
182
187
  - Apply \`context\` and \`rules\` as constraints when writing - but do NOT copy them into the file
183
- - Write to the \`resolvedOutputPath\` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and workspace planning context
188
+ - Write to the \`resolvedOutputPath\` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and the change's context
184
189
  - Show what was created and what's now unlocked
185
190
  - STOP after creating ONE artifact
186
191
 
@@ -1,3 +1,4 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getExploreSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-explore',
@@ -8,6 +9,8 @@ export function getExploreSkillTemplate() {
8
9
 
9
10
  **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
10
11
 
12
+ ${STORE_SELECTION_GUIDANCE}
13
+
11
14
  ---
12
15
 
13
16
  ## The Stance
@@ -295,6 +298,8 @@ export function getOpsxExploreCommandTemplate() {
295
298
 
296
299
  **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
297
300
 
301
+ ${STORE_SELECTION_GUIDANCE}
302
+
298
303
  **Input**: The argument after \`/opsx:explore\` is whatever the user wants to think about. Could be:
299
304
  - A vague idea: "real-time collaboration"
300
305
  - A specific problem: "the auth system is getting unwieldy"
@@ -1,9 +1,12 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getFfChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-ff-change',
4
5
  description: 'Fast-forward through OpenSpec artifact creation. Use when the user wants to quickly create all artifacts needed for implementation without stepping through each one individually.',
5
6
  instructions: `Fast-forward through artifact creation - generate everything needed to start implementation in one go.
6
7
 
8
+ ${STORE_SELECTION_GUIDANCE}
9
+
7
10
  **Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
8
11
 
9
12
  **Steps**
@@ -106,6 +109,8 @@ export function getOpsxFfCommandTemplate() {
106
109
  tags: ['workflow', 'artifacts', 'experimental'],
107
110
  content: `Fast-forward through artifact creation - generate everything needed to start implementation.
108
111
 
112
+ ${STORE_SELECTION_GUIDANCE}
113
+
109
114
  **Input**: The argument after \`/opsx:ff\` is the change name (kebab-case), OR a description of what the user wants to build.
110
115
 
111
116
  **Steps**
@@ -1,9 +1,12 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getNewChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-new-change',
4
5
  description: 'Start a new OpenSpec change using the experimental artifact workflow. Use when the user wants to create a new feature, fix, or modification with a structured step-by-step approach.',
5
6
  instructions: `Start a new change using the experimental artifact-driven approach.
6
7
 
8
+ ${STORE_SELECTION_GUIDANCE}
9
+
7
10
  **Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
8
11
 
9
12
  **Steps**
@@ -78,6 +81,8 @@ export function getOpsxNewCommandTemplate() {
78
81
  tags: ['workflow', 'artifacts', 'experimental'],
79
82
  content: `Start a new change using the experimental artifact-driven approach.
80
83
 
84
+ ${STORE_SELECTION_GUIDANCE}
85
+
81
86
  **Input**: The argument after \`/opsx:new\` is the change name (kebab-case), OR a description of what the user wants to build.
82
87
 
83
88
  **Steps**
@@ -1,3 +1,4 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getOnboardSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-onboard',
@@ -11,6 +12,8 @@ export function getOnboardSkillTemplate() {
11
12
  function getOnboardInstructions() {
12
13
  return `Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
13
14
 
15
+ ${STORE_SELECTION_GUIDANCE}
16
+
14
17
  ---
15
18
 
16
19
  ## Preflight
@@ -269,7 +272,7 @@ For a small task like this, we might only need one spec file.
269
272
  **DO:** Resolve where the spec file should be created:
270
273
  \`\`\`bash
271
274
  openspec instructions specs --change "<name>" --json
272
- # Use resolvedOutputPath from the JSON. If it is a glob, choose the concrete file path using the schema instruction and workspace planning context.
275
+ # Use resolvedOutputPath from the JSON. If it is a glob, choose the concrete file path using the schema instruction and the change's context.
273
276
  \`\`\`
274
277
 
275
278
  Draft the spec content:
@@ -1,3 +1,4 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getOpsxProposeSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-propose',
@@ -13,6 +14,8 @@ When ready to implement, run /opsx:apply
13
14
 
14
15
  ---
15
16
 
17
+ ${STORE_SELECTION_GUIDANCE}
18
+
16
19
  **Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
17
20
 
18
21
  **Steps**
@@ -124,6 +127,8 @@ When ready to implement, run /opsx:apply
124
127
 
125
128
  ---
126
129
 
130
+ ${STORE_SELECTION_GUIDANCE}
131
+
127
132
  **Input**: The argument after \`/opsx:propose\` is the change name (kebab-case), OR a description of what the user wants to build.
128
133
 
129
134
  **Steps**
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared store-selection guidance for skill template workflows.
3
+ *
4
+ * Interpolated into every workflow's instructions so generated skills
5
+ * consistently teach how to target a registered store with `--store <id>`.
6
+ */
7
+ export declare const STORE_SELECTION_GUIDANCE = "**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.";
8
+ //# sourceMappingURL=store-selection.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared store-selection guidance for skill template workflows.
3
+ *
4
+ * Interpolated into every workflow's instructions so generated skills
5
+ * consistently teach how to target a registered store with `--store <id>`.
6
+ */
7
+ export const STORE_SELECTION_GUIDANCE = `**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run \`openspec store list --json\` to discover registered store ids, then pass \`--store <id>\` on the commands that read or write specs and changes (\`new change\`, \`status\`, \`instructions\`, \`list\`, \`show\`, \`validate\`, \`archive\`, \`doctor\`, \`context\`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local \`openspec/\` root.`;
8
+ //# sourceMappingURL=store-selection.js.map
@@ -1,3 +1,4 @@
1
+ import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
1
2
  export function getSyncSpecsSkillTemplate() {
2
3
  return {
3
4
  name: 'openspec-sync-specs',
@@ -6,6 +7,8 @@ export function getSyncSpecsSkillTemplate() {
6
7
 
7
8
  This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
8
9
 
10
+ ${STORE_SELECTION_GUIDANCE}
11
+
9
12
  **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
10
13
 
11
14
  **Steps**
@@ -25,8 +28,6 @@ This is an **agent-driven** operation - you will read delta specs and directly e
25
28
  openspec status --change "<name>" --json
26
29
  \`\`\`
27
30
 
28
- If status reports \`actionContext.mode: "workspace-planning"\`, explain that workspace spec sync is not supported in this slice and STOP. Do not fall back to repo-local paths or edit linked repos.
29
-
30
31
  3. **Find delta specs**
31
32
 
32
33
  Use \`artifactPaths.specs.existingOutputPaths\` from the status JSON as the list of delta spec files.
@@ -153,6 +154,8 @@ export function getOpsxSyncCommandTemplate() {
153
154
 
154
155
  This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
155
156
 
157
+ ${STORE_SELECTION_GUIDANCE}
158
+
156
159
  **Input**: Optionally specify a change name after \`/opsx:sync\` (e.g., \`/opsx:sync add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
157
160
 
158
161
  **Steps**
@@ -172,8 +175,6 @@ This is an **agent-driven** operation - you will read delta specs and directly e
172
175
  openspec status --change "<name>" --json
173
176
  \`\`\`
174
177
 
175
- If status reports \`actionContext.mode: "workspace-planning"\`, explain that workspace spec sync is not supported in this slice and STOP. Do not fall back to repo-local paths or edit linked repos.
176
-
177
178
  3. **Find delta specs**
178
179
 
179
180
  Use \`artifactPaths.specs.existingOutputPaths\` from the status JSON as the list of delta spec files.