@fission-ai/openspec 1.4.1 → 1.6.0-beta.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.
Files changed (241) hide show
  1. package/README.md +37 -4
  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 -2
  5. package/dist/commands/change.js +23 -55
  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 +10 -0
  22. package/dist/commands/validate.js +88 -40
  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 +220 -68
  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.d.ts +1 -1
  50. package/dist/core/command-generation/adapters/claude.js +4 -15
  51. package/dist/core/command-generation/adapters/cursor.js +1 -14
  52. package/dist/core/command-generation/adapters/index.d.ts +2 -0
  53. package/dist/core/command-generation/adapters/index.js +2 -0
  54. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  55. package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
  56. package/dist/core/command-generation/adapters/pi.js +1 -14
  57. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  58. package/dist/core/command-generation/adapters/trae.js +48 -0
  59. package/dist/core/command-generation/adapters/windsurf.js +1 -14
  60. package/dist/core/command-generation/registry.js +4 -0
  61. package/dist/core/command-generation/yaml.d.ts +22 -0
  62. package/dist/core/command-generation/yaml.js +38 -0
  63. package/dist/core/completions/command-registry.js +87 -289
  64. package/dist/core/completions/generators/zsh-generator.js +3 -1
  65. package/dist/core/completions/installers/bash-installer.js +3 -0
  66. package/dist/core/completions/installers/fish-installer.js +8 -0
  67. package/dist/core/completions/installers/powershell-installer.js +30 -2
  68. package/dist/core/completions/installers/zsh-installer.js +3 -0
  69. package/dist/core/completions/shared-flags.d.ts +1 -0
  70. package/dist/core/completions/shared-flags.js +5 -0
  71. package/dist/core/config-schema.d.ts +2 -1
  72. package/dist/core/config-schema.js +26 -0
  73. package/dist/core/config.js +1 -0
  74. package/dist/core/file-state.d.ts +36 -0
  75. package/dist/core/file-state.js +112 -0
  76. package/dist/core/global-config.d.ts +2 -0
  77. package/dist/core/id.d.ts +17 -0
  78. package/dist/core/id.js +30 -0
  79. package/dist/core/index.d.ts +3 -3
  80. package/dist/core/index.js +3 -3
  81. package/dist/core/init.js +27 -6
  82. package/dist/core/list.d.ts +2 -0
  83. package/dist/core/list.js +37 -14
  84. package/dist/core/openers.d.ts +77 -0
  85. package/dist/core/openers.js +251 -0
  86. package/dist/core/openspec-root.d.ts +45 -0
  87. package/dist/core/openspec-root.js +195 -0
  88. package/dist/core/parsers/change-parser.js +2 -1
  89. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  90. package/dist/core/parsers/markdown-parser.js +5 -59
  91. package/dist/core/parsers/requirement-blocks.d.ts +11 -0
  92. package/dist/core/parsers/requirement-blocks.js +42 -8
  93. package/dist/core/parsers/requirement-text.d.ts +55 -0
  94. package/dist/core/parsers/requirement-text.js +136 -0
  95. package/dist/core/planning-home.d.ts +1 -6
  96. package/dist/core/planning-home.js +2 -43
  97. package/dist/core/profile-sync-drift.js +1 -0
  98. package/dist/core/profiles.d.ts +2 -2
  99. package/dist/core/profiles.js +2 -1
  100. package/dist/core/project-config.d.ts +44 -1
  101. package/dist/core/project-config.js +160 -10
  102. package/dist/core/references.d.ts +63 -0
  103. package/dist/core/references.js +310 -0
  104. package/dist/core/relationship-health.d.ts +65 -0
  105. package/dist/core/relationship-health.js +64 -0
  106. package/dist/core/root-selection.d.ts +122 -0
  107. package/dist/core/root-selection.js +337 -0
  108. package/dist/core/schemas/base.schema.js +7 -2
  109. package/dist/core/shared/allowed-tools.d.ts +12 -0
  110. package/dist/core/shared/allowed-tools.js +12 -0
  111. package/dist/core/shared/skill-generation.js +5 -1
  112. package/dist/core/shared/tool-detection.d.ts +2 -2
  113. package/dist/core/shared/tool-detection.js +2 -0
  114. package/dist/core/specs-apply.d.ts +6 -1
  115. package/dist/core/specs-apply.js +41 -5
  116. package/dist/core/store/errors.d.ts +20 -0
  117. package/dist/core/{context-store → store}/errors.js +3 -3
  118. package/dist/core/store/foundation.d.ts +56 -0
  119. package/dist/core/store/foundation.js +251 -0
  120. package/dist/core/store/git.d.ts +23 -0
  121. package/dist/core/store/git.js +137 -0
  122. package/dist/core/{context-store → store}/index.d.ts +0 -1
  123. package/dist/core/{context-store → store}/index.js +0 -1
  124. package/dist/core/store/operations.d.ts +114 -0
  125. package/dist/core/store/operations.js +803 -0
  126. package/dist/core/store/registry.d.ts +58 -0
  127. package/dist/core/store/registry.js +275 -0
  128. package/dist/core/templates/skill-templates.d.ts +1 -0
  129. package/dist/core/templates/skill-templates.js +1 -0
  130. package/dist/core/templates/workflows/apply-change.js +5 -4
  131. package/dist/core/templates/workflows/archive-change.js +5 -4
  132. package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
  133. package/dist/core/templates/workflows/continue-change.js +7 -2
  134. package/dist/core/templates/workflows/explore.js +5 -0
  135. package/dist/core/templates/workflows/ff-change.js +5 -0
  136. package/dist/core/templates/workflows/new-change.js +5 -0
  137. package/dist/core/templates/workflows/onboard.js +4 -1
  138. package/dist/core/templates/workflows/propose.js +5 -0
  139. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  140. package/dist/core/templates/workflows/store-selection.js +8 -0
  141. package/dist/core/templates/workflows/sync-specs.js +5 -4
  142. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  143. package/dist/core/templates/workflows/update-change.js +167 -0
  144. package/dist/core/templates/workflows/verify-change.js +5 -4
  145. package/dist/core/update.js +4 -4
  146. package/dist/core/validation/validator.d.ts +7 -0
  147. package/dist/core/validation/validator.js +101 -38
  148. package/dist/core/view.js +1 -1
  149. package/dist/core/working-set.d.ts +47 -0
  150. package/dist/core/working-set.js +43 -0
  151. package/dist/core/worksets.d.ts +75 -0
  152. package/dist/core/worksets.js +245 -0
  153. package/dist/core/zod-issues.d.ts +4 -0
  154. package/dist/core/zod-issues.js +10 -0
  155. package/dist/utils/change-metadata.d.ts +1 -0
  156. package/dist/utils/change-metadata.js +1 -1
  157. package/dist/utils/change-utils.js +15 -0
  158. package/dist/utils/file-system.js +36 -17
  159. package/dist/utils/task-progress.d.ts +10 -1
  160. package/dist/utils/task-progress.js +66 -2
  161. package/package.json +26 -23
  162. package/dist/commands/context-store.d.ts +0 -3
  163. package/dist/commands/context-store.js +0 -475
  164. package/dist/commands/initiative.d.ts +0 -13
  165. package/dist/commands/initiative.js +0 -318
  166. package/dist/commands/workflow/initiative-link.d.ts +0 -24
  167. package/dist/commands/workflow/initiative-link.js +0 -47
  168. package/dist/commands/workflow/set-change.d.ts +0 -13
  169. package/dist/commands/workflow/set-change.js +0 -87
  170. package/dist/commands/workspace/context-status.d.ts +0 -4
  171. package/dist/commands/workspace/context-status.js +0 -59
  172. package/dist/commands/workspace/open-target-selection.d.ts +0 -13
  173. package/dist/commands/workspace/open-target-selection.js +0 -146
  174. package/dist/commands/workspace/open-view.d.ts +0 -62
  175. package/dist/commands/workspace/open-view.js +0 -249
  176. package/dist/commands/workspace/open.d.ts +0 -37
  177. package/dist/commands/workspace/open.js +0 -110
  178. package/dist/commands/workspace/opener-selection.d.ts +0 -11
  179. package/dist/commands/workspace/opener-selection.js +0 -98
  180. package/dist/commands/workspace/operations.d.ts +0 -29
  181. package/dist/commands/workspace/operations.js +0 -543
  182. package/dist/commands/workspace/prompt-theme.d.ts +0 -29
  183. package/dist/commands/workspace/prompt-theme.js +0 -24
  184. package/dist/commands/workspace/registration.d.ts +0 -13
  185. package/dist/commands/workspace/registration.js +0 -84
  186. package/dist/commands/workspace/selection.d.ts +0 -8
  187. package/dist/commands/workspace/selection.js +0 -104
  188. package/dist/commands/workspace/setup-prompts.d.ts +0 -13
  189. package/dist/commands/workspace/setup-prompts.js +0 -121
  190. package/dist/commands/workspace/types.d.ts +0 -103
  191. package/dist/commands/workspace/types.js +0 -36
  192. package/dist/commands/workspace.d.ts +0 -5
  193. package/dist/commands/workspace.js +0 -574
  194. package/dist/core/collections/index.d.ts +0 -3
  195. package/dist/core/collections/index.js +0 -3
  196. package/dist/core/collections/initiatives/collection.d.ts +0 -4
  197. package/dist/core/collections/initiatives/collection.js +0 -17
  198. package/dist/core/collections/initiatives/index.d.ts +0 -6
  199. package/dist/core/collections/initiatives/index.js +0 -6
  200. package/dist/core/collections/initiatives/operations.d.ts +0 -49
  201. package/dist/core/collections/initiatives/operations.js +0 -175
  202. package/dist/core/collections/initiatives/resolution.d.ts +0 -87
  203. package/dist/core/collections/initiatives/resolution.js +0 -374
  204. package/dist/core/collections/initiatives/schema.d.ts +0 -41
  205. package/dist/core/collections/initiatives/schema.js +0 -134
  206. package/dist/core/collections/initiatives/templates.d.ts +0 -12
  207. package/dist/core/collections/initiatives/templates.js +0 -90
  208. package/dist/core/collections/runtime.d.ts +0 -46
  209. package/dist/core/collections/runtime.js +0 -194
  210. package/dist/core/context-store/binding.d.ts +0 -53
  211. package/dist/core/context-store/binding.js +0 -197
  212. package/dist/core/context-store/errors.d.ts +0 -20
  213. package/dist/core/context-store/foundation.d.ts +0 -55
  214. package/dist/core/context-store/foundation.js +0 -321
  215. package/dist/core/context-store/operations.d.ts +0 -85
  216. package/dist/core/context-store/operations.js +0 -528
  217. package/dist/core/context-store/registry.d.ts +0 -45
  218. package/dist/core/context-store/registry.js +0 -229
  219. package/dist/core/workspace/foundation.d.ts +0 -67
  220. package/dist/core/workspace/foundation.js +0 -295
  221. package/dist/core/workspace/index.d.ts +0 -8
  222. package/dist/core/workspace/index.js +0 -8
  223. package/dist/core/workspace/legacy-state.d.ts +0 -28
  224. package/dist/core/workspace/legacy-state.js +0 -200
  225. package/dist/core/workspace/link-input.d.ts +0 -9
  226. package/dist/core/workspace/link-input.js +0 -32
  227. package/dist/core/workspace/open-surface.d.ts +0 -45
  228. package/dist/core/workspace/open-surface.js +0 -215
  229. package/dist/core/workspace/openers.d.ts +0 -21
  230. package/dist/core/workspace/openers.js +0 -124
  231. package/dist/core/workspace/registry.d.ts +0 -24
  232. package/dist/core/workspace/registry.js +0 -146
  233. package/dist/core/workspace/skills.d.ts +0 -57
  234. package/dist/core/workspace/skills.js +0 -334
  235. package/dist/core/workspace/state-io.d.ts +0 -10
  236. package/dist/core/workspace/state-io.js +0 -121
  237. package/schemas/workspace-planning/schema.yaml +0 -72
  238. package/schemas/workspace-planning/templates/design.md +0 -33
  239. package/schemas/workspace-planning/templates/proposal.md +0 -28
  240. package/schemas/workspace-planning/templates/spec.md +0 -9
  241. package/schemas/workspace-planning/templates/tasks.md +0 -15
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Relationship health composition (slice 3.6).
3
+ *
4
+ * One read-only answer to "are the roots this work relates to available
5
+ * on this machine?" — pure composition over inputs the doctor command
6
+ * gathers. The lock's four categories stay separated: root health,
7
+ * store metadata health, and reference health. Nothing here (or
8
+ * downstream) clones, syncs, or repairs.
9
+ */
10
+ import { makeStoreDiagnostic } from './store/errors.js';
11
+ import { sanitizeInline } from './references.js';
12
+ import { storePointerProblem } from './project-config.js';
13
+ import { toRootOutput } from './root-selection.js';
14
+ function warning(code, message, fix) {
15
+ return makeStoreDiagnostic('warning', code, message, { target: 'relationships', fix });
16
+ }
17
+ export function inspectRelationships(input) {
18
+ const status = [];
19
+ if (input.registryUnreadable) {
20
+ status.push(warning('relationship_registry_unreadable', 'The store registry is unreadable; reference health cannot be checked.', 'Run: openspec store doctor'));
21
+ }
22
+ if (input.bothShapesPointer) {
23
+ status.push(warning('root_pointer_ignored', `${input.bothShapesPointer.filePath} declares store '${input.bothShapesPointer.value}', but this directory is a real OpenSpec root; the declaration is ignored.`, `Remove the store: line from ${input.bothShapesPointer.filePath}, or move the planning files into the store.`));
24
+ }
25
+ if (input.malformedPointer) {
26
+ status.push(warning('root_pointer_invalid', `${input.malformedPointer.filePath} declares a store: pointer that cannot be used (${storePointerProblem(input.malformedPointer.reason)}).`, `Fix or remove the store: line in ${input.malformedPointer.filePath}.`));
27
+ }
28
+ if (input.inertPointerDeclarations && input.inertPointerDeclarations.fields.length > 0) {
29
+ status.push(warning('pointer_declarations_inert', `${input.inertPointerDeclarations.filePath} declares ${input.inertPointerDeclarations.fields.join(' and ')}, but commands read the resolved store's config — these declarations are inert.`, `Move the ${input.inertPointerDeclarations.fields.join('/')} declarations into the store's openspec/config.yaml.`));
30
+ }
31
+ // Store section: metadata facts + the divergence info note.
32
+ let store = null;
33
+ if (input.storeFacts) {
34
+ const storeStatus = [];
35
+ if (input.storeFacts.canonicalRemote &&
36
+ input.storeFacts.originUrl &&
37
+ input.storeFacts.canonicalRemote !== input.storeFacts.originUrl) {
38
+ storeStatus.push(makeStoreDiagnostic('info', 'store_remote_divergence', `The store.yaml remote (${sanitizeInline(input.storeFacts.canonicalRemote, 200)}) differs from the checkout's origin (${sanitizeInline(input.storeFacts.originUrl, 200)}).`, { target: 'store.metadata' }));
39
+ }
40
+ store = {
41
+ id: input.storeFacts.id,
42
+ metadata: {
43
+ present: input.storeFacts.metadataPresent,
44
+ valid: input.storeFacts.metadataValid,
45
+ ...(input.storeFacts.canonicalRemote
46
+ ? { remote: input.storeFacts.canonicalRemote }
47
+ : {}),
48
+ },
49
+ ...(input.storeFacts.originUrl ? { origin_url: input.storeFacts.originUrl } : {}),
50
+ status: storeStatus,
51
+ };
52
+ }
53
+ return {
54
+ root: {
55
+ ...toRootOutput(input.root),
56
+ healthy: input.rootHealthy,
57
+ status: input.rootStatus ?? [],
58
+ },
59
+ store,
60
+ references: input.referenceEntries,
61
+ status,
62
+ };
63
+ }
64
+ //# sourceMappingURL=relationship-health.js.map
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Shared OpenSpec root resolution for normal commands.
3
+ *
4
+ * Normal commands (`new change`, `status`, `instructions`, `list`, `show`,
5
+ * `validate`, `archive`) resolve one OpenSpec root through this module:
6
+ *
7
+ * - `--store <id>` selects a registered store's root.
8
+ * - Without `--store`, the nearest ancestor containing `openspec/` wins.
9
+ * Leftover workspace view state is never considered a root here.
10
+ * - With no nearest root, registered stores produce a selection hint error;
11
+ * otherwise commands may treat the current directory as an implicit root.
12
+ *
13
+ * Diagnostic codes reuse the store taxonomy where an error passes
14
+ * through unchanged (`invalid_store_id`, metadata parse failures);
15
+ * resolver-specific failures use the normal-command codes below
16
+ * (`unknown_store`, `no_registered_stores`, `store_identity_mismatch`,
17
+ * `unhealthy_store_root`, `store_path_not_supported`,
18
+ * `invalid_store_pointer`, `no_root_with_registered_stores`,
19
+ * `no_openspec_root`).
20
+ */
21
+ import { type PlanningHome } from './planning-home.js';
22
+ export type OpenSpecRootSource = 'store' | 'declared' | 'nearest' | 'implicit';
23
+ export interface StoreSelectorOptions {
24
+ store?: string;
25
+ storePath?: string;
26
+ }
27
+ export interface ResolveOpenSpecRootOptions extends StoreSelectorOptions {
28
+ startPath?: string;
29
+ allowImplicitRoot?: boolean;
30
+ globalDataDir?: string;
31
+ }
32
+ export interface ResolvedOpenSpecRoot {
33
+ path: string;
34
+ changesDir: string;
35
+ specsDir: string;
36
+ archiveDir: string;
37
+ defaultSchema: 'spec-driven';
38
+ source: OpenSpecRootSource;
39
+ storeId?: string;
40
+ }
41
+ export interface RootSelectionDiagnostic {
42
+ severity: 'error';
43
+ code: string;
44
+ message: string;
45
+ target?: string;
46
+ fix?: string;
47
+ }
48
+ export declare class RootSelectionError extends Error {
49
+ readonly diagnostic: RootSelectionDiagnostic;
50
+ constructor(message: string, code: string, options?: {
51
+ target?: string;
52
+ fix?: string;
53
+ });
54
+ }
55
+ export declare function isRootSelectionError(error: unknown): error is RootSelectionError;
56
+ /**
57
+ * The metadata-identity and root-health stages of registered-store
58
+ * resolution, as a non-throwing result. `resolveStoreRoot` maps each
59
+ * failure kind to its established error; the reference index assembler
60
+ * maps them to warnings. One shared inspection path — never fork it.
61
+ */
62
+ export type RegisteredStoreInspection = {
63
+ kind: 'ok';
64
+ canonicalRoot: string;
65
+ } | {
66
+ kind: 'metadata_error';
67
+ error: unknown;
68
+ } | {
69
+ kind: 'metadata_missing';
70
+ metadataPath: string;
71
+ } | {
72
+ kind: 'metadata_id_mismatch';
73
+ actualId: string;
74
+ } | {
75
+ kind: 'unhealthy_root';
76
+ problems: string;
77
+ };
78
+ export declare function inspectRegisteredStore(id: string, storeRoot: string): Promise<RegisteredStoreInspection>;
79
+ export declare function resolveOpenSpecRoot(options?: ResolveOpenSpecRootOptions): Promise<ResolvedOpenSpecRoot>;
80
+ export interface RootOutput {
81
+ path: string;
82
+ source: OpenSpecRootSource;
83
+ store_id?: string;
84
+ }
85
+ export declare function toRootOutput(root: ResolvedOpenSpecRoot): RootOutput;
86
+ /**
87
+ * A store-selected root — explicit `--store` or the declared fallback.
88
+ * Cross-root behavior (absolute paths, --store hints, suppressed
89
+ * noun-form suggestions) keys on this, never on `source` directly.
90
+ */
91
+ export declare function isStoreSelectedRoot(root: ResolvedOpenSpecRoot): root is ResolvedOpenSpecRoot & {
92
+ storeId: string;
93
+ };
94
+ /**
95
+ * Human-mode verification signal for a selected store. Written to stderr so
96
+ * raw-Markdown and agent-consumed stdout payloads stay clean.
97
+ */
98
+ export declare function emitStoreRootBanner(root: ResolvedOpenSpecRoot): void;
99
+ /**
100
+ * Keeps follow-up command hints inside the selected store: a hint a user can
101
+ * paste verbatim must carry `--store <id>` when a store was selected.
102
+ */
103
+ export declare function withStoreFlag(root: ResolvedOpenSpecRoot, command: string): string;
104
+ /**
105
+ * Compatibility bridge for workflow code that still expects a PlanningHome.
106
+ * The planning home is always repo-shaped.
107
+ */
108
+ export declare function toPlanningHome(root: ResolvedOpenSpecRoot): PlanningHome;
109
+ /**
110
+ * CLI adapter shared by the supported commands. In JSON mode a resolution
111
+ * failure is reported as a machine-readable payload on stdout (no human prose
112
+ * or blank lines) with a non-zero exit code; the caller must return when this
113
+ * resolves to null. In human mode the error propagates to the command's
114
+ * standard error handling so message text and exit behavior stay consistent.
115
+ */
116
+ export declare function resolveRootForCommand(selector: StoreSelectorOptions, output?: {
117
+ json?: boolean;
118
+ failurePayload?: Record<string, unknown>;
119
+ /** Diagnostic commands inspect what exists; they never scaffold. */
120
+ allowImplicitRoot?: boolean;
121
+ }): Promise<ResolvedOpenSpecRoot | null>;
122
+ //# sourceMappingURL=root-selection.d.ts.map
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Shared OpenSpec root resolution for normal commands.
3
+ *
4
+ * Normal commands (`new change`, `status`, `instructions`, `list`, `show`,
5
+ * `validate`, `archive`) resolve one OpenSpec root through this module:
6
+ *
7
+ * - `--store <id>` selects a registered store's root.
8
+ * - Without `--store`, the nearest ancestor containing `openspec/` wins.
9
+ * Leftover workspace view state is never considered a root here.
10
+ * - With no nearest root, registered stores produce a selection hint error;
11
+ * otherwise commands may treat the current directory as an implicit root.
12
+ *
13
+ * Diagnostic codes reuse the store taxonomy where an error passes
14
+ * through unchanged (`invalid_store_id`, metadata parse failures);
15
+ * resolver-specific failures use the normal-command codes below
16
+ * (`unknown_store`, `no_registered_stores`, `store_identity_mismatch`,
17
+ * `unhealthy_store_root`, `store_path_not_supported`,
18
+ * `invalid_store_pointer`, `no_root_with_registered_stores`,
19
+ * `no_openspec_root`).
20
+ */
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import { StoreError } from './store/errors.js';
24
+ import { getStoreMetadataPath, listStoreRegistryEntries, readStoreRegistryState, readOptionalStoreMetadataState, validateStoreId, } from './store/foundation.js';
25
+ import { getStoreRootForBackend } from './store/registry.js';
26
+ import { inspectOpenSpecRoot } from './openspec-root.js';
27
+ import { findRepoPlanningRootSync } from './planning-home.js';
28
+ import { classifyOpenSpecDir, storePointerProblem } from './project-config.js';
29
+ import { FileSystemUtils } from '../utils/file-system.js';
30
+ export class RootSelectionError extends Error {
31
+ diagnostic;
32
+ constructor(message, code, options = {}) {
33
+ super(message);
34
+ this.name = 'RootSelectionError';
35
+ this.diagnostic = {
36
+ severity: 'error',
37
+ code,
38
+ message,
39
+ ...options,
40
+ };
41
+ }
42
+ }
43
+ export function isRootSelectionError(error) {
44
+ return error instanceof RootSelectionError;
45
+ }
46
+ function fromStoreError(error) {
47
+ if (error instanceof StoreError) {
48
+ throw new RootSelectionError(error.message, error.diagnostic.code, {
49
+ ...(error.diagnostic.target ? { target: error.diagnostic.target } : {}),
50
+ ...(error.diagnostic.fix ? { fix: error.diagnostic.fix } : {}),
51
+ });
52
+ }
53
+ throw error;
54
+ }
55
+ function doctorFix(id) {
56
+ return `Run openspec store doctor ${id} to inspect it.`;
57
+ }
58
+ function makeRoot(rootPath, source, storeId) {
59
+ return {
60
+ path: rootPath,
61
+ changesDir: path.join(rootPath, 'openspec', 'changes'),
62
+ specsDir: path.join(rootPath, 'openspec', 'specs'),
63
+ archiveDir: path.join(rootPath, 'openspec', 'changes', 'archive'),
64
+ defaultSchema: 'spec-driven',
65
+ source,
66
+ ...(storeId ? { storeId } : {}),
67
+ };
68
+ }
69
+ function canonicalDirectory(startPath) {
70
+ const resolved = path.resolve(startPath);
71
+ try {
72
+ const stats = fs.statSync(resolved);
73
+ const dir = stats.isDirectory() ? resolved : path.dirname(resolved);
74
+ return FileSystemUtils.canonicalizeExistingPath(dir);
75
+ }
76
+ catch {
77
+ return resolved;
78
+ }
79
+ }
80
+ async function resolveStoreRoot(id, globalDataDir, source = 'store') {
81
+ try {
82
+ validateStoreId(id);
83
+ }
84
+ catch (error) {
85
+ fromStoreError(error);
86
+ }
87
+ let registry;
88
+ try {
89
+ registry = await readStoreRegistryState(globalDataDir ? { globalDataDir } : {});
90
+ }
91
+ catch (error) {
92
+ fromStoreError(error);
93
+ }
94
+ const entries = registry ? listStoreRegistryEntries(registry) : [];
95
+ const entry = entries.find((candidate) => candidate.id === id);
96
+ if (!entry) {
97
+ if (entries.length === 0) {
98
+ throw new RootSelectionError(`Unknown store '${id}'. No stores are registered.`, 'no_registered_stores', {
99
+ target: 'store.id',
100
+ fix: `Run openspec store setup ${id} or openspec store register <path> first.`,
101
+ });
102
+ }
103
+ throw new RootSelectionError(`Unknown store '${id}'. Registered stores: ${entries
104
+ .map((candidate) => candidate.id)
105
+ .join(', ')}.`, 'unknown_store', {
106
+ target: 'store.id',
107
+ fix: 'Pass a registered store id, or run openspec store list.',
108
+ });
109
+ }
110
+ const storeRoot = getStoreRootForBackend(entry.backend);
111
+ const inspection = await inspectRegisteredStore(id, storeRoot);
112
+ switch (inspection.kind) {
113
+ case 'metadata_error':
114
+ return fromStoreError(inspection.error);
115
+ case 'metadata_missing':
116
+ // The doctor pointer lives in the message because human-mode command
117
+ // wrappers print only the message, not the fix field.
118
+ throw new RootSelectionError(`Store '${id}' is missing identity metadata at ${inspection.metadataPath}. ${doctorFix(id)}`, 'store_identity_mismatch', { target: 'store.metadata', fix: doctorFix(id) });
119
+ case 'metadata_id_mismatch':
120
+ throw new RootSelectionError(`Store '${id}' metadata id '${inspection.actualId}' does not match its registered id. ${doctorFix(id)}`, 'store_identity_mismatch', { target: 'store.metadata', fix: doctorFix(id) });
121
+ case 'unhealthy_root':
122
+ throw new RootSelectionError(`Store '${id}' does not have a healthy OpenSpec root at ${storeRoot}: ${inspection.problems} ${doctorFix(id)}`, 'unhealthy_store_root', { target: 'openspec.root', fix: doctorFix(id) });
123
+ case 'ok':
124
+ return makeRoot(inspection.canonicalRoot, source, id);
125
+ default: {
126
+ // Exhaustiveness guard: a new inspection kind must be handled
127
+ // here explicitly, not fall through to an undefined root.
128
+ const unhandled = inspection;
129
+ throw new Error(`Unhandled store inspection kind: ${JSON.stringify(unhandled)}`);
130
+ }
131
+ }
132
+ }
133
+ export async function inspectRegisteredStore(id, storeRoot) {
134
+ // Identity (metadata) failures win before root-health diagnostics.
135
+ let metadata;
136
+ try {
137
+ metadata = await readOptionalStoreMetadataState(storeRoot);
138
+ }
139
+ catch (error) {
140
+ return { kind: 'metadata_error', error };
141
+ }
142
+ if (!metadata) {
143
+ return { kind: 'metadata_missing', metadataPath: getStoreMetadataPath(storeRoot) };
144
+ }
145
+ if (metadata.id !== id) {
146
+ return { kind: 'metadata_id_mismatch', actualId: metadata.id };
147
+ }
148
+ const inspection = await inspectOpenSpecRoot(storeRoot);
149
+ if (!inspection.healthy) {
150
+ const problems = inspection.diagnostics.map((diagnostic) => diagnostic.message).join(' ') ||
151
+ 'OpenSpec root is missing or incomplete.';
152
+ return { kind: 'unhealthy_root', problems };
153
+ }
154
+ return { kind: 'ok', canonicalRoot: FileSystemUtils.canonicalizeExistingPath(storeRoot) };
155
+ }
156
+ /**
157
+ * Classifies the nearest `openspec/` directory (slice 3.2): a planning
158
+ * shape (specs/ or changes/ directories) is a real root and wins —
159
+ * fallback never override. A config-only directory with a `store:`
160
+ * pointer resolves the declared store; without one, it stays a root
161
+ * (today's behavior for freshly initialized minimal roots).
162
+ */
163
+ /**
164
+ * The nearest-root walk, qualified: an `openspec/` DIRECTORY alone is
165
+ * not a root — it must carry a planning shape or a config file.
166
+ * Without this, the recommended `~/openspec/<id>` store layout would
167
+ * make $HOME a phantom root that captures every command under the
168
+ * home tree.
169
+ */
170
+ function findQualifyingRootSync(startPath) {
171
+ let candidate = findRepoPlanningRootSync(startPath);
172
+ while (candidate) {
173
+ const { hasPlanningShape, pointer } = classifyOpenSpecDir(candidate);
174
+ if (hasPlanningShape || pointer.filePath) {
175
+ return candidate;
176
+ }
177
+ const parent = path.dirname(candidate);
178
+ if (parent === candidate) {
179
+ return null;
180
+ }
181
+ candidate = findRepoPlanningRootSync(parent);
182
+ }
183
+ return null;
184
+ }
185
+ async function resolveNearestOrDeclaredRoot(nearestRoot, globalDataDir) {
186
+ const { hasPlanningShape, pointer } = classifyOpenSpecDir(nearestRoot);
187
+ if (hasPlanningShape) {
188
+ if (pointer.value !== undefined) {
189
+ console.error(`Warning: ${pointer.filePath} declares store '${pointer.value}', but this directory is a real OpenSpec root; the declaration is ignored.`);
190
+ }
191
+ return makeRoot(nearestRoot, 'nearest');
192
+ }
193
+ if (pointer.malformed) {
194
+ const problem = storePointerProblem(pointer.malformed);
195
+ throw new RootSelectionError(`Invalid store declaration in ${pointer.filePath}: ${problem}.`, 'invalid_store_pointer', {
196
+ target: 'store.pointer',
197
+ fix: pointer.malformed === 'unparseable'
198
+ ? `Fix the YAML syntax in ${pointer.filePath}.`
199
+ : `Edit ${pointer.filePath} so the store key is a registered store id, or remove it.`,
200
+ });
201
+ }
202
+ if (pointer.value === undefined) {
203
+ return makeRoot(nearestRoot, 'nearest');
204
+ }
205
+ try {
206
+ return await resolveStoreRoot(pointer.value, globalDataDir, 'declared');
207
+ }
208
+ catch (error) {
209
+ if (error instanceof RootSelectionError) {
210
+ // Rewrap with the declaration origin. The unknown-store fix is
211
+ // reshaped for the actual mistake: the user declared a pointer,
212
+ // they did not pass --store.
213
+ const declarationFix = error.diagnostic.code === 'unknown_store'
214
+ ? `Register the store (openspec store register <path> --id ${pointer.value}) or edit ${pointer.filePath} to name a registered store.`
215
+ : error.diagnostic.fix;
216
+ throw new RootSelectionError(`Declared in ${pointer.filePath}: ${error.message}`, error.diagnostic.code, {
217
+ ...(error.diagnostic.target ? { target: error.diagnostic.target } : {}),
218
+ ...(declarationFix ? { fix: declarationFix } : {}),
219
+ });
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ export async function resolveOpenSpecRoot(options = {}) {
225
+ if (options.storePath !== undefined) {
226
+ throw new RootSelectionError('--store-path is not supported. Register the path with openspec store register <path>, then select it with --store <id>.', 'store_path_not_supported', {
227
+ target: 'store.id',
228
+ fix: 'openspec store register <path>, then rerun with --store <id>.',
229
+ });
230
+ }
231
+ if (options.store !== undefined) {
232
+ return resolveStoreRoot(options.store, options.globalDataDir);
233
+ }
234
+ const startPath = options.startPath ?? process.cwd();
235
+ const nearestRoot = findQualifyingRootSync(startPath);
236
+ if (nearestRoot) {
237
+ return resolveNearestOrDeclaredRoot(nearestRoot, options.globalDataDir);
238
+ }
239
+ let registry;
240
+ try {
241
+ registry = await readStoreRegistryState(options.globalDataDir ? { globalDataDir: options.globalDataDir } : {});
242
+ }
243
+ catch (error) {
244
+ fromStoreError(error);
245
+ }
246
+ const registeredIds = registry
247
+ ? listStoreRegistryEntries(registry).map((entry) => entry.id)
248
+ : [];
249
+ if (registeredIds.length > 0) {
250
+ throw new RootSelectionError(`No OpenSpec root found in the current directory or its ancestors. Registered stores: ${registeredIds.join(', ')}. Pass --store <id> to use one, or run openspec init to create a local root.`, 'no_root_with_registered_stores', {
251
+ target: 'openspec.root',
252
+ fix: `Rerun with --store <id> (registered: ${registeredIds.join(', ')}) or run openspec init.`,
253
+ });
254
+ }
255
+ if (options.allowImplicitRoot === false) {
256
+ throw new RootSelectionError('No OpenSpec root found from the current directory.', 'no_openspec_root', { target: 'openspec.root', fix: 'Run openspec init to create a root here.' });
257
+ }
258
+ return makeRoot(canonicalDirectory(startPath), 'implicit');
259
+ }
260
+ export function toRootOutput(root) {
261
+ return {
262
+ path: root.path,
263
+ source: root.source,
264
+ ...(root.storeId ? { store_id: root.storeId } : {}),
265
+ };
266
+ }
267
+ /**
268
+ * A store-selected root — explicit `--store` or the declared fallback.
269
+ * Cross-root behavior (absolute paths, --store hints, suppressed
270
+ * noun-form suggestions) keys on this, never on `source` directly.
271
+ */
272
+ export function isStoreSelectedRoot(root) {
273
+ return root.storeId !== undefined;
274
+ }
275
+ /**
276
+ * Human-mode verification signal for a selected store. Written to stderr so
277
+ * raw-Markdown and agent-consumed stdout payloads stay clean.
278
+ */
279
+ export function emitStoreRootBanner(root) {
280
+ if (isStoreSelectedRoot(root)) {
281
+ console.error(`Using OpenSpec root: ${root.storeId} (${root.path})`);
282
+ }
283
+ }
284
+ /**
285
+ * Keeps follow-up command hints inside the selected store: a hint a user can
286
+ * paste verbatim must carry `--store <id>` when a store was selected.
287
+ */
288
+ export function withStoreFlag(root, command) {
289
+ return isStoreSelectedRoot(root)
290
+ ? `${command} --store ${root.storeId}`
291
+ : command;
292
+ }
293
+ /**
294
+ * Compatibility bridge for workflow code that still expects a PlanningHome.
295
+ * The planning home is always repo-shaped.
296
+ */
297
+ export function toPlanningHome(root) {
298
+ return {
299
+ kind: 'repo',
300
+ root: root.path,
301
+ changesDir: root.changesDir,
302
+ defaultSchema: root.defaultSchema,
303
+ };
304
+ }
305
+ /**
306
+ * CLI adapter shared by the supported commands. In JSON mode a resolution
307
+ * failure is reported as a machine-readable payload on stdout (no human prose
308
+ * or blank lines) with a non-zero exit code; the caller must return when this
309
+ * resolves to null. In human mode the error propagates to the command's
310
+ * standard error handling so message text and exit behavior stay consistent.
311
+ */
312
+ export async function resolveRootForCommand(selector, output = {}) {
313
+ try {
314
+ const root = await resolveOpenSpecRoot({
315
+ ...(selector.store !== undefined ? { store: selector.store } : {}),
316
+ ...(selector.storePath !== undefined ? { storePath: selector.storePath } : {}),
317
+ ...(output.allowImplicitRoot !== undefined
318
+ ? { allowImplicitRoot: output.allowImplicitRoot }
319
+ : {}),
320
+ });
321
+ // Emitted at resolution time so the banner survives command failures
322
+ // that happen after the root was successfully selected.
323
+ if (!output.json) {
324
+ emitStoreRootBanner(root);
325
+ }
326
+ return root;
327
+ }
328
+ catch (error) {
329
+ if (output.json && isRootSelectionError(error)) {
330
+ console.log(JSON.stringify({ ...(output.failurePayload ?? {}), status: [error.diagnostic] }, null, 2));
331
+ process.exitCode = 1;
332
+ return null;
333
+ }
334
+ throw error;
335
+ }
336
+ }
337
+ //# sourceMappingURL=root-selection.js.map
@@ -4,9 +4,14 @@ export const ScenarioSchema = z.object({
4
4
  rawText: z.string().min(1, VALIDATION_MESSAGES.SCENARIO_EMPTY),
5
5
  });
6
6
  export const RequirementSchema = z.object({
7
+ // SHALL/MUST body-keyword enforcement lives in the imperative validator
8
+ // (Validator.applySpecRules), not here: the parser collapses the requirement
9
+ // header into `text`, so a Zod refine on `text` cannot tell "keyword in header
10
+ // only" from "keyword in body" and emits a misleading generic error. The
11
+ // validator recovers the header and emits the targeted hint for both the
12
+ // main-spec and change-delta paths (#1156).
7
13
  text: z.string()
8
- .min(1, VALIDATION_MESSAGES.REQUIREMENT_EMPTY)
9
- .refine((text) => text.includes('SHALL') || text.includes('MUST'), VALIDATION_MESSAGES.REQUIREMENT_NO_SHALL),
14
+ .min(1, VALIDATION_MESSAGES.REQUIREMENT_EMPTY),
10
15
  scenarios: z.array(ScenarioSchema)
11
16
  .min(1, VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS),
12
17
  });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pre-approved tools for generated skills and slash commands, emitted as the
3
+ * `allowed-tools` frontmatter field (Agent Skills standard for SKILL.md;
4
+ * same field for Claude Code slash commands). Scoped to the OpenSpec CLI so
5
+ * agents that honor it stop prompting on each `openspec` call; the field
6
+ * only pre-approves — it does not restrict — so any other tool a skill or
7
+ * command needs (Read, Write, arbitrary Bash for builds/tests) stays
8
+ * available under the user's normal permission settings. Tools that don't
9
+ * recognize the field ignore it.
10
+ */
11
+ export declare const OPENSPEC_CLI_ALLOWED_TOOLS = "Bash(openspec:*)";
12
+ //# sourceMappingURL=allowed-tools.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pre-approved tools for generated skills and slash commands, emitted as the
3
+ * `allowed-tools` frontmatter field (Agent Skills standard for SKILL.md;
4
+ * same field for Claude Code slash commands). Scoped to the OpenSpec CLI so
5
+ * agents that honor it stop prompting on each `openspec` call; the field
6
+ * only pre-approves — it does not restrict — so any other tool a skill or
7
+ * command needs (Read, Write, arbitrary Bash for builds/tests) stays
8
+ * available under the user's normal permission settings. Tools that don't
9
+ * recognize the field ignore it.
10
+ */
11
+ export const OPENSPEC_CLI_ALLOWED_TOOLS = 'Bash(openspec:*)';
12
+ //# sourceMappingURL=allowed-tools.js.map
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Shared utilities for generating skill and command files.
5
5
  */
6
- import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxProposeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate, getOpsxOnboardCommandTemplate, getOpsxProposeCommandTemplate, } from '../templates/skill-templates.js';
6
+ import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getUpdateChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxProposeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxUpdateCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate, getOpsxOnboardCommandTemplate, getOpsxProposeCommandTemplate, } from '../templates/skill-templates.js';
7
+ import { OPENSPEC_CLI_ALLOWED_TOOLS } from './allowed-tools.js';
7
8
  /**
8
9
  * Gets skill templates with their directory names, optionally filtered by workflow IDs.
9
10
  *
@@ -15,6 +16,7 @@ export function getSkillTemplates(workflowFilter) {
15
16
  { template: getNewChangeSkillTemplate(), dirName: 'openspec-new-change', workflowId: 'new' },
16
17
  { template: getContinueChangeSkillTemplate(), dirName: 'openspec-continue-change', workflowId: 'continue' },
17
18
  { template: getApplyChangeSkillTemplate(), dirName: 'openspec-apply-change', workflowId: 'apply' },
19
+ { template: getUpdateChangeSkillTemplate(), dirName: 'openspec-update-change', workflowId: 'update' },
18
20
  { template: getFfChangeSkillTemplate(), dirName: 'openspec-ff-change', workflowId: 'ff' },
19
21
  { template: getSyncSpecsSkillTemplate(), dirName: 'openspec-sync-specs', workflowId: 'sync' },
20
22
  { template: getArchiveChangeSkillTemplate(), dirName: 'openspec-archive-change', workflowId: 'archive' },
@@ -39,6 +41,7 @@ export function getCommandTemplates(workflowFilter) {
39
41
  { template: getOpsxNewCommandTemplate(), id: 'new' },
40
42
  { template: getOpsxContinueCommandTemplate(), id: 'continue' },
41
43
  { template: getOpsxApplyCommandTemplate(), id: 'apply' },
44
+ { template: getOpsxUpdateCommandTemplate(), id: 'update' },
42
45
  { template: getOpsxFfCommandTemplate(), id: 'ff' },
43
46
  { template: getOpsxSyncCommandTemplate(), id: 'sync' },
44
47
  { template: getOpsxArchiveCommandTemplate(), id: 'archive' },
@@ -82,6 +85,7 @@ export function generateSkillContent(template, generatedByVersion, transformInst
82
85
  return `---
83
86
  name: ${template.name}
84
87
  description: ${template.description}
88
+ allowed-tools: ${OPENSPEC_CLI_ALLOWED_TOOLS}
85
89
  license: ${template.license || 'MIT'}
86
90
  compatibility: ${template.compatibility || 'Requires openspec CLI.'}
87
91
  metadata:
@@ -6,12 +6,12 @@
6
6
  /**
7
7
  * Names of skill directories created by openspec init.
8
8
  */
9
- export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-apply-change", "openspec-ff-change", "openspec-sync-specs", "openspec-archive-change", "openspec-bulk-archive-change", "openspec-verify-change", "openspec-onboard", "openspec-propose"];
9
+ export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-apply-change", "openspec-update-change", "openspec-ff-change", "openspec-sync-specs", "openspec-archive-change", "openspec-bulk-archive-change", "openspec-verify-change", "openspec-onboard", "openspec-propose"];
10
10
  export type SkillName = (typeof SKILL_NAMES)[number];
11
11
  /**
12
12
  * IDs of command templates created by openspec init.
13
13
  */
14
- export declare const COMMAND_IDS: readonly ["explore", "new", "continue", "apply", "ff", "sync", "archive", "bulk-archive", "verify", "onboard", "propose"];
14
+ export declare const COMMAND_IDS: readonly ["explore", "new", "continue", "apply", "update", "ff", "sync", "archive", "bulk-archive", "verify", "onboard", "propose"];
15
15
  export type CommandId = (typeof COMMAND_IDS)[number];
16
16
  /**
17
17
  * Status of skill configuration for a tool.
@@ -14,6 +14,7 @@ export const SKILL_NAMES = [
14
14
  'openspec-new-change',
15
15
  'openspec-continue-change',
16
16
  'openspec-apply-change',
17
+ 'openspec-update-change',
17
18
  'openspec-ff-change',
18
19
  'openspec-sync-specs',
19
20
  'openspec-archive-change',
@@ -30,6 +31,7 @@ export const COMMAND_IDS = [
30
31
  'new',
31
32
  'continue',
32
33
  'apply',
34
+ 'update',
33
35
  'ff',
34
36
  'sync',
35
37
  'archive',
@@ -35,7 +35,9 @@ export declare function findSpecUpdates(changeDir: string, mainSpecsDir: string)
35
35
  * Build an updated spec by applying delta operations.
36
36
  * Returns the rebuilt content and counts of operations.
37
37
  */
38
- export declare function buildUpdatedSpec(update: SpecUpdate, changeName: string): Promise<{
38
+ export declare function buildUpdatedSpec(update: SpecUpdate, changeName: string, options?: {
39
+ silent?: boolean;
40
+ }): Promise<{
39
41
  rebuilt: string;
40
42
  counts: {
41
43
  added: number;
@@ -52,6 +54,9 @@ export declare function writeUpdatedSpec(update: SpecUpdate, rebuilt: string, co
52
54
  modified: number;
53
55
  removed: number;
54
56
  renamed: number;
57
+ }, options?: {
58
+ silent?: boolean;
59
+ displayPath?: string;
55
60
  }): Promise<void>;
56
61
  /**
57
62
  * Build a skeleton spec for new capabilities.