@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
@@ -1,6 +1,8 @@
1
+ import { buildCodeFenceMask } from './requirement-text.js';
1
2
  export function normalizeRequirementName(name) {
2
3
  return name.trim();
3
4
  }
5
+ /** The canonical requirement header the delta reader recognizes. */
4
6
  const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/i;
5
7
  /**
6
8
  * Extracts the Requirements section from a spec file and parses requirement blocks.
@@ -84,15 +86,26 @@ export function parseDeltaSpec(content) {
84
86
  const modifiedLookup = getSectionCaseInsensitive(sections, 'MODIFIED Requirements');
85
87
  const removedLookup = getSectionCaseInsensitive(sections, 'REMOVED Requirements');
86
88
  const renamedLookup = getSectionCaseInsensitive(sections, 'RENAMED Requirements');
87
- const added = parseRequirementBlocksFromSection(addedLookup.body);
88
- const modified = parseRequirementBlocksFromSection(modifiedLookup.body);
89
+ const skippedHeaders = [];
90
+ const added = parseRequirementBlocksFromSection(addedLookup.body, {
91
+ section: addedLookup.title,
92
+ bodyStartLine: addedLookup.bodyStartLine,
93
+ sink: skippedHeaders,
94
+ });
95
+ const modified = parseRequirementBlocksFromSection(modifiedLookup.body, {
96
+ section: modifiedLookup.title,
97
+ bodyStartLine: modifiedLookup.bodyStartLine,
98
+ sink: skippedHeaders,
99
+ });
89
100
  const removedNames = parseRemovedNames(removedLookup.body);
90
101
  const renamedPairs = parseRenamedPairs(renamedLookup.body);
102
+ skippedHeaders.sort((a, b) => a.line - b.line);
91
103
  return {
92
104
  added,
93
105
  modified,
94
106
  removed: removedNames,
95
107
  renamed: renamedPairs,
108
+ skippedHeaders,
96
109
  sectionPresence: {
97
110
  added: addedLookup.found,
98
111
  modified: modifiedLookup.found,
@@ -116,28 +129,48 @@ function splitTopLevelSections(content) {
116
129
  const current = indices[i];
117
130
  const next = indices[i + 1];
118
131
  const body = lines.slice(current.index + 1, next ? next.index : lines.length).join('\n');
119
- result[current.title] = body;
132
+ // First body line, 1-based: the header is at 0-based current.index.
133
+ result[current.title] = { body, bodyStartLine: current.index + 2 };
120
134
  }
121
135
  return result;
122
136
  }
123
137
  function getSectionCaseInsensitive(sections, desired) {
124
138
  const target = desired.toLowerCase();
125
- for (const [title, body] of Object.entries(sections)) {
139
+ for (const [title, { body, bodyStartLine }] of Object.entries(sections)) {
126
140
  if (title.toLowerCase() === target)
127
- return { body, found: true };
141
+ return { title, body, bodyStartLine, found: true };
128
142
  }
129
- return { body: '', found: false };
143
+ return { title: desired, body: '', bodyStartLine: 0, found: false };
130
144
  }
131
- function parseRequirementBlocksFromSection(sectionBody) {
145
+ function parseRequirementBlocksFromSection(sectionBody, skipped) {
132
146
  if (!sectionBody)
133
147
  return [];
134
148
  const lines = normalizeLineEndings(sectionBody).split('\n');
149
+ // Record the non-canonical level-3 headers this reader skips, at the moment
150
+ // it skips them, so the INFO note describes the reader's real boundaries.
151
+ // Fence-masked lines are excluded: the body reader treats them as fenced
152
+ // content, not as headers.
153
+ const fenceMask = skipped ? buildCodeFenceMask(lines) : undefined;
154
+ const recordIfSkippedHeader = (index) => {
155
+ if (!skipped || fenceMask[index])
156
+ return;
157
+ const h3 = lines[index].match(/^###\s+(.+?)\s*$/);
158
+ if (h3 && !REQUIREMENT_HEADER_REGEX.test(lines[index])) {
159
+ skipped.sink.push({
160
+ header: h3[1].trim(),
161
+ section: skipped.section,
162
+ line: skipped.bodyStartLine + index,
163
+ });
164
+ }
165
+ };
135
166
  const blocks = [];
136
167
  let i = 0;
137
168
  while (i < lines.length) {
138
169
  // Seek next requirement header
139
- while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i]))
170
+ while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i])) {
171
+ recordIfSkippedHeader(i);
140
172
  i++;
173
+ }
141
174
  if (i >= lines.length)
142
175
  break;
143
176
  const headerLine = lines[i];
@@ -150,6 +183,7 @@ function parseRequirementBlocksFromSection(sectionBody) {
150
183
  const buf = [headerLine];
151
184
  i++;
152
185
  while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i]) && !/^##\s+/.test(lines[i])) {
186
+ recordIfSkippedHeader(i);
153
187
  buf.push(lines[i]);
154
188
  i++;
155
189
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared, fence-aware requirement-reading helpers.
3
+ *
4
+ * The requirement reader used to be implemented twice — once for main specs
5
+ * (`MarkdownParser.parseRequirements`) and once for change deltas
6
+ * (`Validator.extractRequirementText` / `countScenarios`) — and the two drifted
7
+ * apart. These helpers are the single source of truth for requirement-body
8
+ * extraction, scenario counting, and `SHALL`/`MUST` detection in
9
+ * `validate <change>`, `validate <spec>`, and `archive`.
10
+ */
11
+ /**
12
+ * Build a per-line mask marking lines that fall inside a fenced code block
13
+ * (``` ``` ``` or ``` ~~~ ```), including the fence lines themselves. Mirrors the
14
+ * fence rules markdown uses: a fence opens on the first ```` ```/~~~ ```` of
15
+ * length >= 3 and closes on a line of the same marker whose length is >= the
16
+ * opening length, with nothing but whitespace after it.
17
+ */
18
+ export declare function buildCodeFenceMask(lines: string[]): boolean[];
19
+ /**
20
+ * The one predicate for normative-keyword detection. Matches `SHALL` or `MUST`
21
+ * as whole words so the change-delta reader and the schema-based reader accept
22
+ * and reject identical text.
23
+ */
24
+ export declare function containsShallOrMust(text: string): boolean;
25
+ /**
26
+ * Extract the full requirement body from the lines that follow a
27
+ * `### Requirement:` header (the lines may include scenarios and fenced code).
28
+ *
29
+ * Captures every body line from the start up to the first header found on a
30
+ * non-fenced line — usually the first `#### Scenario:`, but also a stray `###`
31
+ * divider the delta reader absorbed into the block — skipping blank lines and
32
+ * any line inside a fenced code block. `**metadata**:` lines are skipped only
33
+ * when other body text remains: a requirement written entirely as
34
+ * `**Constraint**: The system MUST ...` keeps that line as its body. Captured
35
+ * lines are trimmed and joined with newlines so a requirement whose text wraps
36
+ * across lines — or whose `SHALL`/`MUST` lands on a later line — is read in
37
+ * full.
38
+ */
39
+ export declare function extractRequirementBody(bodyLines: string[]): string;
40
+ /**
41
+ * Parser/display fallback for a requirement block with no body text. This is
42
+ * what lets a bare `### The system SHALL ...` header remain readable on the
43
+ * spec path (the title is the requirement). Validator body-keyword checks for
44
+ * canonical `### Requirement:` blocks use `extractRequirementBody` directly so
45
+ * a keyword that appears only in the header still receives the #1156/#1280
46
+ * body-keyword hint.
47
+ */
48
+ export declare function extractRequirementText(headerTitle: string, bodyLines: string[]): string;
49
+ /**
50
+ * Count the real scenarios in a requirement block: `#### ` headers on non-fenced
51
+ * lines. A `#### Scenario:` that lives inside a fenced example is not a real
52
+ * scenario and is not counted.
53
+ */
54
+ export declare function countScenarios(bodyLines: string[]): number;
55
+ //# sourceMappingURL=requirement-text.d.ts.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Shared, fence-aware requirement-reading helpers.
3
+ *
4
+ * The requirement reader used to be implemented twice — once for main specs
5
+ * (`MarkdownParser.parseRequirements`) and once for change deltas
6
+ * (`Validator.extractRequirementText` / `countScenarios`) — and the two drifted
7
+ * apart. These helpers are the single source of truth for requirement-body
8
+ * extraction, scenario counting, and `SHALL`/`MUST` detection in
9
+ * `validate <change>`, `validate <spec>`, and `archive`.
10
+ */
11
+ /**
12
+ * Build a per-line mask marking lines that fall inside a fenced code block
13
+ * (``` ``` ``` or ``` ~~~ ```), including the fence lines themselves. Mirrors the
14
+ * fence rules markdown uses: a fence opens on the first ```` ```/~~~ ```` of
15
+ * length >= 3 and closes on a line of the same marker whose length is >= the
16
+ * opening length, with nothing but whitespace after it.
17
+ */
18
+ export function buildCodeFenceMask(lines) {
19
+ const mask = new Array(lines.length).fill(false);
20
+ let activeFence = null;
21
+ for (let i = 0; i < lines.length; i++) {
22
+ const fence = getFenceMarker(lines[i]);
23
+ if (!activeFence) {
24
+ if (fence) {
25
+ activeFence = fence;
26
+ mask[i] = true;
27
+ }
28
+ continue;
29
+ }
30
+ mask[i] = true;
31
+ if (isClosingFence(lines[i], activeFence)) {
32
+ activeFence = null;
33
+ }
34
+ }
35
+ return mask;
36
+ }
37
+ function getFenceMarker(line) {
38
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
39
+ if (!fenceMatch) {
40
+ return null;
41
+ }
42
+ return {
43
+ marker: fenceMatch[1][0],
44
+ length: fenceMatch[1].length,
45
+ };
46
+ }
47
+ function isClosingFence(line, activeFence) {
48
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})\s*$/);
49
+ return Boolean(fenceMatch &&
50
+ fenceMatch[1][0] === activeFence.marker &&
51
+ fenceMatch[1].length >= activeFence.length);
52
+ }
53
+ /** Lines that look like `**ID**: ...` / `**Priority**: ...` metadata. */
54
+ const METADATA_LINE = /^\*\*[^*]+\*\*:/;
55
+ /** Any markdown header line — the boundary where a requirement body ends. */
56
+ const HEADER_LINE = /^#{1,6}\s/;
57
+ /**
58
+ * A level-4 header. Deliberately matches ANY `####` header, not only
59
+ * `#### Scenario:` — the spec path treats every level-4 child of a requirement
60
+ * as a scenario, so the delta counter must too (parity). Don't tighten this to
61
+ * `Scenario:` without changing both paths together.
62
+ */
63
+ const SCENARIO_HEADER = /^####\s+/;
64
+ /**
65
+ * The one predicate for normative-keyword detection. Matches `SHALL` or `MUST`
66
+ * as whole words so the change-delta reader and the schema-based reader accept
67
+ * and reject identical text.
68
+ */
69
+ export function containsShallOrMust(text) {
70
+ return /\b(SHALL|MUST)\b/.test(text);
71
+ }
72
+ /**
73
+ * Extract the full requirement body from the lines that follow a
74
+ * `### Requirement:` header (the lines may include scenarios and fenced code).
75
+ *
76
+ * Captures every body line from the start up to the first header found on a
77
+ * non-fenced line — usually the first `#### Scenario:`, but also a stray `###`
78
+ * divider the delta reader absorbed into the block — skipping blank lines and
79
+ * any line inside a fenced code block. `**metadata**:` lines are skipped only
80
+ * when other body text remains: a requirement written entirely as
81
+ * `**Constraint**: The system MUST ...` keeps that line as its body. Captured
82
+ * lines are trimmed and joined with newlines so a requirement whose text wraps
83
+ * across lines — or whose `SHALL`/`MUST` lands on a later line — is read in
84
+ * full.
85
+ */
86
+ export function extractRequirementBody(bodyLines) {
87
+ const mask = buildCodeFenceMask(bodyLines);
88
+ const captured = [];
89
+ const metadata = [];
90
+ for (let i = 0; i < bodyLines.length; i++) {
91
+ if (mask[i])
92
+ continue; // inside a fenced code block
93
+ const line = bodyLines[i];
94
+ if (HEADER_LINE.test(line))
95
+ break; // first scenario or stray divider
96
+ const trimmed = line.trim();
97
+ if (trimmed.length === 0)
98
+ continue; // blank
99
+ if (METADATA_LINE.test(trimmed)) {
100
+ metadata.push(trimmed); // **ID**: / **Priority**: ...
101
+ continue;
102
+ }
103
+ captured.push(trimmed);
104
+ }
105
+ if (captured.length > 0)
106
+ return captured.join('\n');
107
+ return metadata.join('\n'); // metadata-only body: the metadata IS the body
108
+ }
109
+ /**
110
+ * Parser/display fallback for a requirement block with no body text. This is
111
+ * what lets a bare `### The system SHALL ...` header remain readable on the
112
+ * spec path (the title is the requirement). Validator body-keyword checks for
113
+ * canonical `### Requirement:` blocks use `extractRequirementBody` directly so
114
+ * a keyword that appears only in the header still receives the #1156/#1280
115
+ * body-keyword hint.
116
+ */
117
+ export function extractRequirementText(headerTitle, bodyLines) {
118
+ return extractRequirementBody(bodyLines) || headerTitle.trim();
119
+ }
120
+ /**
121
+ * Count the real scenarios in a requirement block: `#### ` headers on non-fenced
122
+ * lines. A `#### Scenario:` that lives inside a fenced example is not a real
123
+ * scenario and is not counted.
124
+ */
125
+ export function countScenarios(bodyLines) {
126
+ const mask = buildCodeFenceMask(bodyLines);
127
+ let count = 0;
128
+ for (let i = 0; i < bodyLines.length; i++) {
129
+ if (mask[i])
130
+ continue;
131
+ if (SCENARIO_HEADER.test(bodyLines[i]))
132
+ count++;
133
+ }
134
+ return count;
135
+ }
136
+ //# sourceMappingURL=requirement-text.js.map
@@ -1,19 +1,14 @@
1
- export type PlanningHomeKind = 'repo' | 'workspace';
1
+ export type PlanningHomeKind = 'repo';
2
2
  export interface PlanningHome {
3
3
  kind: PlanningHomeKind;
4
4
  root: string;
5
5
  changesDir: string;
6
6
  defaultSchema: string;
7
- workspace?: {
8
- name: string;
9
- links: string[];
10
- };
11
7
  }
12
8
  export interface ResolvePlanningHomeOptions {
13
9
  startPath?: string;
14
10
  allowImplicitRepoRoot?: boolean;
15
11
  }
16
- export declare function findWorkspacePlanningRootSync(startPath?: string): string | null;
17
12
  export declare function findRepoPlanningRootSync(startPath?: string): string | null;
18
13
  export declare function resolveCurrentPlanningHomeSync(options?: ResolvePlanningHomeOptions): PlanningHome;
19
14
  export declare function getChangeDir(planningHome: PlanningHome, changeName: string): string;
@@ -1,9 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import { getWorkspaceChangesDir, readWorkspaceViewStateSync, workspaceStateFileExistsSync, } from './workspace/index.js';
4
3
  import { FileSystemUtils } from '../utils/file-system.js';
5
4
  const REPO_DEFAULT_SCHEMA = 'spec-driven';
6
- const WORKSPACE_DEFAULT_SCHEMA = 'workspace-planning';
7
5
  function pathExistsAsDirectory(candidatePath) {
8
6
  try {
9
7
  return fs.statSync(candidatePath).isDirectory();
@@ -36,41 +34,9 @@ function findNearestAncestor(startPath, predicate) {
36
34
  currentDir = parentDir;
37
35
  }
38
36
  }
39
- export function findWorkspacePlanningRootSync(startPath = process.cwd()) {
40
- return findNearestAncestor(startPath, workspaceStateFileExistsSync);
41
- }
42
37
  export function findRepoPlanningRootSync(startPath = process.cwd()) {
43
38
  return findNearestAncestor(startPath, (dirPath) => pathExistsAsDirectory(path.join(dirPath, 'openspec')));
44
39
  }
45
- function isSameOrDescendant(rootPath, candidatePath) {
46
- const relative = path.relative(rootPath, candidatePath);
47
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
48
- }
49
- function countPathSegments(candidatePath) {
50
- return path.resolve(candidatePath).split(path.sep).filter(Boolean).length;
51
- }
52
- function isWindowsLikePath(candidatePath) {
53
- return /^[A-Za-z]:[\\/]/.test(candidatePath) || candidatePath.startsWith('\\\\');
54
- }
55
- function relativePlanningPath(fromPath, toPath) {
56
- if (isWindowsLikePath(fromPath) || isWindowsLikePath(toPath)) {
57
- return path.win32.relative(path.win32.normalize(fromPath), path.win32.normalize(toPath));
58
- }
59
- return path.posix.relative(fromPath.replace(/\\/g, '/'), toPath.replace(/\\/g, '/'));
60
- }
61
- function workspacePlanningHome(workspaceRoot) {
62
- const viewState = readWorkspaceViewStateSync(workspaceRoot);
63
- return {
64
- kind: 'workspace',
65
- root: workspaceRoot,
66
- changesDir: getWorkspaceChangesDir(workspaceRoot),
67
- defaultSchema: WORKSPACE_DEFAULT_SCHEMA,
68
- workspace: {
69
- name: viewState?.name ?? path.basename(workspaceRoot),
70
- links: Object.keys(viewState?.links ?? {}).sort((a, b) => a.localeCompare(b)),
71
- },
72
- };
73
- }
74
40
  function repoPlanningHome(repoRoot) {
75
41
  return {
76
42
  kind: 'repo',
@@ -82,13 +48,7 @@ function repoPlanningHome(repoRoot) {
82
48
  export function resolveCurrentPlanningHomeSync(options = {}) {
83
49
  const startPath = options.startPath ?? process.cwd();
84
50
  const searchStart = getSearchStartDirectory(startPath);
85
- const workspaceRoot = findWorkspacePlanningRootSync(searchStart);
86
51
  const repoRoot = findRepoPlanningRootSync(searchStart);
87
- if (workspaceRoot && isSameOrDescendant(workspaceRoot, searchStart)) {
88
- if (!repoRoot || countPathSegments(workspaceRoot) >= countPathSegments(repoRoot)) {
89
- return workspacePlanningHome(workspaceRoot);
90
- }
91
- }
92
52
  if (repoRoot) {
93
53
  return repoPlanningHome(repoRoot);
94
54
  }
@@ -101,8 +61,7 @@ export function getChangeDir(planningHome, changeName) {
101
61
  return FileSystemUtils.joinPath(planningHome.changesDir, changeName);
102
62
  }
103
63
  export function formatChangeLocation(planningHome, changeName) {
104
- const changeDir = getChangeDir(planningHome, changeName);
105
- const relative = relativePlanningPath(planningHome.root, changeDir);
106
- return relative.length > 0 ? relative : changeDir;
64
+ // Repo homes always nest changesDir under the root.
65
+ return path.relative(planningHome.root, getChangeDir(planningHome, changeName));
107
66
  }
108
67
  //# sourceMappingURL=planning-home.js.map
@@ -12,6 +12,7 @@ export const WORKFLOW_TO_SKILL_DIR = {
12
12
  'new': 'openspec-new-change',
13
13
  'continue': 'openspec-continue-change',
14
14
  'apply': 'openspec-apply-change',
15
+ 'update': 'openspec-update-change',
15
16
  'ff': 'openspec-ff-change',
16
17
  'sync': 'openspec-sync-specs',
17
18
  'archive': 'openspec-archive-change',
@@ -9,11 +9,11 @@ import type { Profile } from './global-config.js';
9
9
  * Core workflows included in the 'core' profile.
10
10
  * These provide the streamlined experience for new users.
11
11
  */
12
- export declare const CORE_WORKFLOWS: readonly ["propose", "explore", "apply", "sync", "archive"];
12
+ export declare const CORE_WORKFLOWS: readonly ["propose", "explore", "apply", "update", "sync", "archive"];
13
13
  /**
14
14
  * All available workflows in the system.
15
15
  */
16
- export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "sync", "archive", "bulk-archive", "verify", "onboard"];
16
+ export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "update", "ff", "sync", "archive", "bulk-archive", "verify", "onboard"];
17
17
  export type WorkflowId = (typeof ALL_WORKFLOWS)[number];
18
18
  export type CoreWorkflowId = (typeof CORE_WORKFLOWS)[number];
19
19
  /**
@@ -8,7 +8,7 @@
8
8
  * Core workflows included in the 'core' profile.
9
9
  * These provide the streamlined experience for new users.
10
10
  */
11
- export const CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'sync', 'archive'];
11
+ export const CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'update', 'sync', 'archive'];
12
12
  /**
13
13
  * All available workflows in the system.
14
14
  */
@@ -18,6 +18,7 @@ export const ALL_WORKFLOWS = [
18
18
  'new',
19
19
  'continue',
20
20
  'apply',
21
+ 'update',
21
22
  'ff',
22
23
  'sync',
23
24
  'archive',
@@ -16,8 +16,18 @@ export declare const ProjectConfigSchema: z.ZodObject<{
16
16
  schema: z.ZodString;
17
17
  context: z.ZodOptional<z.ZodString>;
18
18
  rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
19
+ store: z.ZodOptional<z.ZodString>;
19
20
  }, z.core.$strip>;
20
- export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
21
+ /** Normalized in-memory shape of a referenced store declaration. */
22
+ export interface DeclarationEntry {
23
+ id: string;
24
+ /** Clone source rendered into onboarding fixes. */
25
+ remote?: string;
26
+ }
27
+ export type ProjectConfig = z.infer<typeof ProjectConfigSchema> & {
28
+ references?: DeclarationEntry[];
29
+ };
30
+ export declare const MAX_CONTEXT_SIZE: number;
21
31
  /**
22
32
  * Read and parse openspec/config.yaml from project root.
23
33
  * Uses resilient parsing - validates each field independently using Zod safeParse.
@@ -61,4 +71,37 @@ export declare function suggestSchemas(invalidSchemaName: string, availableSchem
61
71
  name: string;
62
72
  isBuiltIn: boolean;
63
73
  }[]): string;
74
+ export interface StorePointerRead {
75
+ /** The declared store id, when present and a string. */
76
+ value?: string;
77
+ /** Set when the pointer cannot be trusted: the config file could not be
78
+ * read as YAML, or the store key is present but not a string. An empty
79
+ * or comments-only config is NOT malformed - it simply has no pointer. */
80
+ malformed?: 'unparseable' | 'non_string';
81
+ /** Absolute path of the config file actually read, or null when none exists. */
82
+ filePath: string | null;
83
+ }
84
+ /**
85
+ * Warning-silent targeted read of the `store:` pointer. Used by root
86
+ * resolution (which must not re-emit the resilient parser's field
87
+ * warnings) and by `openspec init`'s pointer guard. Unlike
88
+ * `readProjectConfig`, a malformed value is REPORTED, not dropped —
89
+ * a dropped pointer would silently flip where work lands.
90
+ */
91
+ export declare function readStorePointer(projectRoot: string): StorePointerRead;
92
+ /** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
93
+ export declare function resolveConfigFilePath(projectRoot: string): string | null;
94
+ /** Human rendering of a malformed pointer reason, shared by every surface. */
95
+ export declare function storePointerProblem(reason: 'unparseable' | 'non_string'): string;
96
+ export interface OpenSpecDirClassification {
97
+ /** True when openspec/specs or openspec/changes exists as a directory. */
98
+ hasPlanningShape: boolean;
99
+ pointer: StorePointerRead;
100
+ }
101
+ /**
102
+ * One classification for "real root vs config-only pointer dir", shared
103
+ * by root resolution and the init pointer guard so they can never
104
+ * disagree (slice 3.2).
105
+ */
106
+ export declare function classifyOpenSpecDir(projectRoot: string): OpenSpecDirClassification;
64
107
  //# sourceMappingURL=project-config.d.ts.map