@codewalla_india/openspec 1.3.1 → 1.3.2

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 (333) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +272 -191
  3. package/bin/openspec.js +5 -5
  4. package/dist/cli/index.js +99 -62
  5. package/dist/commands/change.d.ts +0 -2
  6. package/dist/commands/change.js +107 -86
  7. package/dist/commands/config.js +14 -16
  8. package/dist/commands/doctor.js +9 -4
  9. package/dist/commands/feedback.js +79 -30
  10. package/dist/commands/modify.d.ts +26 -0
  11. package/dist/commands/modify.js +147 -0
  12. package/dist/commands/schema.js +170 -91
  13. package/dist/commands/show.js +5 -2
  14. package/dist/commands/spec.js +49 -29
  15. package/dist/commands/store.js +15 -21
  16. package/dist/commands/validate.d.ts +8 -0
  17. package/dist/commands/validate.js +43 -20
  18. package/dist/commands/workflow/index.d.ts +2 -2
  19. package/dist/commands/workflow/index.js +1 -1
  20. package/dist/commands/workflow/instructions.d.ts +6 -26
  21. package/dist/commands/workflow/instructions.js +183 -376
  22. package/dist/commands/workflow/new-change.d.ts +0 -4
  23. package/dist/commands/workflow/new-change.js +10 -28
  24. package/dist/commands/workflow/shared.d.ts +34 -19
  25. package/dist/commands/workflow/shared.js +34 -4
  26. package/dist/commands/workflow/status.js +10 -32
  27. package/dist/commands/workflow/templates.js +15 -5
  28. package/dist/commands/workset.d.ts +2 -2
  29. package/dist/commands/workset.js +19 -22
  30. package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
  31. package/dist/comprehension-quiz/fingerprint.js +84 -0
  32. package/dist/comprehension-quiz/index.d.ts +42 -0
  33. package/dist/comprehension-quiz/index.js +47 -0
  34. package/dist/comprehension-quiz/pass-record.d.ts +44 -0
  35. package/dist/comprehension-quiz/pass-record.js +98 -0
  36. package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +53 -0
  38. package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
  39. package/dist/comprehension-quiz/providers/factory.js +174 -0
  40. package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
  41. package/dist/comprehension-quiz/providers/interface.js +63 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +34 -0
  43. package/dist/comprehension-quiz/providers/local.js +53 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +53 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
  47. package/dist/comprehension-quiz/providers/openai.js +53 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +69 -0
  49. package/dist/comprehension-quiz/question-generator.js +141 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +165 -0
  52. package/dist/comprehension-quiz/types.d.ts +115 -0
  53. package/dist/comprehension-quiz/types.js +11 -0
  54. package/dist/core/archive.d.ts +14 -1
  55. package/dist/core/archive.js +1350 -190
  56. package/dist/core/artifact-graph/graph.d.ts +28 -9
  57. package/dist/core/artifact-graph/graph.js +94 -43
  58. package/dist/core/artifact-graph/index.d.ts +1 -1
  59. package/dist/core/artifact-graph/index.js +1 -1
  60. package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
  61. package/dist/core/artifact-graph/instruction-loader.js +70 -12
  62. package/dist/core/artifact-graph/outputs.d.ts +1 -0
  63. package/dist/core/artifact-graph/outputs.js +67 -5
  64. package/dist/core/artifact-graph/resolver.d.ts +14 -0
  65. package/dist/core/artifact-graph/resolver.js +69 -15
  66. package/dist/core/artifact-graph/types.js +21 -3
  67. package/dist/core/available-tools.d.ts +2 -2
  68. package/dist/core/available-tools.js +14 -3
  69. package/dist/core/change-metadata/schema.d.ts +2 -0
  70. package/dist/core/change-metadata/schema.js +13 -0
  71. package/dist/core/change-status-policy.d.ts +10 -1
  72. package/dist/core/change-status-policy.js +30 -2
  73. package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
  74. package/dist/core/command-generation/adapters/amazon-q.js +11 -5
  75. package/dist/core/command-generation/adapters/antigravity.js +6 -5
  76. package/dist/core/command-generation/adapters/auggie.js +7 -6
  77. package/dist/core/command-generation/adapters/bob.d.ts +5 -1
  78. package/dist/core/command-generation/adapters/bob.js +11 -10
  79. package/dist/core/command-generation/adapters/claude.d.ts +1 -1
  80. package/dist/core/command-generation/adapters/claude.js +12 -17
  81. package/dist/core/command-generation/adapters/cline.js +5 -5
  82. package/dist/core/command-generation/adapters/codebuddy.js +8 -7
  83. package/dist/core/command-generation/adapters/continue.js +8 -7
  84. package/dist/core/command-generation/adapters/costrict.js +7 -6
  85. package/dist/core/command-generation/adapters/crush.js +9 -9
  86. package/dist/core/command-generation/adapters/cursor.js +8 -8
  87. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  88. package/dist/core/command-generation/adapters/devin.js +36 -0
  89. package/dist/core/command-generation/adapters/factory.js +7 -6
  90. package/dist/core/command-generation/adapters/gemini.js +40 -5
  91. package/dist/core/command-generation/adapters/github-copilot.js +6 -5
  92. package/dist/core/command-generation/adapters/iflow.js +9 -8
  93. package/dist/core/command-generation/adapters/index.d.ts +4 -2
  94. package/dist/core/command-generation/adapters/index.js +4 -2
  95. package/dist/core/command-generation/adapters/junie.js +6 -5
  96. package/dist/core/command-generation/adapters/kilocode.js +1 -1
  97. package/dist/core/command-generation/adapters/kiro.js +6 -5
  98. package/dist/core/command-generation/adapters/lingma.js +9 -9
  99. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  101. package/dist/core/command-generation/adapters/opencode.js +6 -8
  102. package/dist/core/command-generation/adapters/pi.d.ts +2 -2
  103. package/dist/core/command-generation/adapters/pi.js +7 -10
  104. package/dist/core/command-generation/adapters/qoder.js +9 -9
  105. package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
  106. package/dist/core/command-generation/adapters/qwen.js +14 -9
  107. package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
  108. package/dist/core/command-generation/adapters/roocode.js +9 -9
  109. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  110. package/dist/core/command-generation/adapters/trae.js +28 -0
  111. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  112. package/dist/core/command-generation/adapters/zcode.js +33 -0
  113. package/dist/core/command-generation/generator.d.ts +8 -0
  114. package/dist/core/command-generation/generator.js +15 -1
  115. package/dist/core/command-generation/index.d.ts +1 -1
  116. package/dist/core/command-generation/index.js +1 -1
  117. package/dist/core/command-generation/invocation.d.ts +73 -0
  118. package/dist/core/command-generation/invocation.js +77 -0
  119. package/dist/core/command-generation/registry.js +8 -4
  120. package/dist/core/command-generation/types.d.ts +9 -2
  121. package/dist/core/command-generation/yaml.d.ts +20 -6
  122. package/dist/core/command-generation/yaml.js +31 -21
  123. package/dist/core/command-surface.d.ts +15 -0
  124. package/dist/core/command-surface.js +33 -0
  125. package/dist/core/completions/command-registry.js +16 -61
  126. package/dist/core/completions/generators/bash-generator.js +41 -41
  127. package/dist/core/completions/generators/fish-generator.js +7 -7
  128. package/dist/core/completions/generators/powershell-generator.js +44 -37
  129. package/dist/core/completions/generators/zsh-generator.js +33 -33
  130. package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
  131. package/dist/core/completions/installers/zsh-installer.js +20 -4
  132. package/dist/core/completions/templates/bash-templates.js +24 -24
  133. package/dist/core/completions/templates/fish-templates.js +38 -38
  134. package/dist/core/completions/templates/powershell-templates.js +28 -28
  135. package/dist/core/completions/templates/zsh-templates.js +39 -39
  136. package/dist/core/config-prompts.d.ts +1 -1
  137. package/dist/core/config-prompts.js +14 -1
  138. package/dist/core/config-schema.d.ts +10 -1
  139. package/dist/core/config-schema.js +67 -1
  140. package/dist/core/config.d.ts +15 -0
  141. package/dist/core/config.js +46 -5
  142. package/dist/core/file-state.js +56 -24
  143. package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
  144. package/dist/core/github-copilot/cloud-agent.js +497 -0
  145. package/dist/core/global-config.d.ts +16 -0
  146. package/dist/core/init.d.ts +58 -0
  147. package/dist/core/init.js +474 -73
  148. package/dist/core/legacy-cleanup.d.ts +59 -0
  149. package/dist/core/legacy-cleanup.js +253 -24
  150. package/dist/core/list.js +23 -16
  151. package/dist/core/migration.d.ts +73 -1
  152. package/dist/core/migration.js +360 -11
  153. package/dist/core/modify/conflict-detector.d.ts +26 -0
  154. package/dist/core/modify/conflict-detector.js +62 -0
  155. package/dist/core/modify/dependency-propagator.d.ts +38 -0
  156. package/dist/core/modify/dependency-propagator.js +103 -0
  157. package/dist/core/modify/history-tracker.d.ts +34 -0
  158. package/dist/core/modify/history-tracker.js +114 -0
  159. package/dist/core/modify/index.d.ts +7 -0
  160. package/dist/core/modify/index.js +7 -0
  161. package/dist/core/onboarding-commands.d.ts +30 -0
  162. package/dist/core/onboarding-commands.js +38 -0
  163. package/dist/core/openspec-root.js +18 -15
  164. package/dist/core/parsers/change-parser.d.ts +17 -2
  165. package/dist/core/parsers/change-parser.js +35 -21
  166. package/dist/core/parsers/code-fence.d.ts +15 -0
  167. package/dist/core/parsers/code-fence.js +49 -0
  168. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  169. package/dist/core/parsers/markdown-parser.js +7 -60
  170. package/dist/core/parsers/requirement-blocks.d.ts +29 -0
  171. package/dist/core/parsers/requirement-blocks.js +150 -31
  172. package/dist/core/parsers/requirement-text.d.ts +48 -0
  173. package/dist/core/parsers/requirement-text.js +98 -0
  174. package/dist/core/parsers/spec-structure.d.ts +1 -1
  175. package/dist/core/parsers/spec-structure.js +20 -30
  176. package/dist/core/profile-sync-drift.d.ts +0 -8
  177. package/dist/core/profile-sync-drift.js +49 -50
  178. package/dist/core/profiles.d.ts +2 -2
  179. package/dist/core/profiles.js +2 -2
  180. package/dist/core/project-config.d.ts +27 -18
  181. package/dist/core/project-config.js +101 -34
  182. package/dist/core/references.js +42 -4
  183. package/dist/core/relationship-health.d.ts +8 -0
  184. package/dist/core/relationship-health.js +13 -0
  185. package/dist/core/root-selection.d.ts +9 -6
  186. package/dist/core/root-selection.js +42 -5
  187. package/dist/core/schemas/base.schema.js +7 -2
  188. package/dist/core/shared/allowed-tools.d.ts +12 -0
  189. package/dist/core/shared/allowed-tools.js +12 -0
  190. package/dist/core/shared/index.d.ts +1 -0
  191. package/dist/core/shared/index.js +1 -0
  192. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  193. package/dist/core/shared/skill-content-equivalence.js +41 -0
  194. package/dist/core/shared/skill-generation.js +17 -13
  195. package/dist/core/shared/skill-paths.d.ts +13 -0
  196. package/dist/core/shared/skill-paths.js +23 -0
  197. package/dist/core/shared/tool-detection.d.ts +27 -7
  198. package/dist/core/shared/tool-detection.js +167 -37
  199. package/dist/core/shared-skill-target.d.ts +16 -0
  200. package/dist/core/shared-skill-target.js +156 -0
  201. package/dist/core/specs-apply.d.ts +80 -33
  202. package/dist/core/specs-apply.js +667 -145
  203. package/dist/core/store/git.d.ts +13 -0
  204. package/dist/core/store/git.js +24 -0
  205. package/dist/core/store/operations.js +20 -0
  206. package/dist/core/templates/skill-templates.d.ts +3 -2
  207. package/dist/core/templates/skill-templates.js +3 -2
  208. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  209. package/dist/core/templates/workflows/apply-change.js +186 -224
  210. package/dist/core/templates/workflows/archive-change.js +387 -260
  211. package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
  212. package/dist/core/templates/workflows/continue-change.js +210 -219
  213. package/dist/core/templates/workflows/explore.js +479 -443
  214. package/dist/core/templates/workflows/feedback.js +97 -97
  215. package/dist/core/templates/workflows/ff-change.js +200 -191
  216. package/dist/core/templates/workflows/modify-change.d.ts +4 -1
  217. package/dist/core/templates/workflows/modify-change.js +237 -115
  218. package/dist/core/templates/workflows/new-change.js +127 -134
  219. package/dist/core/templates/workflows/onboard.js +548 -542
  220. package/dist/core/templates/workflows/propose.js +273 -217
  221. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  222. package/dist/core/templates/workflows/store-selection.js +1 -1
  223. package/dist/core/templates/workflows/sync-specs.js +500 -273
  224. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  225. package/dist/core/templates/workflows/update-change.js +177 -0
  226. package/dist/core/templates/workflows/verify-change.js +324 -326
  227. package/dist/core/update.d.ts +40 -5
  228. package/dist/core/update.js +411 -96
  229. package/dist/core/validation/constants.d.ts +4 -1
  230. package/dist/core/validation/constants.js +4 -1
  231. package/dist/core/validation/plan-validator.d.ts +8 -0
  232. package/dist/core/validation/plan-validator.js +45 -0
  233. package/dist/core/validation/task-numbering.d.ts +16 -0
  234. package/dist/core/validation/task-numbering.js +57 -0
  235. package/dist/core/validation/validator.d.ts +27 -3
  236. package/dist/core/validation/validator.js +337 -48
  237. package/dist/core/version-check.d.ts +152 -0
  238. package/dist/core/version-check.js +731 -0
  239. package/dist/core/view.js +13 -18
  240. package/dist/prompts/searchable-multi-select.js +1 -1
  241. package/dist/telemetry/caller-detection.d.ts +27 -0
  242. package/dist/telemetry/caller-detection.js +85 -0
  243. package/dist/telemetry/caller-detection.test.d.ts +5 -0
  244. package/dist/telemetry/config.d.ts +48 -2
  245. package/dist/telemetry/config.js +113 -11
  246. package/dist/telemetry/config.test.d.ts +5 -0
  247. package/dist/telemetry/deduplication.d.ts +35 -0
  248. package/dist/telemetry/deduplication.js +82 -0
  249. package/dist/telemetry/deduplication.test.d.ts +5 -0
  250. package/dist/telemetry/identity.d.ts +62 -22
  251. package/dist/telemetry/identity.js +147 -102
  252. package/dist/telemetry/index.d.ts +126 -15
  253. package/dist/telemetry/index.js +569 -37
  254. package/dist/telemetry/sanitization.d.ts +40 -0
  255. package/dist/telemetry/sanitization.js +114 -0
  256. package/dist/telemetry/sanitization.test.d.ts +5 -0
  257. package/dist/telemetry/throttling.d.ts +20 -0
  258. package/dist/telemetry/throttling.js +70 -0
  259. package/dist/ui/welcome-screen.d.ts +9 -1
  260. package/dist/ui/welcome-screen.js +91 -38
  261. package/dist/utils/change-metadata.d.ts +43 -0
  262. package/dist/utils/change-metadata.js +123 -7
  263. package/dist/utils/change-utils.d.ts +10 -4
  264. package/dist/utils/change-utils.js +20 -13
  265. package/dist/utils/ci.d.ts +12 -0
  266. package/dist/utils/ci.js +16 -0
  267. package/dist/utils/command-references.d.ts +92 -6
  268. package/dist/utils/command-references.js +180 -7
  269. package/dist/utils/date.d.ts +8 -0
  270. package/dist/utils/date.js +13 -0
  271. package/dist/utils/file-system.d.ts +10 -0
  272. package/dist/utils/file-system.js +70 -0
  273. package/dist/utils/index.d.ts +1 -1
  274. package/dist/utils/index.js +1 -1
  275. package/dist/utils/interactive.d.ts +23 -0
  276. package/dist/utils/interactive.js +32 -0
  277. package/dist/utils/item-discovery.d.ts +19 -0
  278. package/dist/utils/item-discovery.js +30 -48
  279. package/dist/utils/shell-detection.d.ts +1 -1
  280. package/dist/utils/shell-detection.js +64 -11
  281. package/dist/utils/spec-discovery.d.ts +40 -0
  282. package/dist/utils/spec-discovery.js +115 -0
  283. package/dist/utils/task-progress.d.ts +29 -1
  284. package/dist/utils/task-progress.js +109 -15
  285. package/package.json +96 -84
  286. package/schemas/spec-driven/schema.yaml +252 -184
  287. package/schemas/spec-driven/templates/design.md +19 -19
  288. package/schemas/spec-driven/templates/plan-template.md +91 -0
  289. package/schemas/spec-driven/templates/proposal.md +29 -24
  290. package/schemas/spec-driven/templates/spec.md +11 -8
  291. package/schemas/spec-driven/templates/tasks.md +9 -9
  292. package/scripts/postinstall.js +83 -83
  293. package/dist/core/command-generation/adapters/codex.d.ts +0 -16
  294. package/dist/core/command-generation/adapters/codex.js +0 -39
  295. package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
  296. package/dist/core/command-generation/adapters/windsurf.js +0 -38
  297. package/dist/core/comprehension/config.d.ts +0 -20
  298. package/dist/core/comprehension/config.js +0 -23
  299. package/dist/core/comprehension/fingerprint.d.ts +0 -14
  300. package/dist/core/comprehension/fingerprint.js +0 -33
  301. package/dist/core/comprehension/index.d.ts +0 -57
  302. package/dist/core/comprehension/index.js +0 -87
  303. package/dist/core/comprehension/pass-record.d.ts +0 -29
  304. package/dist/core/comprehension/pass-record.js +0 -64
  305. package/dist/core/comprehension/stats.d.ts +0 -35
  306. package/dist/core/comprehension/stats.js +0 -133
  307. package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
  308. package/dist/core/templates/workflows/comprehension-guidance.js +0 -73
  309. package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
  310. package/dist/core/templates/workflows/mcp-guidance.js +0 -131
  311. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
  312. package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
  313. package/dist/telemetry/caller.d.ts +0 -5
  314. package/dist/telemetry/caller.js +0 -29
  315. package/dist/telemetry/client.d.ts +0 -27
  316. package/dist/telemetry/client.js +0 -127
  317. package/dist/telemetry/command-context.d.ts +0 -13
  318. package/dist/telemetry/command-context.js +0 -59
  319. package/dist/telemetry/comprehension.d.ts +0 -44
  320. package/dist/telemetry/comprehension.js +0 -105
  321. package/dist/telemetry/content.d.ts +0 -10
  322. package/dist/telemetry/content.js +0 -56
  323. package/dist/telemetry/git-stats.d.ts +0 -12
  324. package/dist/telemetry/git-stats.js +0 -69
  325. package/dist/telemetry/identify-cache.d.ts +0 -7
  326. package/dist/telemetry/identify-cache.js +0 -47
  327. package/dist/telemetry/input.d.ts +0 -17
  328. package/dist/telemetry/input.js +0 -68
  329. package/dist/telemetry/marker.d.ts +0 -37
  330. package/dist/telemetry/marker.js +0 -67
  331. package/dist/telemetry/workflow.d.ts +0 -85
  332. package/dist/telemetry/workflow.js +0 -318
  333. package/schemas/spec-driven/templates/plan.md +0 -19
@@ -1,3 +1,4 @@
1
+ import * as fs from 'node:fs';
1
2
  import type { SchemaYaml } from './types.js';
2
3
  /**
3
4
  * Error thrown when loading a schema fails.
@@ -22,6 +23,19 @@ export declare function getUserSchemasDir(): string;
22
23
  * @returns The path to the project's schemas directory
23
24
  */
24
25
  export declare function getProjectSchemasDir(projectRoot: string): string;
26
+ /**
27
+ * Determines whether a directory entry represents a schema directory candidate.
28
+ *
29
+ * Returns true for real directories and for symlinks whose target is a
30
+ * directory. `fs.Dirent.isDirectory()` reports the raw entry type, so a symlink
31
+ * (even one pointing at a directory) has `isDirectory() === false`; we
32
+ * dereference such entries via `fs.statSync` to admit symlinked schema dirs
33
+ * while still rejecting symlinks-to-files and broken/dangling symlinks.
34
+ *
35
+ * @param parentDir - The directory containing the entry
36
+ * @param entry - The directory entry from `fs.readdirSync(..., { withFileTypes: true })`
37
+ */
38
+ export declare function isSchemaDir(parentDir: string, entry: fs.Dirent): boolean;
25
39
  /**
26
40
  * Resolves a schema name to its directory path.
27
41
  *
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { getGlobalDataDir } from '../global-config.js';
5
+ import { FileSystemUtils } from '../../utils/file-system.js';
5
6
  import { parseSchema, SchemaValidationError } from './schema.js';
6
7
  /**
7
8
  * Error thrown when loading a schema fails.
@@ -39,6 +40,53 @@ export function getUserSchemasDir() {
39
40
  export function getProjectSchemasDir(projectRoot) {
40
41
  return path.join(projectRoot, 'openspec', 'schemas');
41
42
  }
43
+ /**
44
+ * Determines whether a directory entry represents a schema directory candidate.
45
+ *
46
+ * Returns true for real directories and for symlinks whose target is a
47
+ * directory. `fs.Dirent.isDirectory()` reports the raw entry type, so a symlink
48
+ * (even one pointing at a directory) has `isDirectory() === false`; we
49
+ * dereference such entries via `fs.statSync` to admit symlinked schema dirs
50
+ * while still rejecting symlinks-to-files and broken/dangling symlinks.
51
+ *
52
+ * @param parentDir - The directory containing the entry
53
+ * @param entry - The directory entry from `fs.readdirSync(..., { withFileTypes: true })`
54
+ */
55
+ export function isSchemaDir(parentDir, entry) {
56
+ if (entry.isDirectory()) {
57
+ return true;
58
+ }
59
+ if (entry.isSymbolicLink()) {
60
+ try {
61
+ // statSync follows the link; isDirectory() reflects the target type.
62
+ return fs.statSync(path.join(parentDir, entry.name)).isDirectory();
63
+ }
64
+ catch {
65
+ // Broken symlink (dangling target) — statSync throws; treat as non-dir.
66
+ return false;
67
+ }
68
+ }
69
+ return false;
70
+ }
71
+ /**
72
+ * Returns a schema directory only when its schema file stays within that
73
+ * directory's canonical trust boundary. The directory itself may be a symlink;
74
+ * external user schema links are an intentionally supported workflow.
75
+ */
76
+ function getSchemaCandidateDir(schemasDir, name) {
77
+ const schemaDir = path.join(schemasDir, name);
78
+ const schemaPath = path.join(schemaDir, 'schema.yaml');
79
+ if (!fs.existsSync(schemaPath)) {
80
+ return null;
81
+ }
82
+ try {
83
+ FileSystemUtils.assertPathWithin(schemaDir, schemaPath);
84
+ return schemaDir;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
42
90
  /**
43
91
  * Resolves a schema name to its directory path.
44
92
  *
@@ -55,24 +103,30 @@ export function getProjectSchemasDir(projectRoot) {
55
103
  * @returns The path to the schema directory, or null if not found
56
104
  */
57
105
  export function getSchemaDir(name, projectRoot) {
106
+ if (name.length === 0 ||
107
+ name === '.' ||
108
+ name === '..' ||
109
+ /[\\/]/u.test(name) ||
110
+ /^[A-Za-z]:/u.test(name) ||
111
+ path.posix.isAbsolute(name) ||
112
+ path.win32.isAbsolute(name)) {
113
+ return null;
114
+ }
58
115
  // 1. Check project-local directory (if projectRoot provided)
59
116
  if (projectRoot) {
60
- const projectDir = path.join(getProjectSchemasDir(projectRoot), name);
61
- const projectSchemaPath = path.join(projectDir, 'schema.yaml');
62
- if (fs.existsSync(projectSchemaPath)) {
117
+ const projectDir = getSchemaCandidateDir(getProjectSchemasDir(projectRoot), name);
118
+ if (projectDir) {
63
119
  return projectDir;
64
120
  }
65
121
  }
66
122
  // 2. Check user override directory
67
- const userDir = path.join(getUserSchemasDir(), name);
68
- const userSchemaPath = path.join(userDir, 'schema.yaml');
69
- if (fs.existsSync(userSchemaPath)) {
123
+ const userDir = getSchemaCandidateDir(getUserSchemasDir(), name);
124
+ if (userDir) {
70
125
  return userDir;
71
126
  }
72
127
  // 3. Check package built-in directory
73
- const packageDir = path.join(getPackageSchemasDir(), name);
74
- const packageSchemaPath = path.join(packageDir, 'schema.yaml');
75
- if (fs.existsSync(packageSchemaPath)) {
128
+ const packageDir = getSchemaCandidateDir(getPackageSchemasDir(), name);
129
+ if (packageDir) {
76
130
  return packageDir;
77
131
  }
78
132
  return null;
@@ -134,7 +188,7 @@ export function listSchemas(projectRoot) {
134
188
  const packageDir = getPackageSchemasDir();
135
189
  if (fs.existsSync(packageDir)) {
136
190
  for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
137
- if (entry.isDirectory()) {
191
+ if (isSchemaDir(packageDir, entry)) {
138
192
  const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
139
193
  if (fs.existsSync(schemaPath)) {
140
194
  schemas.add(entry.name);
@@ -146,7 +200,7 @@ export function listSchemas(projectRoot) {
146
200
  const userDir = getUserSchemasDir();
147
201
  if (fs.existsSync(userDir)) {
148
202
  for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
149
- if (entry.isDirectory()) {
203
+ if (isSchemaDir(userDir, entry)) {
150
204
  const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
151
205
  if (fs.existsSync(schemaPath)) {
152
206
  schemas.add(entry.name);
@@ -159,7 +213,7 @@ export function listSchemas(projectRoot) {
159
213
  const projectDir = getProjectSchemasDir(projectRoot);
160
214
  if (fs.existsSync(projectDir)) {
161
215
  for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
162
- if (entry.isDirectory()) {
216
+ if (isSchemaDir(projectDir, entry)) {
163
217
  const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
164
218
  if (fs.existsSync(schemaPath)) {
165
219
  schemas.add(entry.name);
@@ -184,7 +238,7 @@ export function listSchemasWithInfo(projectRoot) {
184
238
  const projectDir = getProjectSchemasDir(projectRoot);
185
239
  if (fs.existsSync(projectDir)) {
186
240
  for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
187
- if (entry.isDirectory()) {
241
+ if (isSchemaDir(projectDir, entry)) {
188
242
  const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
189
243
  if (fs.existsSync(schemaPath)) {
190
244
  try {
@@ -209,7 +263,7 @@ export function listSchemasWithInfo(projectRoot) {
209
263
  const userDir = getUserSchemasDir();
210
264
  if (fs.existsSync(userDir)) {
211
265
  for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
212
- if (entry.isDirectory() && !seenNames.has(entry.name)) {
266
+ if (isSchemaDir(userDir, entry) && !seenNames.has(entry.name)) {
213
267
  const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
214
268
  if (fs.existsSync(schemaPath)) {
215
269
  try {
@@ -233,7 +287,7 @@ export function listSchemasWithInfo(projectRoot) {
233
287
  const packageDir = getPackageSchemasDir();
234
288
  if (fs.existsSync(packageDir)) {
235
289
  for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
236
- if (entry.isDirectory() && !seenNames.has(entry.name)) {
290
+ if (isSchemaDir(packageDir, entry) && !seenNames.has(entry.name)) {
237
291
  const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
238
292
  if (fs.existsSync(schemaPath)) {
239
293
  try {
@@ -1,10 +1,28 @@
1
+ import * as path from 'node:path';
1
2
  import { z } from 'zod';
3
+ function relativePathSchema(fieldName) {
4
+ return z
5
+ .string()
6
+ .min(1, { error: `${fieldName} is required` })
7
+ .superRefine((value, ctx) => {
8
+ const segments = value.split(/[\\/]+/u);
9
+ const isDrivePath = /^[A-Za-z]:/u.test(value);
10
+ const isAbsolute = path.posix.isAbsolute(value) || path.win32.isAbsolute(value) || isDrivePath;
11
+ const escapes = segments.includes('..');
12
+ if (isAbsolute || escapes || value.includes('\0')) {
13
+ ctx.addIssue({
14
+ code: 'custom',
15
+ message: `${fieldName} must be a relative path inside its allowed directory`,
16
+ });
17
+ }
18
+ });
19
+ }
2
20
  // Artifact definition schema
3
21
  export const ArtifactSchema = z.object({
4
22
  id: z.string().min(1, { error: 'Artifact ID is required' }),
5
- generates: z.string().min(1, { error: 'generates field is required' }),
23
+ generates: relativePathSchema('generates field'),
6
24
  description: z.string(),
7
- template: z.string().min(1, { error: 'template field is required' }),
25
+ template: relativePathSchema('template field'),
8
26
  instruction: z.string().optional(),
9
27
  requires: z.array(z.string()).default([]),
10
28
  });
@@ -13,7 +31,7 @@ export const ApplyPhaseSchema = z.object({
13
31
  // Artifact IDs that must exist before apply is available
14
32
  requires: z.array(z.string()).min(1, { error: 'At least one required artifact' }),
15
33
  // Path to file with checkboxes for progress (relative to change dir), or null if no tracking
16
- tracks: z.string().nullable().optional(),
34
+ tracks: relativePathSchema('apply.tracks').nullable().optional(),
17
35
  // Custom guidance for the apply phase
18
36
  instruction: z.string().optional(),
19
37
  });
@@ -10,8 +10,8 @@ import { type AIToolOption } from './config.js';
10
10
  * the tools that are present.
11
11
  *
12
12
  * For tools with `detectionPaths`, checks those specific paths (files or
13
- * directories). Otherwise checks for the tool's `skillsDir` directory at
14
- * the project root. Only tools with a `skillsDir` property are considered.
13
+ * directories). Otherwise checks the project's `skillsDir`, or managed skill
14
+ * files in the user's home directory for a global skill target.
15
15
  */
16
16
  export declare function getAvailableTools(projectPath: string): AIToolOption[];
17
17
  //# sourceMappingURL=available-tools.d.ts.map
@@ -7,16 +7,25 @@
7
7
  import path from 'path';
8
8
  import * as fs from 'fs';
9
9
  import { AI_TOOLS } from './config.js';
10
+ import { reconcileSharedSkillTargets } from './shared-skill-target.js';
11
+ import { SKILL_NAMES } from './shared/tool-detection.js';
12
+ import { resolveToolSkillsDir, toolSupportsSkills } from './shared/skill-paths.js';
10
13
  /**
11
14
  * Scans the project path for AI tool configuration directories and returns
12
15
  * the tools that are present.
13
16
  *
14
17
  * For tools with `detectionPaths`, checks those specific paths (files or
15
- * directories). Otherwise checks for the tool's `skillsDir` directory at
16
- * the project root. Only tools with a `skillsDir` property are considered.
18
+ * directories). Otherwise checks the project's `skillsDir`, or managed skill
19
+ * files in the user's home directory for a global skill target.
17
20
  */
18
21
  export function getAvailableTools(projectPath) {
19
- return AI_TOOLS.filter((tool) => {
22
+ const available = AI_TOOLS.filter((tool) => {
23
+ if (!toolSupportsSkills(tool))
24
+ return false;
25
+ if (tool.globalSkillsDir) {
26
+ const skillsDir = resolveToolSkillsDir(projectPath, tool);
27
+ return SKILL_NAMES.some((skillName) => fs.existsSync(path.join(skillsDir, skillName, 'SKILL.md')));
28
+ }
20
29
  if (!tool.skillsDir)
21
30
  return false;
22
31
  if (tool.detectionPaths && tool.detectionPaths.length > 0) {
@@ -39,5 +48,7 @@ export function getAvailableTools(projectPath) {
39
48
  return false;
40
49
  }
41
50
  });
51
+ const activeProjectTools = new Set(reconcileSharedSkillTargets(projectPath, available.filter((tool) => tool.skillsDir)).map((tool) => tool.value));
52
+ return available.filter((tool) => tool.globalSkillsDir || activeProjectTools.has(tool.value));
42
53
  }
43
54
  //# sourceMappingURL=available-tools.js.map
@@ -14,6 +14,8 @@ export declare const ChangeMetadataSchema: z.ZodObject<{
14
14
  store: z.ZodString;
15
15
  id: z.ZodString;
16
16
  }, z.core.$strict>>;
17
+ skip_specs: z.ZodOptional<z.ZodBoolean>;
18
+ retire_capabilities: z.ZodOptional<z.ZodBoolean>;
17
19
  }, z.core.$strip>;
18
20
  export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
19
21
  //# sourceMappingURL=schema.d.ts.map
@@ -26,5 +26,18 @@ export const ChangeMetadataSchema = z.object({
26
26
  goal: z.string().min(1).optional(),
27
27
  affected_areas: z.array(z.string().min(1)).optional(),
28
28
  initiative: InitiativeLinkSchema.optional(),
29
+ // Declares that this change intentionally has no spec deltas (pure refactor,
30
+ // tooling, or docs work). Validation accepts zero deltas, and the artifact
31
+ // graph counts artifacts whose `generates` path lives under specs/ as
32
+ // complete - that path prefix, not the artifact id, is the contract custom
33
+ // schemas inherit.
34
+ skip_specs: z.boolean().optional(),
35
+ // Declares that this change may retire a capability: when its REMOVED entries
36
+ // take the last requirement a capability has, archive deletes that
37
+ // capability's main spec instead of aborting on a spec it could not write
38
+ // (#1302). Required because the deletion is not recoverable from the working
39
+ // tree - only from git - so it is the author's call, not an inference from the
40
+ // shape of a delta.
41
+ retire_capabilities: z.boolean().optional(),
29
42
  });
30
43
  //# sourceMappingURL=schema.js.map
@@ -18,7 +18,7 @@ export interface ActionContext {
18
18
  }
19
19
  export interface ChangeStatusPolicyArtifact {
20
20
  id: string;
21
- status: 'done' | 'ready' | 'blocked';
21
+ status: 'done' | 'skipped' | 'ready' | 'blocked';
22
22
  }
23
23
  export interface ChangeNextStepsInput {
24
24
  changeName: string;
@@ -31,7 +31,16 @@ export interface ActionContextInput {
31
31
  projectRoot: string;
32
32
  artifactIds: string[];
33
33
  }
34
+ export interface CanModifyResult {
35
+ canModify: boolean;
36
+ reason?: string;
37
+ }
34
38
  export declare function summarizePlanningHome(planningHome: PlanningHome | undefined): PlanningHomeSummary | undefined;
35
39
  export declare function buildActionContext(input: ActionContextInput): ActionContext;
36
40
  export declare function buildNextSteps(input: ChangeNextStepsInput): string[];
41
+ /**
42
+ * Checks if a change can be modified (pre-apply validation).
43
+ * Returns false if implementation has started (any tasks are checked off).
44
+ */
45
+ export declare function canModify(changeName: string, changesDir: string): Promise<CanModifyResult>;
37
46
  //# sourceMappingURL=change-status-policy.d.ts.map
@@ -1,3 +1,5 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
1
3
  export function summarizePlanningHome(planningHome) {
2
4
  if (!planningHome) {
3
5
  return undefined;
@@ -23,13 +25,39 @@ export function buildActionContext(input) {
23
25
  export function buildNextSteps(input) {
24
26
  const readyArtifact = input.artifactStatuses.find((artifact) => artifact.status === 'ready');
25
27
  const steps = [];
28
+ const storeFlag = input.storeId ? ` --store ${input.storeId}` : '';
26
29
  if (readyArtifact) {
27
- const storeFlag = input.storeId ? ` --store ${input.storeId}` : '';
28
30
  steps.push(`Run openspec instructions ${readyArtifact.id} --change "${input.changeName}"${storeFlag} --json before writing that artifact.`);
29
31
  }
30
32
  else if (input.allArtifactsComplete) {
31
- steps.push('Review planning artifacts; use /opsx:modify to revise or /opsx:apply to implement.');
33
+ steps.push(`All planning artifacts are complete. Run openspec instructions apply --change "${input.changeName}"${storeFlag} --json to inspect implementation progress.`);
32
34
  }
33
35
  return steps;
34
36
  }
37
+ /**
38
+ * Checks if a change can be modified (pre-apply validation).
39
+ * Returns false if implementation has started (any tasks are checked off).
40
+ */
41
+ export async function canModify(changeName, changesDir) {
42
+ const tasksPath = join(changesDir, changeName, 'tasks.md');
43
+ try {
44
+ const tasksContent = await readFile(tasksPath, 'utf-8');
45
+ // Check if any tasks are checked off (marked with - [x])
46
+ const hasCompletedTasks = /- \[x\]/.test(tasksContent);
47
+ if (hasCompletedTasks) {
48
+ return {
49
+ canModify: false,
50
+ reason: 'Modification is blocked because implementation has started (tasks have been completed). Use /opsx:modify only before /opsx:apply begins.',
51
+ };
52
+ }
53
+ return { canModify: true };
54
+ }
55
+ catch (error) {
56
+ // If tasks.md doesn't exist, allow modification (change hasn't been fully set up yet)
57
+ if (error.code === 'ENOENT') {
58
+ return { canModify: true };
59
+ }
60
+ throw error;
61
+ }
62
+ }
35
63
  //# sourceMappingURL=change-status-policy.js.map
@@ -8,6 +8,10 @@ import type { ToolCommandAdapter } from '../types.js';
8
8
  * Amazon Q adapter for command generation.
9
9
  * File path: .amazonq/prompts/opsx-<id>.md
10
10
  * Frontmatter: description
11
+ *
12
+ * Amazon Q surfaces these files as its prompt library rather than as slash
13
+ * commands: the user types `@opsx-propose`, not `/opsx-propose`.
14
+ * https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-prompts.html
11
15
  */
12
16
  export declare const amazonQAdapter: ToolCommandAdapter;
13
17
  //# sourceMappingURL=amazon-q.d.ts.map
@@ -4,22 +4,28 @@
4
4
  * Formats commands for Amazon Q Developer following its frontmatter specification.
5
5
  */
6
6
  import path from 'path';
7
+ import { escapeYamlValue } from '../yaml.js';
7
8
  /**
8
9
  * Amazon Q adapter for command generation.
9
10
  * File path: .amazonq/prompts/opsx-<id>.md
10
11
  * Frontmatter: description
12
+ *
13
+ * Amazon Q surfaces these files as its prompt library rather than as slash
14
+ * commands: the user types `@opsx-propose`, not `/opsx-propose`.
15
+ * https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-prompts.html
11
16
  */
12
17
  export const amazonQAdapter = {
13
18
  toolId: 'amazon-q',
14
19
  getFilePath(commandId) {
15
20
  return path.join('.amazonq', 'prompts', `opsx-${commandId}.md`);
16
21
  },
22
+ invocationPrefix: '@',
17
23
  formatFile(content) {
18
- return `---
19
- description: ${content.description}
20
- ---
21
-
22
- ${content.body}
24
+ return `---
25
+ description: ${escapeYamlValue(content.description)}
26
+ ---
27
+
28
+ ${content.body}
23
29
  `;
24
30
  },
25
31
  };
@@ -4,6 +4,7 @@
4
4
  * Formats commands for Antigravity following its frontmatter specification.
5
5
  */
6
6
  import path from 'path';
7
+ import { escapeYamlValue } from '../yaml.js';
7
8
  /**
8
9
  * Antigravity adapter for command generation.
9
10
  * File path: .agent/workflows/opsx-<id>.md
@@ -15,11 +16,11 @@ export const antigravityAdapter = {
15
16
  return path.join('.agent', 'workflows', `opsx-${commandId}.md`);
16
17
  },
17
18
  formatFile(content) {
18
- return `---
19
- description: ${content.description}
20
- ---
21
-
22
- ${content.body}
19
+ return `---
20
+ description: ${escapeYamlValue(content.description)}
21
+ ---
22
+
23
+ ${content.body}
23
24
  `;
24
25
  },
25
26
  };
@@ -4,6 +4,7 @@
4
4
  * Formats commands for Auggie following its frontmatter specification.
5
5
  */
6
6
  import path from 'path';
7
+ import { escapeYamlValue } from '../yaml.js';
7
8
  /**
8
9
  * Auggie adapter for command generation.
9
10
  * File path: .augment/commands/opsx-<id>.md
@@ -15,12 +16,12 @@ export const auggieAdapter = {
15
16
  return path.join('.augment', 'commands', `opsx-${commandId}.md`);
16
17
  },
17
18
  formatFile(content) {
18
- return `---
19
- description: ${content.description}
20
- argument-hint: command arguments
21
- ---
22
-
23
- ${content.body}
19
+ return `---
20
+ description: ${escapeYamlValue(content.description)}
21
+ argument-hint: command arguments
22
+ ---
23
+
24
+ ${content.body}
24
25
  `;
25
26
  },
26
27
  };
@@ -8,7 +8,11 @@ import type { ToolCommandAdapter } from '../types.js';
8
8
  /**
9
9
  * Bob Shell adapter for command generation.
10
10
  * File path: .bob/commands/opsx-<id>.md
11
- * Frontmatter: description, argument-hint
11
+ * Frontmatter: description
12
+ *
13
+ * Bob uses the filename (minus .md) as the slash command name, so
14
+ * opsx-propose.md → /opsx-propose. generateCommand rewrites the body's
15
+ * command references to that form before this adapter formats it.
12
16
  */
13
17
  export declare const bobAdapter: ToolCommandAdapter;
14
18
  //# sourceMappingURL=bob.d.ts.map
@@ -5,12 +5,15 @@
5
5
  * Commands are stored in .bob/commands/ directory.
6
6
  */
7
7
  import path from 'path';
8
- import { transformToHyphenCommands } from '../../../utils/command-references.js';
9
8
  import { escapeYamlValue } from '../yaml.js';
10
9
  /**
11
10
  * Bob Shell adapter for command generation.
12
11
  * File path: .bob/commands/opsx-<id>.md
13
- * Frontmatter: description, argument-hint
12
+ * Frontmatter: description
13
+ *
14
+ * Bob uses the filename (minus .md) as the slash command name, so
15
+ * opsx-propose.md → /opsx-propose. generateCommand rewrites the body's
16
+ * command references to that form before this adapter formats it.
14
17
  */
15
18
  export const bobAdapter = {
16
19
  toolId: 'bob',
@@ -18,14 +21,12 @@ export const bobAdapter = {
18
21
  return path.join('.bob', 'commands', `opsx-${commandId}.md`);
19
22
  },
20
23
  formatFile(content) {
21
- // Transform command references from colon to hyphen format for Bob
22
- const transformedBody = transformToHyphenCommands(content.body);
23
- return `---
24
- description: ${escapeYamlValue(content.description)}
25
- argument-hint: command arguments
26
- ---
27
-
28
- ${transformedBody}
24
+ return `---
25
+ description: ${escapeYamlValue(content.description)}
26
+ argument-hint: command arguments
27
+ ---
28
+
29
+ ${content.body}
29
30
  `;
30
31
  },
31
32
  };
@@ -7,7 +7,7 @@ import type { ToolCommandAdapter } from '../types.js';
7
7
  /**
8
8
  * Claude Code adapter for command generation.
9
9
  * File path: .claude/commands/opsx/<id>.md
10
- * Frontmatter: name, description, category, tags
10
+ * Frontmatter: name, description, allowed-tools, category, tags
11
11
  */
12
12
  export declare const claudeAdapter: ToolCommandAdapter;
13
13
  //# sourceMappingURL=claude.d.ts.map
@@ -4,18 +4,12 @@
4
4
  * Formats commands for Claude Code following its frontmatter specification.
5
5
  */
6
6
  import path from 'path';
7
- import { escapeYamlValue } from '../yaml.js';
8
- /**
9
- * Formats a tags array as a YAML array with proper escaping.
10
- */
11
- function formatTagsArray(tags) {
12
- const escapedTags = tags.map((tag) => escapeYamlValue(tag));
13
- return `[${escapedTags.join(', ')}]`;
14
- }
7
+ import { escapeYamlValue, formatTagsArray } from '../yaml.js';
8
+ import { OPENSPEC_CLI_ALLOWED_TOOLS } from '../../shared/allowed-tools.js';
15
9
  /**
16
10
  * Claude Code adapter for command generation.
17
11
  * File path: .claude/commands/opsx/<id>.md
18
- * Frontmatter: name, description, category, tags
12
+ * Frontmatter: name, description, allowed-tools, category, tags
19
13
  */
20
14
  export const claudeAdapter = {
21
15
  toolId: 'claude',
@@ -23,14 +17,15 @@ export const claudeAdapter = {
23
17
  return path.join('.claude', 'commands', 'opsx', `${commandId}.md`);
24
18
  },
25
19
  formatFile(content) {
26
- return `---
27
- name: ${escapeYamlValue(content.name)}
28
- description: ${escapeYamlValue(content.description)}
29
- category: ${escapeYamlValue(content.category)}
30
- tags: ${formatTagsArray(content.tags)}
31
- ---
32
-
33
- ${content.body}
20
+ return `---
21
+ name: ${escapeYamlValue(content.name)}
22
+ description: ${escapeYamlValue(content.description)}
23
+ allowed-tools: ${OPENSPEC_CLI_ALLOWED_TOOLS}
24
+ category: ${escapeYamlValue(content.category)}
25
+ tags: ${formatTagsArray(content.tags)}
26
+ ---
27
+
28
+ ${content.body}
34
29
  `;
35
30
  },
36
31
  };
@@ -16,11 +16,11 @@ export const clineAdapter = {
16
16
  return path.join('.clinerules', 'workflows', `opsx-${commandId}.md`);
17
17
  },
18
18
  formatFile(content) {
19
- return `# ${content.name}
20
-
21
- ${content.description}
22
-
23
- ${content.body}
19
+ return `# ${content.name}
20
+
21
+ ${content.description}
22
+
23
+ ${content.body}
24
24
  `;
25
25
  },
26
26
  };
@@ -4,6 +4,7 @@
4
4
  * Formats commands for CodeBuddy following its frontmatter specification.
5
5
  */
6
6
  import path from 'path';
7
+ import { escapeYamlValue } from '../yaml.js';
7
8
  /**
8
9
  * CodeBuddy adapter for command generation.
9
10
  * File path: .codebuddy/commands/opsx/<id>.md
@@ -15,13 +16,13 @@ export const codebuddyAdapter = {
15
16
  return path.join('.codebuddy', 'commands', 'opsx', `${commandId}.md`);
16
17
  },
17
18
  formatFile(content) {
18
- return `---
19
- name: ${content.name}
20
- description: "${content.description}"
21
- argument-hint: "[command arguments]"
22
- ---
23
-
24
- ${content.body}
19
+ return `---
20
+ name: ${escapeYamlValue(content.name)}
21
+ description: ${escapeYamlValue(content.description)}
22
+ argument-hint: "[command arguments]"
23
+ ---
24
+
25
+ ${content.body}
25
26
  `;
26
27
  },
27
28
  };
@@ -4,6 +4,7 @@
4
4
  * Formats commands for Continue following its .prompt specification.
5
5
  */
6
6
  import path from 'path';
7
+ import { escapeYamlValue } from '../yaml.js';
7
8
  /**
8
9
  * Continue adapter for command generation.
9
10
  * File path: .continue/prompts/opsx-<id>.prompt
@@ -15,13 +16,13 @@ export const continueAdapter = {
15
16
  return path.join('.continue', 'prompts', `opsx-${commandId}.prompt`);
16
17
  },
17
18
  formatFile(content) {
18
- return `---
19
- name: opsx-${content.id}
20
- description: ${content.description}
21
- invokable: true
22
- ---
23
-
24
- ${content.body}
19
+ return `---
20
+ name: ${escapeYamlValue(`opsx-${content.id}`)}
21
+ description: ${escapeYamlValue(content.description)}
22
+ invokable: true
23
+ ---
24
+
25
+ ${content.body}
25
26
  `;
26
27
  },
27
28
  };