@dynamicworks/br-openspec 1.3.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 (291) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +210 -0
  3. package/README.pt-BR.md +212 -0
  4. package/bin/openspec.js +3 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +484 -0
  7. package/dist/commands/change.d.ts +35 -0
  8. package/dist/commands/change.js +278 -0
  9. package/dist/commands/completion.d.ts +72 -0
  10. package/dist/commands/completion.js +258 -0
  11. package/dist/commands/config.d.ts +36 -0
  12. package/dist/commands/config.js +553 -0
  13. package/dist/commands/feedback.d.ts +9 -0
  14. package/dist/commands/feedback.js +184 -0
  15. package/dist/commands/schema.d.ts +6 -0
  16. package/dist/commands/schema.js +869 -0
  17. package/dist/commands/show.d.ts +14 -0
  18. package/dist/commands/show.js +133 -0
  19. package/dist/commands/spec.d.ts +15 -0
  20. package/dist/commands/spec.js +226 -0
  21. package/dist/commands/tools.d.ts +11 -0
  22. package/dist/commands/tools.js +252 -0
  23. package/dist/commands/validate.d.ts +24 -0
  24. package/dist/commands/validate.js +295 -0
  25. package/dist/commands/workflow/index.d.ts +17 -0
  26. package/dist/commands/workflow/index.js +12 -0
  27. package/dist/commands/workflow/instructions.d.ts +29 -0
  28. package/dist/commands/workflow/instructions.js +328 -0
  29. package/dist/commands/workflow/new-change.d.ts +11 -0
  30. package/dist/commands/workflow/new-change.js +44 -0
  31. package/dist/commands/workflow/schemas.d.ts +10 -0
  32. package/dist/commands/workflow/schemas.js +35 -0
  33. package/dist/commands/workflow/shared.d.ts +57 -0
  34. package/dist/commands/workflow/shared.js +117 -0
  35. package/dist/commands/workflow/status.d.ts +14 -0
  36. package/dist/commands/workflow/status.js +76 -0
  37. package/dist/commands/workflow/templates.d.ts +16 -0
  38. package/dist/commands/workflow/templates.js +70 -0
  39. package/dist/core/archive.d.ts +11 -0
  40. package/dist/core/archive.js +322 -0
  41. package/dist/core/artifact-graph/graph.d.ts +56 -0
  42. package/dist/core/artifact-graph/graph.js +141 -0
  43. package/dist/core/artifact-graph/index.d.ts +8 -0
  44. package/dist/core/artifact-graph/index.js +14 -0
  45. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  46. package/dist/core/artifact-graph/instruction-loader.js +217 -0
  47. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  48. package/dist/core/artifact-graph/outputs.js +39 -0
  49. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  50. package/dist/core/artifact-graph/resolver.js +258 -0
  51. package/dist/core/artifact-graph/schema.d.ts +13 -0
  52. package/dist/core/artifact-graph/schema.js +108 -0
  53. package/dist/core/artifact-graph/state.d.ts +12 -0
  54. package/dist/core/artifact-graph/state.js +31 -0
  55. package/dist/core/artifact-graph/types.d.ts +45 -0
  56. package/dist/core/artifact-graph/types.js +43 -0
  57. package/dist/core/available-tools.d.ts +17 -0
  58. package/dist/core/available-tools.js +43 -0
  59. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  60. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  61. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  62. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  63. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  64. package/dist/core/command-generation/adapters/auggie.js +27 -0
  65. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  66. package/dist/core/command-generation/adapters/bob.js +45 -0
  67. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  68. package/dist/core/command-generation/adapters/claude.js +50 -0
  69. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  70. package/dist/core/command-generation/adapters/cline.js +27 -0
  71. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  72. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  73. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  74. package/dist/core/command-generation/adapters/codex.js +39 -0
  75. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  76. package/dist/core/command-generation/adapters/continue.js +28 -0
  77. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  78. package/dist/core/command-generation/adapters/costrict.js +27 -0
  79. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  80. package/dist/core/command-generation/adapters/crush.js +30 -0
  81. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  82. package/dist/core/command-generation/adapters/cursor.js +44 -0
  83. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  84. package/dist/core/command-generation/adapters/factory.js +27 -0
  85. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  86. package/dist/core/command-generation/adapters/gemini.js +26 -0
  87. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  88. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  89. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  90. package/dist/core/command-generation/adapters/iflow.js +29 -0
  91. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  92. package/dist/core/command-generation/adapters/index.js +32 -0
  93. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  94. package/dist/core/command-generation/adapters/junie.js +26 -0
  95. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  96. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  97. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  98. package/dist/core/command-generation/adapters/kiro.js +26 -0
  99. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  100. package/dist/core/command-generation/adapters/lingma.js +30 -0
  101. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  102. package/dist/core/command-generation/adapters/opencode.js +29 -0
  103. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  104. package/dist/core/command-generation/adapters/pi.js +55 -0
  105. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  106. package/dist/core/command-generation/adapters/qoder.js +30 -0
  107. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  108. package/dist/core/command-generation/adapters/qwen.js +26 -0
  109. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  110. package/dist/core/command-generation/adapters/roocode.js +27 -0
  111. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  112. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  113. package/dist/core/command-generation/generator.d.ts +21 -0
  114. package/dist/core/command-generation/generator.js +27 -0
  115. package/dist/core/command-generation/index.d.ts +22 -0
  116. package/dist/core/command-generation/index.js +24 -0
  117. package/dist/core/command-generation/registry.d.ts +36 -0
  118. package/dist/core/command-generation/registry.js +98 -0
  119. package/dist/core/command-generation/types.d.ts +56 -0
  120. package/dist/core/command-generation/types.js +8 -0
  121. package/dist/core/completions/command-registry.d.ts +7 -0
  122. package/dist/core/completions/command-registry.js +462 -0
  123. package/dist/core/completions/completion-provider.d.ts +60 -0
  124. package/dist/core/completions/completion-provider.js +102 -0
  125. package/dist/core/completions/factory.d.ts +64 -0
  126. package/dist/core/completions/factory.js +75 -0
  127. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  128. package/dist/core/completions/generators/bash-generator.js +174 -0
  129. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  130. package/dist/core/completions/generators/fish-generator.js +157 -0
  131. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  132. package/dist/core/completions/generators/powershell-generator.js +208 -0
  133. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  134. package/dist/core/completions/generators/zsh-generator.js +250 -0
  135. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  136. package/dist/core/completions/installers/bash-installer.js +319 -0
  137. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  138. package/dist/core/completions/installers/fish-installer.js +143 -0
  139. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  140. package/dist/core/completions/installers/powershell-installer.js +400 -0
  141. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  142. package/dist/core/completions/installers/zsh-installer.js +450 -0
  143. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  144. package/dist/core/completions/templates/bash-templates.js +24 -0
  145. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  146. package/dist/core/completions/templates/fish-templates.js +39 -0
  147. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  148. package/dist/core/completions/templates/powershell-templates.js +25 -0
  149. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  150. package/dist/core/completions/templates/zsh-templates.js +36 -0
  151. package/dist/core/completions/types.d.ts +79 -0
  152. package/dist/core/completions/types.js +2 -0
  153. package/dist/core/config-prompts.d.ts +9 -0
  154. package/dist/core/config-prompts.js +34 -0
  155. package/dist/core/config-schema.d.ts +86 -0
  156. package/dist/core/config-schema.js +213 -0
  157. package/dist/core/config.d.ts +18 -0
  158. package/dist/core/config.js +38 -0
  159. package/dist/core/converters/json-converter.d.ts +6 -0
  160. package/dist/core/converters/json-converter.js +51 -0
  161. package/dist/core/global-config.d.ts +44 -0
  162. package/dist/core/global-config.js +125 -0
  163. package/dist/core/index.d.ts +2 -0
  164. package/dist/core/index.js +3 -0
  165. package/dist/core/init.d.ts +37 -0
  166. package/dist/core/init.js +549 -0
  167. package/dist/core/is-project-initialized.d.ts +12 -0
  168. package/dist/core/is-project-initialized.js +18 -0
  169. package/dist/core/legacy-cleanup.d.ts +162 -0
  170. package/dist/core/legacy-cleanup.js +515 -0
  171. package/dist/core/list.d.ts +9 -0
  172. package/dist/core/list.js +172 -0
  173. package/dist/core/migration.d.ts +23 -0
  174. package/dist/core/migration.js +109 -0
  175. package/dist/core/parsers/change-parser.d.ts +13 -0
  176. package/dist/core/parsers/change-parser.js +197 -0
  177. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  178. package/dist/core/parsers/markdown-parser.js +228 -0
  179. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  180. package/dist/core/parsers/requirement-blocks.js +201 -0
  181. package/dist/core/parsers/spec-structure.d.ts +9 -0
  182. package/dist/core/parsers/spec-structure.js +88 -0
  183. package/dist/core/profile-sync-drift.d.ts +38 -0
  184. package/dist/core/profile-sync-drift.js +200 -0
  185. package/dist/core/profiles.d.ts +26 -0
  186. package/dist/core/profiles.js +40 -0
  187. package/dist/core/project-config.d.ts +64 -0
  188. package/dist/core/project-config.js +224 -0
  189. package/dist/core/schemas/base.schema.d.ts +13 -0
  190. package/dist/core/schemas/base.schema.js +13 -0
  191. package/dist/core/schemas/change.schema.d.ts +73 -0
  192. package/dist/core/schemas/change.schema.js +31 -0
  193. package/dist/core/schemas/index.d.ts +4 -0
  194. package/dist/core/schemas/index.js +4 -0
  195. package/dist/core/schemas/spec.schema.d.ts +18 -0
  196. package/dist/core/schemas/spec.schema.js +15 -0
  197. package/dist/core/shared/index.d.ts +8 -0
  198. package/dist/core/shared/index.js +8 -0
  199. package/dist/core/shared/skill-generation.d.ts +49 -0
  200. package/dist/core/shared/skill-generation.js +96 -0
  201. package/dist/core/shared/tool-detection.d.ts +71 -0
  202. package/dist/core/shared/tool-detection.js +158 -0
  203. package/dist/core/specs-apply.d.ts +73 -0
  204. package/dist/core/specs-apply.js +393 -0
  205. package/dist/core/styles/palette.d.ts +7 -0
  206. package/dist/core/styles/palette.js +8 -0
  207. package/dist/core/templates/index.d.ts +8 -0
  208. package/dist/core/templates/index.js +9 -0
  209. package/dist/core/templates/skill-templates.d.ts +20 -0
  210. package/dist/core/templates/skill-templates.js +19 -0
  211. package/dist/core/templates/types.d.ts +19 -0
  212. package/dist/core/templates/types.js +5 -0
  213. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  214. package/dist/core/templates/workflows/apply-change.js +308 -0
  215. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  216. package/dist/core/templates/workflows/archive-change.js +271 -0
  217. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  218. package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
  219. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  220. package/dist/core/templates/workflows/continue-change.js +232 -0
  221. package/dist/core/templates/workflows/explore.d.ts +10 -0
  222. package/dist/core/templates/workflows/explore.js +463 -0
  223. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  224. package/dist/core/templates/workflows/feedback.js +108 -0
  225. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  226. package/dist/core/templates/workflows/ff-change.js +198 -0
  227. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  228. package/dist/core/templates/workflows/new-change.js +21 -0
  229. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  230. package/dist/core/templates/workflows/onboard.js +21 -0
  231. package/dist/core/templates/workflows/propose.d.ts +10 -0
  232. package/dist/core/templates/workflows/propose.js +216 -0
  233. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  234. package/dist/core/templates/workflows/sync-specs.js +272 -0
  235. package/dist/core/templates/workflows/upstream-sync.d.ts +10 -0
  236. package/dist/core/templates/workflows/upstream-sync.js +116 -0
  237. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  238. package/dist/core/templates/workflows/verify-change.js +21 -0
  239. package/dist/core/tools-manager.d.ts +56 -0
  240. package/dist/core/tools-manager.js +215 -0
  241. package/dist/core/update.d.ts +77 -0
  242. package/dist/core/update.js +538 -0
  243. package/dist/core/validation/constants.d.ts +34 -0
  244. package/dist/core/validation/constants.js +40 -0
  245. package/dist/core/validation/types.d.ts +18 -0
  246. package/dist/core/validation/types.js +2 -0
  247. package/dist/core/validation/validator.d.ts +33 -0
  248. package/dist/core/validation/validator.js +419 -0
  249. package/dist/core/view.d.ts +8 -0
  250. package/dist/core/view.js +169 -0
  251. package/dist/index.d.ts +3 -0
  252. package/dist/index.js +3 -0
  253. package/dist/messages/index.d.ts +867 -0
  254. package/dist/messages/index.js +1960 -0
  255. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  256. package/dist/prompts/searchable-multi-select.js +160 -0
  257. package/dist/telemetry/config.d.ts +38 -0
  258. package/dist/telemetry/config.js +136 -0
  259. package/dist/telemetry/index.d.ts +31 -0
  260. package/dist/telemetry/index.js +165 -0
  261. package/dist/ui/ascii-patterns.d.ts +16 -0
  262. package/dist/ui/ascii-patterns.js +133 -0
  263. package/dist/ui/welcome-screen.d.ts +10 -0
  264. package/dist/ui/welcome-screen.js +147 -0
  265. package/dist/utils/change-metadata.d.ts +51 -0
  266. package/dist/utils/change-metadata.js +147 -0
  267. package/dist/utils/change-utils.d.ts +62 -0
  268. package/dist/utils/change-utils.js +121 -0
  269. package/dist/utils/command-references.d.ts +18 -0
  270. package/dist/utils/command-references.js +20 -0
  271. package/dist/utils/file-system.d.ts +41 -0
  272. package/dist/utils/file-system.js +302 -0
  273. package/dist/utils/index.d.ts +6 -0
  274. package/dist/utils/index.js +9 -0
  275. package/dist/utils/interactive.d.ts +18 -0
  276. package/dist/utils/interactive.js +21 -0
  277. package/dist/utils/item-discovery.d.ts +4 -0
  278. package/dist/utils/item-discovery.js +72 -0
  279. package/dist/utils/match.d.ts +3 -0
  280. package/dist/utils/match.js +22 -0
  281. package/dist/utils/shell-detection.d.ts +20 -0
  282. package/dist/utils/shell-detection.js +41 -0
  283. package/dist/utils/task-progress.d.ts +8 -0
  284. package/dist/utils/task-progress.js +37 -0
  285. package/package.json +84 -0
  286. package/schemas/spec-driven/schema.yaml +153 -0
  287. package/schemas/spec-driven/templates/design.md +19 -0
  288. package/schemas/spec-driven/templates/proposal.md +23 -0
  289. package/schemas/spec-driven/templates/spec.md +8 -0
  290. package/schemas/spec-driven/templates/tasks.md +9 -0
  291. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Command Reference Utilities
3
+ *
4
+ * Utilities for transforming command references to tool-specific formats.
5
+ */
6
+ /**
7
+ * Transforms colon-based command references to hyphen-based format.
8
+ * Converts `/opsx:` patterns to `/opsx-` for tools that use hyphen syntax.
9
+ *
10
+ * @param text - The text containing command references
11
+ * @returns Text with command references transformed to hyphen format
12
+ *
13
+ * @example
14
+ * transformToHyphenCommands('/opsx:new') // returns '/opsx-new'
15
+ * transformToHyphenCommands('Use /opsx:apply to implement') // returns 'Use /opsx-apply to implement'
16
+ */
17
+ export declare function transformToHyphenCommands(text: string): string;
18
+ //# sourceMappingURL=command-references.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Command Reference Utilities
3
+ *
4
+ * Utilities for transforming command references to tool-specific formats.
5
+ */
6
+ /**
7
+ * Transforms colon-based command references to hyphen-based format.
8
+ * Converts `/opsx:` patterns to `/opsx-` for tools that use hyphen syntax.
9
+ *
10
+ * @param text - The text containing command references
11
+ * @returns Text with command references transformed to hyphen format
12
+ *
13
+ * @example
14
+ * transformToHyphenCommands('/opsx:new') // returns '/opsx-new'
15
+ * transformToHyphenCommands('Use /opsx:apply to implement') // returns 'Use /opsx-apply to implement'
16
+ */
17
+ export function transformToHyphenCommands(text) {
18
+ return text.replace(/\/opsx:/g, '/opsx-');
19
+ }
20
+ //# sourceMappingURL=command-references.js.map
@@ -0,0 +1,41 @@
1
+ export declare class FileSystemUtils {
2
+ /**
3
+ * Converts a path to use forward slashes (POSIX style).
4
+ * Essential for cross-platform compatibility with glob libraries like fast-glob.
5
+ */
6
+ static toPosixPath(p: string): string;
7
+ /**
8
+ * Returns a canonical absolute path when the target exists.
9
+ * Falls back to path.resolve() so callers can still produce a stable absolute path.
10
+ */
11
+ static canonicalizeExistingPath(targetPath: string): string;
12
+ private static isWindowsBasePath;
13
+ private static normalizeSegments;
14
+ static joinPath(basePath: string, ...segments: string[]): string;
15
+ static createDirectory(dirPath: string): Promise<void>;
16
+ static fileExists(filePath: string): Promise<boolean>;
17
+ /**
18
+ * Finds the first existing parent directory by walking up the directory tree.
19
+ * @param dirPath Starting directory path
20
+ * @returns The first existing directory path, or null if root is reached without finding one
21
+ */
22
+ private static findFirstExistingDirectory;
23
+ static canWriteFile(filePath: string): Promise<boolean>;
24
+ static directoryExists(dirPath: string): Promise<boolean>;
25
+ static writeFile(filePath: string, content: string): Promise<void>;
26
+ static readFile(filePath: string): Promise<string>;
27
+ static updateFileWithMarkers(filePath: string, content: string, startMarker: string, endMarker: string): Promise<void>;
28
+ static ensureWritePermissions(dirPath: string): Promise<boolean>;
29
+ }
30
+ /**
31
+ * Removes a marker block from file content.
32
+ * Only removes markers that are on their own lines (ignores inline mentions).
33
+ * Cleans up double blank lines that may result from removal.
34
+ *
35
+ * @param content - File content with markers
36
+ * @param startMarker - The start marker string
37
+ * @param endMarker - The end marker string
38
+ * @returns Content with marker block removed, or original content if markers not found/invalid
39
+ */
40
+ export declare function removeMarkerBlock(content: string, startMarker: string, endMarker: string): string;
41
+ //# sourceMappingURL=file-system.d.ts.map
@@ -0,0 +1,302 @@
1
+ import * as nodeFs from 'fs';
2
+ import path from 'path';
3
+ import { FILE_SYSTEM_MESSAGES } from '../messages/index.js';
4
+ const fs = nodeFs.promises;
5
+ const { constants: fsConstants } = nodeFs;
6
+ function isMarkerOnOwnLine(content, markerIndex, markerLength) {
7
+ let leftIndex = markerIndex - 1;
8
+ while (leftIndex >= 0 && content[leftIndex] !== '\n') {
9
+ const char = content[leftIndex];
10
+ if (char !== ' ' && char !== '\t' && char !== '\r') {
11
+ return false;
12
+ }
13
+ leftIndex--;
14
+ }
15
+ let rightIndex = markerIndex + markerLength;
16
+ while (rightIndex < content.length && content[rightIndex] !== '\n') {
17
+ const char = content[rightIndex];
18
+ if (char !== ' ' && char !== '\t' && char !== '\r') {
19
+ return false;
20
+ }
21
+ rightIndex++;
22
+ }
23
+ return true;
24
+ }
25
+ function findMarkerIndex(content, marker, fromIndex = 0) {
26
+ let currentIndex = content.indexOf(marker, fromIndex);
27
+ while (currentIndex !== -1) {
28
+ if (isMarkerOnOwnLine(content, currentIndex, marker.length)) {
29
+ return currentIndex;
30
+ }
31
+ currentIndex = content.indexOf(marker, currentIndex + marker.length);
32
+ }
33
+ return -1;
34
+ }
35
+ export class FileSystemUtils {
36
+ /**
37
+ * Converts a path to use forward slashes (POSIX style).
38
+ * Essential for cross-platform compatibility with glob libraries like fast-glob.
39
+ */
40
+ static toPosixPath(p) {
41
+ return p.replace(/\\/g, '/');
42
+ }
43
+ /**
44
+ * Returns a canonical absolute path when the target exists.
45
+ * Falls back to path.resolve() so callers can still produce a stable absolute path.
46
+ */
47
+ static canonicalizeExistingPath(targetPath) {
48
+ try {
49
+ // Prefer the native resolver so Windows short-path aliases are expanded.
50
+ return nodeFs.realpathSync.native(targetPath);
51
+ }
52
+ catch {
53
+ try {
54
+ return nodeFs.realpathSync(targetPath);
55
+ }
56
+ catch {
57
+ return path.resolve(targetPath);
58
+ }
59
+ }
60
+ }
61
+ static isWindowsBasePath(basePath) {
62
+ return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\');
63
+ }
64
+ static normalizeSegments(segments) {
65
+ return segments
66
+ .flatMap((segment) => segment.split(/[\\/]+/u))
67
+ .filter((part) => part.length > 0);
68
+ }
69
+ static joinPath(basePath, ...segments) {
70
+ const normalizedSegments = this.normalizeSegments(segments);
71
+ if (this.isWindowsBasePath(basePath)) {
72
+ const normalizedBasePath = path.win32.normalize(basePath);
73
+ return normalizedSegments.length
74
+ ? path.win32.join(normalizedBasePath, ...normalizedSegments)
75
+ : normalizedBasePath;
76
+ }
77
+ const posixBasePath = basePath.replace(/\\/g, '/');
78
+ return normalizedSegments.length
79
+ ? path.posix.join(posixBasePath, ...normalizedSegments)
80
+ : path.posix.normalize(posixBasePath);
81
+ }
82
+ static async createDirectory(dirPath) {
83
+ await fs.mkdir(dirPath, { recursive: true });
84
+ }
85
+ static async fileExists(filePath) {
86
+ try {
87
+ await fs.access(filePath);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ if (error.code !== 'ENOENT') {
92
+ console.debug(FILE_SYSTEM_MESSAGES.unableToCheckFileExists(filePath, error.message));
93
+ }
94
+ return false;
95
+ }
96
+ }
97
+ /**
98
+ * Finds the first existing parent directory by walking up the directory tree.
99
+ * @param dirPath Starting directory path
100
+ * @returns The first existing directory path, or null if root is reached without finding one
101
+ */
102
+ static async findFirstExistingDirectory(dirPath) {
103
+ let currentDir = dirPath;
104
+ while (true) {
105
+ try {
106
+ const stats = await fs.stat(currentDir);
107
+ if (stats.isDirectory()) {
108
+ return currentDir;
109
+ }
110
+ // Path component exists but is not a directory (edge case)
111
+ console.debug(FILE_SYSTEM_MESSAGES.pathComponentNotDir(currentDir));
112
+ return null;
113
+ }
114
+ catch (error) {
115
+ if (error.code === 'ENOENT') {
116
+ // Directory doesn't exist, move up one level
117
+ const parentDir = path.dirname(currentDir);
118
+ if (parentDir === currentDir) {
119
+ // Reached filesystem root without finding existing directory
120
+ return null;
121
+ }
122
+ currentDir = parentDir;
123
+ }
124
+ else {
125
+ // Unexpected error (permissions, I/O error, etc.)
126
+ console.debug(FILE_SYSTEM_MESSAGES.errorCheckingDir(currentDir, error.message));
127
+ return null;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ static async canWriteFile(filePath) {
133
+ try {
134
+ const stats = await fs.stat(filePath);
135
+ if (!stats.isFile()) {
136
+ return true;
137
+ }
138
+ // On Windows, stats.mode doesn't reliably indicate write permissions.
139
+ // Use fs.access with W_OK to check actual write permissions cross-platform.
140
+ try {
141
+ await fs.access(filePath, fsConstants.W_OK);
142
+ return true;
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ }
148
+ catch (error) {
149
+ if (error.code === 'ENOENT') {
150
+ // File doesn't exist - find first existing parent directory and check its permissions
151
+ const parentDir = path.dirname(filePath);
152
+ const existingDir = await this.findFirstExistingDirectory(parentDir);
153
+ if (existingDir === null) {
154
+ // No existing parent directory found (edge case)
155
+ return false;
156
+ }
157
+ // Check if the existing parent directory is writable
158
+ try {
159
+ await fs.access(existingDir, fsConstants.W_OK);
160
+ return true;
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
166
+ console.debug(FILE_SYSTEM_MESSAGES.unableToDetermineWritePermissions(filePath, error.message));
167
+ return false;
168
+ }
169
+ }
170
+ static async directoryExists(dirPath) {
171
+ try {
172
+ const stats = await fs.stat(dirPath);
173
+ return stats.isDirectory();
174
+ }
175
+ catch (error) {
176
+ if (error.code !== 'ENOENT') {
177
+ console.debug(FILE_SYSTEM_MESSAGES.unableToCheckDirExists(dirPath, error.message));
178
+ }
179
+ return false;
180
+ }
181
+ }
182
+ static async writeFile(filePath, content) {
183
+ const dir = path.dirname(filePath);
184
+ await this.createDirectory(dir);
185
+ await fs.writeFile(filePath, content, 'utf-8');
186
+ }
187
+ static async readFile(filePath) {
188
+ return await fs.readFile(filePath, 'utf-8');
189
+ }
190
+ static async updateFileWithMarkers(filePath, content, startMarker, endMarker) {
191
+ let existingContent = '';
192
+ if (await this.fileExists(filePath)) {
193
+ existingContent = await this.readFile(filePath);
194
+ const startIndex = findMarkerIndex(existingContent, startMarker);
195
+ const endIndex = startIndex !== -1
196
+ ? findMarkerIndex(existingContent, endMarker, startIndex + startMarker.length)
197
+ : findMarkerIndex(existingContent, endMarker);
198
+ if (startIndex !== -1 && endIndex !== -1) {
199
+ if (endIndex < startIndex) {
200
+ throw new Error(FILE_SYSTEM_MESSAGES.endMarkerBeforeStart(filePath));
201
+ }
202
+ const before = existingContent.substring(0, startIndex);
203
+ const after = existingContent.substring(endIndex + endMarker.length);
204
+ existingContent = before + startMarker + '\n' + content + '\n' + endMarker + after;
205
+ }
206
+ else if (startIndex === -1 && endIndex === -1) {
207
+ existingContent = startMarker + '\n' + content + '\n' + endMarker + '\n\n' + existingContent;
208
+ }
209
+ else {
210
+ throw new Error(FILE_SYSTEM_MESSAGES.invalidMarkerState(filePath, startIndex !== -1, endIndex !== -1));
211
+ }
212
+ }
213
+ else {
214
+ existingContent = startMarker + '\n' + content + '\n' + endMarker;
215
+ }
216
+ await this.writeFile(filePath, existingContent);
217
+ }
218
+ static async ensureWritePermissions(dirPath) {
219
+ try {
220
+ // If directory doesn't exist, check parent directory permissions
221
+ if (!await this.directoryExists(dirPath)) {
222
+ const parentDir = path.dirname(dirPath);
223
+ if (!await this.directoryExists(parentDir)) {
224
+ await this.createDirectory(parentDir);
225
+ }
226
+ return await this.ensureWritePermissions(parentDir);
227
+ }
228
+ const testFile = path.join(dirPath, '.openspec-test-' + Date.now() + '-' + Math.random().toString(36).slice(2));
229
+ await fs.writeFile(testFile, '');
230
+ // On Windows, file may be temporarily locked by antivirus or indexing services.
231
+ // Retry unlink with a small delay if it fails.
232
+ const maxRetries = 3;
233
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
234
+ try {
235
+ await fs.unlink(testFile);
236
+ break;
237
+ }
238
+ catch (unlinkError) {
239
+ if (attempt === maxRetries - 1) {
240
+ // Last attempt failed, but we successfully wrote the file, so permissions are OK
241
+ // Just log and continue - the temp file will be cleaned up eventually
242
+ console.debug(FILE_SYSTEM_MESSAGES.couldNotCleanUpTestFile(testFile, unlinkError.message));
243
+ }
244
+ else {
245
+ // Wait briefly before retrying (Windows file lock release)
246
+ await new Promise((resolve) => setTimeout(resolve, 50));
247
+ }
248
+ }
249
+ }
250
+ return true;
251
+ }
252
+ catch (error) {
253
+ console.debug(FILE_SYSTEM_MESSAGES.insufficientPermissions(dirPath, error.message));
254
+ return false;
255
+ }
256
+ }
257
+ }
258
+ /**
259
+ * Removes a marker block from file content.
260
+ * Only removes markers that are on their own lines (ignores inline mentions).
261
+ * Cleans up double blank lines that may result from removal.
262
+ *
263
+ * @param content - File content with markers
264
+ * @param startMarker - The start marker string
265
+ * @param endMarker - The end marker string
266
+ * @returns Content with marker block removed, or original content if markers not found/invalid
267
+ */
268
+ export function removeMarkerBlock(content, startMarker, endMarker) {
269
+ const startIndex = findMarkerIndex(content, startMarker);
270
+ const endIndex = startIndex !== -1
271
+ ? findMarkerIndex(content, endMarker, startIndex + startMarker.length)
272
+ : findMarkerIndex(content, endMarker);
273
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
274
+ return content;
275
+ }
276
+ // Find the start of the line containing the start marker
277
+ let lineStart = startIndex;
278
+ while (lineStart > 0 && content[lineStart - 1] !== '\n') {
279
+ lineStart--;
280
+ }
281
+ // Find the end of the line containing the end marker
282
+ let lineEnd = endIndex + endMarker.length;
283
+ while (lineEnd < content.length && content[lineEnd] !== '\n') {
284
+ lineEnd++;
285
+ }
286
+ // Include the trailing newline if present
287
+ if (lineEnd < content.length && content[lineEnd] === '\n') {
288
+ lineEnd++;
289
+ }
290
+ const before = content.substring(0, lineStart);
291
+ const after = content.substring(lineEnd);
292
+ // Clean up double blank lines (handle both Unix \n and Windows \r\n)
293
+ let result = before + after;
294
+ result = result.replace(/(\r?\n){3,}/g, '\n\n');
295
+ // Trim trailing whitespace but preserve leading whitespace and original newline style
296
+ if (result.trimEnd() === '') {
297
+ return '';
298
+ }
299
+ const newline = content.includes('\r\n') ? '\r\n' : '\n';
300
+ return result.trimEnd() + newline;
301
+ }
302
+ //# sourceMappingURL=file-system.js.map
@@ -0,0 +1,6 @@
1
+ export { validateChangeName, createChange } from './change-utils.js';
2
+ export type { ValidationResult, CreateChangeOptions } from './change-utils.js';
3
+ export { readChangeMetadata, writeChangeMetadata, resolveSchemaForChange, validateSchemaName, ChangeMetadataError, } from './change-metadata.js';
4
+ export { FileSystemUtils, removeMarkerBlock } from './file-system.js';
5
+ export { transformToHyphenCommands } from './command-references.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ // Shared utilities
2
+ export { validateChangeName, createChange } from './change-utils.js';
3
+ // Change metadata utilities
4
+ export { readChangeMetadata, writeChangeMetadata, resolveSchemaForChange, validateSchemaName, ChangeMetadataError, } from './change-metadata.js';
5
+ // File system utilities
6
+ export { FileSystemUtils, removeMarkerBlock } from './file-system.js';
7
+ // Command reference utilities
8
+ export { transformToHyphenCommands } from './command-references.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ export type InteractiveOptions = {
2
+ /**
3
+ * Explicit "disable prompts" flag passed by internal callers.
4
+ */
5
+ noInteractive?: boolean;
6
+ /**
7
+ * Commander-style negated option: `--no-interactive` sets this to false.
8
+ */
9
+ interactive?: boolean;
10
+ };
11
+ /**
12
+ * Resolves whether non-interactive mode is requested.
13
+ * Handles both explicit `noInteractive: true` and Commander.js style `interactive: false`.
14
+ * Use this helper instead of manually checking options.noInteractive to avoid bugs.
15
+ */
16
+ export declare function resolveNoInteractive(value?: boolean | InteractiveOptions): boolean;
17
+ export declare function isInteractive(value?: boolean | InteractiveOptions): boolean;
18
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Resolves whether non-interactive mode is requested.
3
+ * Handles both explicit `noInteractive: true` and Commander.js style `interactive: false`.
4
+ * Use this helper instead of manually checking options.noInteractive to avoid bugs.
5
+ */
6
+ export function resolveNoInteractive(value) {
7
+ if (typeof value === 'boolean')
8
+ return value;
9
+ return value?.noInteractive === true || value?.interactive === false;
10
+ }
11
+ export function isInteractive(value) {
12
+ if (resolveNoInteractive(value))
13
+ return false;
14
+ if (process.env.OPEN_SPEC_INTERACTIVE === '0')
15
+ return false;
16
+ // Respect the standard CI environment variable (set by GitHub Actions, GitLab CI, Travis, etc.)
17
+ if ('CI' in process.env)
18
+ return false;
19
+ return !!process.stdin.isTTY;
20
+ }
21
+ //# sourceMappingURL=interactive.js.map
@@ -0,0 +1,4 @@
1
+ export declare function getActiveChangeIds(root?: string): Promise<string[]>;
2
+ export declare function getSpecIds(root?: string): Promise<string[]>;
3
+ export declare function getArchivedChangeIds(root?: string): Promise<string[]>;
4
+ //# sourceMappingURL=item-discovery.d.ts.map
@@ -0,0 +1,72 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ export async function getActiveChangeIds(root = process.cwd()) {
4
+ const changesPath = path.join(root, 'openspec', 'changes');
5
+ try {
6
+ const entries = await fs.readdir(changesPath, { withFileTypes: true });
7
+ const result = [];
8
+ for (const entry of entries) {
9
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'archive')
10
+ continue;
11
+ const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
12
+ try {
13
+ await fs.access(proposalPath);
14
+ result.push(entry.name);
15
+ }
16
+ catch {
17
+ // skip directories without proposal.md
18
+ }
19
+ }
20
+ return result.sort();
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ }
26
+ export async function getSpecIds(root = process.cwd()) {
27
+ const specsPath = path.join(root, 'openspec', 'specs');
28
+ const result = [];
29
+ try {
30
+ const entries = await fs.readdir(specsPath, { withFileTypes: true });
31
+ for (const entry of entries) {
32
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
33
+ continue;
34
+ const specFile = path.join(specsPath, entry.name, 'spec.md');
35
+ try {
36
+ await fs.access(specFile);
37
+ result.push(entry.name);
38
+ }
39
+ catch {
40
+ // ignore
41
+ }
42
+ }
43
+ }
44
+ catch {
45
+ // ignore
46
+ }
47
+ return result.sort();
48
+ }
49
+ export async function getArchivedChangeIds(root = process.cwd()) {
50
+ const archivePath = path.join(root, 'openspec', 'changes', 'archive');
51
+ try {
52
+ const entries = await fs.readdir(archivePath, { withFileTypes: true });
53
+ const result = [];
54
+ for (const entry of entries) {
55
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
56
+ continue;
57
+ const proposalPath = path.join(archivePath, entry.name, 'proposal.md');
58
+ try {
59
+ await fs.access(proposalPath);
60
+ result.push(entry.name);
61
+ }
62
+ catch {
63
+ // skip directories without proposal.md
64
+ }
65
+ }
66
+ return result.sort();
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ //# sourceMappingURL=item-discovery.js.map
@@ -0,0 +1,3 @@
1
+ export declare function nearestMatches(input: string, candidates: string[], max?: number): string[];
2
+ export declare function levenshtein(a: string, b: string): number;
3
+ //# sourceMappingURL=match.d.ts.map
@@ -0,0 +1,22 @@
1
+ export function nearestMatches(input, candidates, max = 5) {
2
+ const scored = candidates.map(candidate => ({ candidate, distance: levenshtein(input, candidate) }));
3
+ scored.sort((a, b) => a.distance - b.distance);
4
+ return scored.slice(0, max).map(s => s.candidate);
5
+ }
6
+ export function levenshtein(a, b) {
7
+ const m = a.length;
8
+ const n = b.length;
9
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
10
+ for (let i = 0; i <= m; i++)
11
+ dp[i][0] = i;
12
+ for (let j = 0; j <= n; j++)
13
+ dp[0][j] = j;
14
+ for (let i = 1; i <= m; i++) {
15
+ for (let j = 1; j <= n; j++) {
16
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
17
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
18
+ }
19
+ }
20
+ return dp[m][n];
21
+ }
22
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Supported shell types for completion generation
3
+ */
4
+ export type SupportedShell = 'zsh' | 'bash' | 'fish' | 'powershell';
5
+ /**
6
+ * Result of shell detection
7
+ */
8
+ export interface ShellDetectionResult {
9
+ /** The detected shell if supported, otherwise undefined */
10
+ shell: SupportedShell | undefined;
11
+ /** The raw shell name detected (even if unsupported), or undefined if nothing detected */
12
+ detected: string | undefined;
13
+ }
14
+ /**
15
+ * Detects the current user's shell based on environment variables
16
+ *
17
+ * @returns Detection result with supported shell and raw detected name
18
+ */
19
+ export declare function detectShell(): ShellDetectionResult;
20
+ //# sourceMappingURL=shell-detection.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Detects the current user's shell based on environment variables
3
+ *
4
+ * @returns Detection result with supported shell and raw detected name
5
+ */
6
+ export function detectShell() {
7
+ // Try SHELL environment variable first (Unix-like systems)
8
+ const shellPath = process.env.SHELL;
9
+ if (shellPath) {
10
+ const shellName = shellPath.toLowerCase();
11
+ if (shellName.includes('zsh')) {
12
+ return { shell: 'zsh', detected: 'zsh' };
13
+ }
14
+ if (shellName.includes('bash')) {
15
+ return { shell: 'bash', detected: 'bash' };
16
+ }
17
+ if (shellName.includes('fish')) {
18
+ return { shell: 'fish', detected: 'fish' };
19
+ }
20
+ // Shell detected but not supported
21
+ // Extract shell name from path (e.g., /bin/tcsh -> tcsh)
22
+ const match = shellPath.match(/\/([^/]+)$/);
23
+ const detectedName = match ? match[1] : shellPath;
24
+ return { shell: undefined, detected: detectedName };
25
+ }
26
+ // Check for PowerShell on Windows
27
+ // PSModulePath is a reliable PowerShell-specific environment variable
28
+ if (process.env.PSModulePath || process.platform === 'win32') {
29
+ const comspec = process.env.COMSPEC?.toLowerCase();
30
+ // If PSModulePath exists, we're definitely in PowerShell
31
+ if (process.env.PSModulePath) {
32
+ return { shell: 'powershell', detected: 'powershell' };
33
+ }
34
+ // On Windows without PSModulePath, we might be in cmd.exe
35
+ if (comspec?.includes('cmd.exe')) {
36
+ return { shell: undefined, detected: 'cmd.exe' };
37
+ }
38
+ }
39
+ return { shell: undefined, detected: undefined };
40
+ }
41
+ //# sourceMappingURL=shell-detection.js.map
@@ -0,0 +1,8 @@
1
+ export interface TaskProgress {
2
+ total: number;
3
+ completed: number;
4
+ }
5
+ export declare function countTasksFromContent(content: string): TaskProgress;
6
+ export declare function getTaskProgressForChange(changesDir: string, changeName: string): Promise<TaskProgress>;
7
+ export declare function formatTaskStatus(progress: TaskProgress): string;
8
+ //# sourceMappingURL=task-progress.d.ts.map
@@ -0,0 +1,37 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { TASK_PROGRESS_MESSAGES } from '../messages/index.js';
4
+ const TASK_PATTERN = /^[-*]\s+\[[\sx]\]/i;
5
+ const COMPLETED_TASK_PATTERN = /^[-*]\s+\[x\]/i;
6
+ export function countTasksFromContent(content) {
7
+ const lines = content.split('\n');
8
+ let total = 0;
9
+ let completed = 0;
10
+ for (const line of lines) {
11
+ if (line.match(TASK_PATTERN)) {
12
+ total++;
13
+ if (line.match(COMPLETED_TASK_PATTERN)) {
14
+ completed++;
15
+ }
16
+ }
17
+ }
18
+ return { total, completed };
19
+ }
20
+ export async function getTaskProgressForChange(changesDir, changeName) {
21
+ const tasksPath = path.join(changesDir, changeName, 'tasks.md');
22
+ try {
23
+ const content = await fs.readFile(tasksPath, 'utf-8');
24
+ return countTasksFromContent(content);
25
+ }
26
+ catch {
27
+ return { total: 0, completed: 0 };
28
+ }
29
+ }
30
+ export function formatTaskStatus(progress) {
31
+ if (progress.total === 0)
32
+ return TASK_PROGRESS_MESSAGES.noTasks;
33
+ if (progress.completed === progress.total)
34
+ return TASK_PROGRESS_MESSAGES.complete;
35
+ return TASK_PROGRESS_MESSAGES.tasksCount(progress.completed, progress.total);
36
+ }
37
+ //# sourceMappingURL=task-progress.js.map