synarcx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +90 -0
  3. package/bin/synarcx.js +3 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +474 -0
  6. package/dist/commands/change.d.ts +35 -0
  7. package/dist/commands/change.js +278 -0
  8. package/dist/commands/completion.d.ts +72 -0
  9. package/dist/commands/completion.js +264 -0
  10. package/dist/commands/config.d.ts +36 -0
  11. package/dist/commands/config.js +552 -0
  12. package/dist/commands/feedback.d.ts +9 -0
  13. package/dist/commands/feedback.js +170 -0
  14. package/dist/commands/schema.d.ts +6 -0
  15. package/dist/commands/schema.js +870 -0
  16. package/dist/commands/show.d.ts +14 -0
  17. package/dist/commands/show.js +132 -0
  18. package/dist/commands/spec.d.ts +15 -0
  19. package/dist/commands/spec.js +226 -0
  20. package/dist/commands/validate.d.ts +24 -0
  21. package/dist/commands/validate.js +295 -0
  22. package/dist/commands/workflow/index.d.ts +17 -0
  23. package/dist/commands/workflow/index.js +12 -0
  24. package/dist/commands/workflow/instructions.d.ts +29 -0
  25. package/dist/commands/workflow/instructions.js +327 -0
  26. package/dist/commands/workflow/new-change.d.ts +11 -0
  27. package/dist/commands/workflow/new-change.js +45 -0
  28. package/dist/commands/workflow/schemas.d.ts +10 -0
  29. package/dist/commands/workflow/schemas.js +34 -0
  30. package/dist/commands/workflow/shared.d.ts +57 -0
  31. package/dist/commands/workflow/shared.js +117 -0
  32. package/dist/commands/workflow/status.d.ts +14 -0
  33. package/dist/commands/workflow/status.js +75 -0
  34. package/dist/commands/workflow/templates.d.ts +16 -0
  35. package/dist/commands/workflow/templates.js +69 -0
  36. package/dist/commands/workspace/open.d.ts +29 -0
  37. package/dist/commands/workspace/open.js +84 -0
  38. package/dist/commands/workspace/operations.d.ts +18 -0
  39. package/dist/commands/workspace/operations.js +461 -0
  40. package/dist/commands/workspace/selection.d.ts +5 -0
  41. package/dist/commands/workspace/selection.js +90 -0
  42. package/dist/commands/workspace/types.d.ts +83 -0
  43. package/dist/commands/workspace/types.js +36 -0
  44. package/dist/commands/workspace.d.ts +3 -0
  45. package/dist/commands/workspace.js +635 -0
  46. package/dist/core/archive.d.ts +11 -0
  47. package/dist/core/archive.js +319 -0
  48. package/dist/core/artifact-graph/graph.d.ts +56 -0
  49. package/dist/core/artifact-graph/graph.js +141 -0
  50. package/dist/core/artifact-graph/index.d.ts +8 -0
  51. package/dist/core/artifact-graph/index.js +14 -0
  52. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  53. package/dist/core/artifact-graph/instruction-loader.js +217 -0
  54. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  55. package/dist/core/artifact-graph/outputs.js +39 -0
  56. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  57. package/dist/core/artifact-graph/resolver.js +258 -0
  58. package/dist/core/artifact-graph/schema.d.ts +13 -0
  59. package/dist/core/artifact-graph/schema.js +108 -0
  60. package/dist/core/artifact-graph/state.d.ts +12 -0
  61. package/dist/core/artifact-graph/state.js +31 -0
  62. package/dist/core/artifact-graph/types.d.ts +45 -0
  63. package/dist/core/artifact-graph/types.js +43 -0
  64. package/dist/core/available-tools.d.ts +17 -0
  65. package/dist/core/available-tools.js +43 -0
  66. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  67. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  68. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  70. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/auggie.js +27 -0
  72. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  73. package/dist/core/command-generation/adapters/bob.js +45 -0
  74. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  75. package/dist/core/command-generation/adapters/claude.js +50 -0
  76. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  77. package/dist/core/command-generation/adapters/cline.js +27 -0
  78. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  80. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  81. package/dist/core/command-generation/adapters/codex.js +39 -0
  82. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/continue.js +28 -0
  84. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  85. package/dist/core/command-generation/adapters/costrict.js +27 -0
  86. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/crush.js +30 -0
  88. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  89. package/dist/core/command-generation/adapters/cursor.js +44 -0
  90. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/factory.js +27 -0
  92. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/gemini.js +26 -0
  94. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  95. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  96. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/iflow.js +29 -0
  98. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  99. package/dist/core/command-generation/adapters/index.js +32 -0
  100. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  101. package/dist/core/command-generation/adapters/junie.js +26 -0
  102. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  103. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  104. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  105. package/dist/core/command-generation/adapters/kiro.js +26 -0
  106. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/lingma.js +30 -0
  108. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  109. package/dist/core/command-generation/adapters/opencode.js +27 -0
  110. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  111. package/dist/core/command-generation/adapters/pi.js +55 -0
  112. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/qoder.js +30 -0
  114. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  115. package/dist/core/command-generation/adapters/qwen.js +26 -0
  116. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  117. package/dist/core/command-generation/adapters/roocode.js +27 -0
  118. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  119. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  120. package/dist/core/command-generation/generator.d.ts +21 -0
  121. package/dist/core/command-generation/generator.js +27 -0
  122. package/dist/core/command-generation/index.d.ts +22 -0
  123. package/dist/core/command-generation/index.js +24 -0
  124. package/dist/core/command-generation/registry.d.ts +36 -0
  125. package/dist/core/command-generation/registry.js +98 -0
  126. package/dist/core/command-generation/types.d.ts +56 -0
  127. package/dist/core/command-generation/types.js +8 -0
  128. package/dist/core/completions/command-registry.d.ts +7 -0
  129. package/dist/core/completions/command-registry.js +596 -0
  130. package/dist/core/completions/completion-provider.d.ts +71 -0
  131. package/dist/core/completions/completion-provider.js +129 -0
  132. package/dist/core/completions/factory.d.ts +64 -0
  133. package/dist/core/completions/factory.js +75 -0
  134. package/dist/core/completions/generators/bash-generator.d.ts +35 -0
  135. package/dist/core/completions/generators/bash-generator.js +230 -0
  136. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  137. package/dist/core/completions/generators/fish-generator.js +160 -0
  138. package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
  139. package/dist/core/completions/generators/powershell-generator.js +266 -0
  140. package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
  141. package/dist/core/completions/generators/zsh-generator.js +274 -0
  142. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  143. package/dist/core/completions/installers/bash-installer.js +318 -0
  144. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  145. package/dist/core/completions/installers/fish-installer.js +143 -0
  146. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  147. package/dist/core/completions/installers/powershell-installer.js +387 -0
  148. package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
  149. package/dist/core/completions/installers/zsh-installer.js +421 -0
  150. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  151. package/dist/core/completions/templates/bash-templates.js +30 -0
  152. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  153. package/dist/core/completions/templates/fish-templates.js +45 -0
  154. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  155. package/dist/core/completions/templates/powershell-templates.js +34 -0
  156. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  157. package/dist/core/completions/templates/zsh-templates.js +45 -0
  158. package/dist/core/completions/types.d.ts +101 -0
  159. package/dist/core/completions/types.js +2 -0
  160. package/dist/core/config-prompts.d.ts +9 -0
  161. package/dist/core/config-prompts.js +34 -0
  162. package/dist/core/config-schema.d.ts +86 -0
  163. package/dist/core/config-schema.js +213 -0
  164. package/dist/core/config.d.ts +19 -0
  165. package/dist/core/config.js +38 -0
  166. package/dist/core/converters/json-converter.d.ts +6 -0
  167. package/dist/core/converters/json-converter.js +51 -0
  168. package/dist/core/global-config.d.ts +49 -0
  169. package/dist/core/global-config.js +124 -0
  170. package/dist/core/index.d.ts +3 -0
  171. package/dist/core/index.js +4 -0
  172. package/dist/core/init.d.ts +37 -0
  173. package/dist/core/init.js +585 -0
  174. package/dist/core/legacy-cleanup.d.ts +169 -0
  175. package/dist/core/legacy-cleanup.js +578 -0
  176. package/dist/core/list.d.ts +9 -0
  177. package/dist/core/list.js +172 -0
  178. package/dist/core/migration.d.ts +23 -0
  179. package/dist/core/migration.js +108 -0
  180. package/dist/core/parsers/change-parser.d.ts +13 -0
  181. package/dist/core/parsers/change-parser.js +197 -0
  182. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  183. package/dist/core/parsers/markdown-parser.js +227 -0
  184. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  185. package/dist/core/parsers/requirement-blocks.js +201 -0
  186. package/dist/core/parsers/spec-structure.d.ts +9 -0
  187. package/dist/core/parsers/spec-structure.js +88 -0
  188. package/dist/core/profile-sync-drift.d.ts +38 -0
  189. package/dist/core/profile-sync-drift.js +197 -0
  190. package/dist/core/profiles.d.ts +26 -0
  191. package/dist/core/profiles.js +37 -0
  192. package/dist/core/project-config.d.ts +64 -0
  193. package/dist/core/project-config.js +224 -0
  194. package/dist/core/schemas/base.schema.d.ts +13 -0
  195. package/dist/core/schemas/base.schema.js +13 -0
  196. package/dist/core/schemas/change.schema.d.ts +73 -0
  197. package/dist/core/schemas/change.schema.js +31 -0
  198. package/dist/core/schemas/index.d.ts +4 -0
  199. package/dist/core/schemas/index.js +4 -0
  200. package/dist/core/schemas/spec.schema.d.ts +18 -0
  201. package/dist/core/schemas/spec.schema.js +15 -0
  202. package/dist/core/shared/index.d.ts +8 -0
  203. package/dist/core/shared/index.js +8 -0
  204. package/dist/core/shared/skill-generation.d.ts +49 -0
  205. package/dist/core/shared/skill-generation.js +90 -0
  206. package/dist/core/shared/tool-detection.d.ts +71 -0
  207. package/dist/core/shared/tool-detection.js +152 -0
  208. package/dist/core/specs-apply.d.ts +73 -0
  209. package/dist/core/specs-apply.js +393 -0
  210. package/dist/core/styles/palette.d.ts +7 -0
  211. package/dist/core/styles/palette.js +8 -0
  212. package/dist/core/templates/index.d.ts +8 -0
  213. package/dist/core/templates/index.js +9 -0
  214. package/dist/core/templates/skill-templates.d.ts +15 -0
  215. package/dist/core/templates/skill-templates.js +14 -0
  216. package/dist/core/templates/types.d.ts +19 -0
  217. package/dist/core/templates/types.js +5 -0
  218. package/dist/core/templates/workflows/analyze.d.ts +4 -0
  219. package/dist/core/templates/workflows/analyze.js +101 -0
  220. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  221. package/dist/core/templates/workflows/apply-change.js +308 -0
  222. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  223. package/dist/core/templates/workflows/archive-change.js +271 -0
  224. package/dist/core/templates/workflows/clarify.d.ts +4 -0
  225. package/dist/core/templates/workflows/clarify.js +108 -0
  226. package/dist/core/templates/workflows/debug.d.ts +4 -0
  227. package/dist/core/templates/workflows/debug.js +117 -0
  228. package/dist/core/templates/workflows/explore.d.ts +10 -0
  229. package/dist/core/templates/workflows/explore.js +479 -0
  230. package/dist/core/templates/workflows/propose.d.ts +10 -0
  231. package/dist/core/templates/workflows/propose.js +216 -0
  232. package/dist/core/templates/workflows/sync.d.ts +4 -0
  233. package/dist/core/templates/workflows/sync.js +108 -0
  234. package/dist/core/update.d.ts +82 -0
  235. package/dist/core/update.js +555 -0
  236. package/dist/core/validation/constants.d.ts +34 -0
  237. package/dist/core/validation/constants.js +40 -0
  238. package/dist/core/validation/types.d.ts +18 -0
  239. package/dist/core/validation/types.js +2 -0
  240. package/dist/core/validation/validator.d.ts +33 -0
  241. package/dist/core/validation/validator.js +418 -0
  242. package/dist/core/view.d.ts +8 -0
  243. package/dist/core/view.js +169 -0
  244. package/dist/core/workspace/foundation.d.ts +79 -0
  245. package/dist/core/workspace/foundation.js +367 -0
  246. package/dist/core/workspace/index.d.ts +5 -0
  247. package/dist/core/workspace/index.js +5 -0
  248. package/dist/core/workspace/link-input.d.ts +9 -0
  249. package/dist/core/workspace/link-input.js +32 -0
  250. package/dist/core/workspace/open-surface.d.ts +24 -0
  251. package/dist/core/workspace/open-surface.js +137 -0
  252. package/dist/core/workspace/openers.d.ts +21 -0
  253. package/dist/core/workspace/openers.js +119 -0
  254. package/dist/index.d.ts +3 -0
  255. package/dist/index.js +3 -0
  256. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  257. package/dist/prompts/searchable-multi-select.js +159 -0
  258. package/dist/ui/ascii-patterns.d.ts +25 -0
  259. package/dist/ui/ascii-patterns.js +140 -0
  260. package/dist/ui/welcome-screen.d.ts +10 -0
  261. package/dist/ui/welcome-screen.js +144 -0
  262. package/dist/utils/change-metadata.d.ts +51 -0
  263. package/dist/utils/change-metadata.js +147 -0
  264. package/dist/utils/change-utils.d.ts +62 -0
  265. package/dist/utils/change-utils.js +122 -0
  266. package/dist/utils/command-references.d.ts +18 -0
  267. package/dist/utils/command-references.js +20 -0
  268. package/dist/utils/file-system.d.ts +41 -0
  269. package/dist/utils/file-system.js +301 -0
  270. package/dist/utils/index.d.ts +6 -0
  271. package/dist/utils/index.js +9 -0
  272. package/dist/utils/interactive.d.ts +18 -0
  273. package/dist/utils/interactive.js +21 -0
  274. package/dist/utils/item-discovery.d.ts +4 -0
  275. package/dist/utils/item-discovery.js +73 -0
  276. package/dist/utils/match.d.ts +3 -0
  277. package/dist/utils/match.js +22 -0
  278. package/dist/utils/shell-detection.d.ts +20 -0
  279. package/dist/utils/shell-detection.js +41 -0
  280. package/dist/utils/task-progress.d.ts +8 -0
  281. package/dist/utils/task-progress.js +36 -0
  282. package/package.json +76 -0
  283. package/schemas/synarcx/schema.yaml +153 -0
  284. package/schemas/synarcx/templates/design.md +19 -0
  285. package/schemas/synarcx/templates/proposal.md +23 -0
  286. package/schemas/synarcx/templates/spec.md +8 -0
  287. package/schemas/synarcx/templates/tasks.md +9 -0
  288. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,555 @@
1
+ /**
2
+ * Update Command
3
+ *
4
+ * Refreshes synarcx skills and commands for configured tools.
5
+ * Supports profile-aware updates, delivery changes, migration, and smart update detection.
6
+ */
7
+ import path from 'path';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import * as fs from 'fs';
11
+ import { createRequire } from 'module';
12
+ import { FileSystemUtils } from '../utils/file-system.js';
13
+ import { transformToHyphenCommands } from '../utils/command-references.js';
14
+ import { AI_TOOLS, SYNSPEC_DIR_NAME } from './config.js';
15
+ import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
16
+ import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, } from './shared/index.js';
17
+ import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, getToolsFromLegacyArtifacts, } from './legacy-cleanup.js';
18
+ import { isInteractive } from '../utils/interactive.js';
19
+ import { getGlobalConfig } from './global-config.js';
20
+ import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
21
+ import { getAvailableTools } from './available-tools.js';
22
+ import { WORKFLOW_TO_SKILL_DIR, getCommandConfiguredTools, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
23
+ import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, } from './migration.js';
24
+ const require = createRequire(import.meta.url);
25
+ const { version: OPENSPEC_VERSION } = require('../../package.json');
26
+ const OLD_CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive'];
27
+ /**
28
+ * Scans installed workflow artifacts (skills and managed commands) across all configured tools.
29
+ * Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
30
+ *
31
+ * Wrapper around the shared migration module's scanInstalledWorkflows that accepts tool IDs.
32
+ */
33
+ export function scanInstalledWorkflows(projectPath, toolIds) {
34
+ const tools = toolIds
35
+ .map((id) => AI_TOOLS.find((t) => t.value === id))
36
+ .filter((t) => t != null);
37
+ return scanInstalledWorkflowsShared(projectPath, tools);
38
+ }
39
+ export class UpdateCommand {
40
+ force;
41
+ constructor(options = {}) {
42
+ this.force = options.force ?? false;
43
+ }
44
+ async execute(projectPath) {
45
+ const resolvedProjectPath = path.resolve(projectPath);
46
+ const openspecPath = path.join(resolvedProjectPath, SYNSPEC_DIR_NAME);
47
+ // 1. Check synspec directory exists
48
+ if (!await FileSystemUtils.directoryExists(openspecPath)) {
49
+ throw new Error(`No synarcx directory found. Run 'synarcx init' first.`);
50
+ }
51
+ // 2. Perform one-time migration if needed before any legacy upgrade generation.
52
+ // Use detected tool directories to preserve existing OPP skills/commands.
53
+ const detectedTools = getAvailableTools(resolvedProjectPath);
54
+ migrateIfNeededShared(resolvedProjectPath, detectedTools);
55
+ // 3. Read global config for profile/delivery
56
+ const globalConfig = getGlobalConfig();
57
+ const profile = globalConfig.profile ?? 'core';
58
+ const delivery = globalConfig.delivery ?? 'both';
59
+ const profileWorkflows = getProfileWorkflows(profile, globalConfig.workflows);
60
+ const desiredWorkflows = profileWorkflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
61
+ const shouldGenerateSkills = delivery !== 'commands';
62
+ const shouldGenerateCommands = delivery !== 'skills';
63
+ // 4. Detect and handle legacy artifacts + upgrade legacy tools using effective config
64
+ const newlyConfiguredTools = await this.handleLegacyCleanup(resolvedProjectPath, desiredWorkflows, delivery);
65
+ // 5. Find configured tools
66
+ const configuredTools = getConfiguredToolsForProfileSync(resolvedProjectPath);
67
+ if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) {
68
+ console.log(chalk.yellow('No configured tools found.'));
69
+ console.log(chalk.dim('Run "synarcx init" to set up tools.'));
70
+ return;
71
+ }
72
+ // 6. Check version status for all configured tools
73
+ const commandConfiguredTools = getCommandConfiguredTools(resolvedProjectPath);
74
+ const commandConfiguredSet = new Set(commandConfiguredTools);
75
+ const toolStatuses = configuredTools.map((toolId) => {
76
+ const status = getToolVersionStatus(resolvedProjectPath, toolId, OPENSPEC_VERSION);
77
+ if (!status.configured && commandConfiguredSet.has(toolId)) {
78
+ return { ...status, configured: true };
79
+ }
80
+ return status;
81
+ });
82
+ const statusByTool = new Map(toolStatuses.map((status) => [status.toolId, status]));
83
+ // 7. Smart update detection
84
+ const toolsNeedingVersionUpdate = toolStatuses
85
+ .filter((s) => s.needsUpdate)
86
+ .map((s) => s.toolId);
87
+ const toolsNeedingConfigSync = getToolsNeedingProfileSync(resolvedProjectPath, desiredWorkflows, delivery, configuredTools);
88
+ const toolsToUpdateSet = new Set([
89
+ ...toolsNeedingVersionUpdate,
90
+ ...toolsNeedingConfigSync,
91
+ ]);
92
+ const toolsUpToDate = toolStatuses.filter((s) => !toolsToUpdateSet.has(s.toolId));
93
+ if (!this.force && toolsToUpdateSet.size === 0) {
94
+ // All tools are up to date
95
+ this.displayUpToDateMessage(toolStatuses);
96
+ // Still check for new tool directories and extra workflows
97
+ this.detectNewTools(resolvedProjectPath, configuredTools);
98
+ this.displayExtraWorkflowsNote(resolvedProjectPath, configuredTools, desiredWorkflows);
99
+ this.displayOldCoreCustomProfileNote(profile, globalConfig.workflows);
100
+ return;
101
+ }
102
+ // 8. Display update plan
103
+ if (this.force) {
104
+ console.log(`Force updating ${configuredTools.length} tool(s): ${configuredTools.join(', ')}`);
105
+ }
106
+ else {
107
+ this.displayUpdatePlan([...toolsToUpdateSet], statusByTool, toolsUpToDate);
108
+ }
109
+ console.log();
110
+ // 9. Determine what to generate based on delivery
111
+ const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
112
+ const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
113
+ // 10. Update tools (all if force, otherwise only those needing update)
114
+ const toolsToUpdate = this.force ? configuredTools : [...toolsToUpdateSet];
115
+ const updatedTools = [];
116
+ const failedTools = [];
117
+ let removedCommandCount = 0;
118
+ let removedSkillCount = 0;
119
+ let removedDeselectedCommandCount = 0;
120
+ let removedDeselectedSkillCount = 0;
121
+ for (const toolId of toolsToUpdate) {
122
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
123
+ if (!tool?.skillsDir)
124
+ continue;
125
+ const spinner = ora(`Updating ${tool.name}...`).start();
126
+ try {
127
+ const skillsDir = path.join(resolvedProjectPath, tool.skillsDir, 'skills');
128
+ // Generate skill files if delivery includes skills
129
+ if (shouldGenerateSkills) {
130
+ for (const { template, dirName } of skillTemplates) {
131
+ const skillDir = path.join(skillsDir, dirName);
132
+ const skillFile = path.join(skillDir, 'SKILL.md');
133
+ // Use hyphen-based command references for tools where filename = command name
134
+ const transformer = tool.value === 'pi' ? transformToHyphenCommands : undefined;
135
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
136
+ await FileSystemUtils.writeFile(skillFile, skillContent);
137
+ }
138
+ removedDeselectedSkillCount += await this.removeUnselectedSkillDirs(skillsDir, desiredWorkflows);
139
+ }
140
+ // Delete skill directories if delivery is commands-only
141
+ if (!shouldGenerateSkills) {
142
+ removedSkillCount += await this.removeSkillDirs(skillsDir);
143
+ }
144
+ // Generate commands if delivery includes commands
145
+ if (shouldGenerateCommands) {
146
+ const adapter = CommandAdapterRegistry.get(tool.value);
147
+ if (adapter) {
148
+ const generatedCommands = generateCommands(commandContents, adapter);
149
+ for (const cmd of generatedCommands) {
150
+ const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
151
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
152
+ }
153
+ removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(resolvedProjectPath, toolId, desiredWorkflows);
154
+ }
155
+ }
156
+ // Delete command files if delivery is skills-only
157
+ if (!shouldGenerateCommands) {
158
+ removedCommandCount += await this.removeCommandFiles(resolvedProjectPath, toolId);
159
+ }
160
+ spinner.succeed(`Updated ${tool.name}`);
161
+ updatedTools.push(tool.name);
162
+ }
163
+ catch (error) {
164
+ spinner.fail(`Failed to update ${tool.name}`);
165
+ failedTools.push({
166
+ name: tool.name,
167
+ error: error instanceof Error ? error.message : String(error)
168
+ });
169
+ }
170
+ }
171
+ // 11. Summary
172
+ console.log();
173
+ if (updatedTools.length > 0) {
174
+ console.log(chalk.green(`✓ Updated: ${updatedTools.join(', ')} (v${OPENSPEC_VERSION})`));
175
+ }
176
+ if (failedTools.length > 0) {
177
+ console.log(chalk.red(`✗ Failed: ${failedTools.map(f => `${f.name} (${f.error})`).join(', ')}`));
178
+ }
179
+ if (removedCommandCount > 0) {
180
+ console.log(chalk.dim(`Removed: ${removedCommandCount} command files (delivery: skills)`));
181
+ }
182
+ if (removedSkillCount > 0) {
183
+ console.log(chalk.dim(`Removed: ${removedSkillCount} skill directories (delivery: commands)`));
184
+ }
185
+ if (removedDeselectedCommandCount > 0) {
186
+ console.log(chalk.dim(`Removed: ${removedDeselectedCommandCount} command files (deselected workflows)`));
187
+ }
188
+ if (removedDeselectedSkillCount > 0) {
189
+ console.log(chalk.dim(`Removed: ${removedDeselectedSkillCount} skill directories (deselected workflows)`));
190
+ }
191
+ // 12. Show onboarding message for newly configured tools from legacy upgrade
192
+ if (newlyConfiguredTools.length > 0) {
193
+ console.log();
194
+ console.log(chalk.bold('Getting started:'));
195
+ console.log(' /syn:propose Create a new change with all artifacts');
196
+ console.log(' /syn:apply Implement tasks');
197
+ console.log();
198
+ }
199
+ const configuredAndNewTools = [...new Set([...configuredTools, ...newlyConfiguredTools])];
200
+ // 13. Detect new tool directories not currently configured
201
+ this.detectNewTools(resolvedProjectPath, configuredAndNewTools);
202
+ // 14. Display note about extra workflows not in profile
203
+ this.displayExtraWorkflowsNote(resolvedProjectPath, configuredAndNewTools, desiredWorkflows);
204
+ this.displayOldCoreCustomProfileNote(profile, globalConfig.workflows);
205
+ // 15. List affected tools
206
+ if (updatedTools.length > 0) {
207
+ const toolDisplayNames = updatedTools;
208
+ console.log(chalk.dim(`Tools: ${toolDisplayNames.join(', ')}`));
209
+ }
210
+ console.log();
211
+ console.log(chalk.dim('Restart your IDE for changes to take effect.'));
212
+ }
213
+ /**
214
+ * Display message when all tools are up to date.
215
+ */
216
+ displayUpToDateMessage(toolStatuses) {
217
+ const toolNames = toolStatuses.map((s) => s.toolId);
218
+ console.log(chalk.green(`✓ All ${toolStatuses.length} tool(s) up to date (v${OPENSPEC_VERSION})`));
219
+ console.log(chalk.dim(` Tools: ${toolNames.join(', ')}`));
220
+ console.log();
221
+ console.log(chalk.dim('Use --force to refresh files anyway.'));
222
+ }
223
+ /**
224
+ * Display the update plan showing which tools need updating.
225
+ */
226
+ displayUpdatePlan(toolsToUpdate, statusByTool, upToDate) {
227
+ const updates = toolsToUpdate.map((toolId) => {
228
+ const status = statusByTool.get(toolId);
229
+ if (status?.needsUpdate) {
230
+ const fromVersion = status.generatedByVersion ?? 'unknown';
231
+ return `${status.toolId} (${fromVersion} → ${OPENSPEC_VERSION})`;
232
+ }
233
+ return `${toolId} (config sync)`;
234
+ });
235
+ console.log(`Updating ${toolsToUpdate.length} tool(s): ${updates.join(', ')}`);
236
+ if (upToDate.length > 0) {
237
+ const upToDateNames = upToDate.map((s) => s.toolId);
238
+ console.log(chalk.dim(`Already up to date: ${upToDateNames.join(', ')}`));
239
+ }
240
+ }
241
+ /**
242
+ * Detects new tool directories that aren't currently configured and displays a hint.
243
+ */
244
+ detectNewTools(projectPath, configuredTools) {
245
+ const availableTools = getAvailableTools(projectPath);
246
+ const configuredSet = new Set(configuredTools);
247
+ const newTools = availableTools.filter((t) => !configuredSet.has(t.value));
248
+ if (newTools.length > 0) {
249
+ const newToolNames = newTools.map((tool) => tool.name);
250
+ const isSingleTool = newToolNames.length === 1;
251
+ const toolNoun = isSingleTool ? 'tool' : 'tools';
252
+ const pronoun = isSingleTool ? 'it' : 'them';
253
+ console.log();
254
+ console.log(chalk.yellow(`Detected new ${toolNoun}: ${newToolNames.join(', ')}. Run 'synarcx init' to add ${pronoun}.`));
255
+ }
256
+ }
257
+ /**
258
+ * Displays a note about extra workflows installed that aren't in the current profile.
259
+ */
260
+ displayExtraWorkflowsNote(projectPath, configuredTools, profileWorkflows) {
261
+ const installedWorkflows = scanInstalledWorkflows(projectPath, configuredTools);
262
+ const profileSet = new Set(profileWorkflows);
263
+ const extraWorkflows = installedWorkflows.filter((w) => !profileSet.has(w));
264
+ if (extraWorkflows.length > 0) {
265
+ console.log(chalk.dim(`Note: ${extraWorkflows.length} extra workflows not in profile (use \`synarcx config profile\` to manage)`));
266
+ }
267
+ }
268
+ /**
269
+ * Suggest opting back into core when a custom profile still matches the old
270
+ * pre-sync core set. Keep custom profiles user-owned; do not mutate them.
271
+ */
272
+ displayOldCoreCustomProfileNote(profile, workflows) {
273
+ if (profile !== 'custom' || !workflows) {
274
+ return;
275
+ }
276
+ const workflowSet = new Set(workflows);
277
+ const matchesOldCore = workflowSet.size === OLD_CORE_WORKFLOWS.length &&
278
+ OLD_CORE_WORKFLOWS.every((workflow) => workflowSet.has(workflow));
279
+ if (!matchesOldCore) {
280
+ return;
281
+ }
282
+ console.log(chalk.dim('Note: The core profile now includes sync. Your custom profile is preserving the old core workflow set.'));
283
+ console.log(chalk.dim('Run `synarcx config profile core` and then `synarcx update` to add sync.'));
284
+ }
285
+ /**
286
+ * Removes skill directories for workflows when delivery changed to commands-only.
287
+ * Returns the number of directories removed.
288
+ */
289
+ async removeSkillDirs(skillsDir) {
290
+ let removed = 0;
291
+ for (const workflow of ALL_WORKFLOWS) {
292
+ const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
293
+ if (!dirName)
294
+ continue;
295
+ const skillDir = path.join(skillsDir, dirName);
296
+ try {
297
+ if (fs.existsSync(skillDir)) {
298
+ await fs.promises.rm(skillDir, { recursive: true, force: true });
299
+ removed++;
300
+ }
301
+ }
302
+ catch {
303
+ // Ignore errors
304
+ }
305
+ }
306
+ return removed;
307
+ }
308
+ /**
309
+ * Removes skill directories for workflows that are no longer selected in the active profile.
310
+ * Returns the number of directories removed.
311
+ */
312
+ async removeUnselectedSkillDirs(skillsDir, desiredWorkflows) {
313
+ const desiredSet = new Set(desiredWorkflows);
314
+ let removed = 0;
315
+ for (const workflow of ALL_WORKFLOWS) {
316
+ if (desiredSet.has(workflow))
317
+ continue;
318
+ const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
319
+ if (!dirName)
320
+ continue;
321
+ const skillDir = path.join(skillsDir, dirName);
322
+ try {
323
+ if (fs.existsSync(skillDir)) {
324
+ await fs.promises.rm(skillDir, { recursive: true, force: true });
325
+ removed++;
326
+ }
327
+ }
328
+ catch {
329
+ // Ignore errors
330
+ }
331
+ }
332
+ return removed;
333
+ }
334
+ /**
335
+ * Removes command files for workflows when delivery changed to skills-only.
336
+ * Returns the number of files removed.
337
+ */
338
+ async removeCommandFiles(projectPath, toolId) {
339
+ let removed = 0;
340
+ const adapter = CommandAdapterRegistry.get(toolId);
341
+ if (!adapter)
342
+ return 0;
343
+ for (const workflow of ALL_WORKFLOWS) {
344
+ const cmdPath = adapter.getFilePath(workflow);
345
+ const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
346
+ try {
347
+ if (fs.existsSync(fullPath)) {
348
+ await fs.promises.unlink(fullPath);
349
+ removed++;
350
+ }
351
+ }
352
+ catch {
353
+ // Ignore errors
354
+ }
355
+ }
356
+ return removed;
357
+ }
358
+ /**
359
+ * Removes command files for workflows that are no longer selected in the active profile.
360
+ * Returns the number of files removed.
361
+ */
362
+ async removeUnselectedCommandFiles(projectPath, toolId, desiredWorkflows) {
363
+ let removed = 0;
364
+ const adapter = CommandAdapterRegistry.get(toolId);
365
+ if (!adapter)
366
+ return 0;
367
+ const desiredSet = new Set(desiredWorkflows);
368
+ for (const workflow of ALL_WORKFLOWS) {
369
+ if (desiredSet.has(workflow))
370
+ continue;
371
+ const cmdPath = adapter.getFilePath(workflow);
372
+ const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
373
+ try {
374
+ if (fs.existsSync(fullPath)) {
375
+ await fs.promises.unlink(fullPath);
376
+ removed++;
377
+ }
378
+ }
379
+ catch {
380
+ // Ignore errors
381
+ }
382
+ }
383
+ return removed;
384
+ }
385
+ /**
386
+ * Detect and handle legacy OpenSpec artifacts.
387
+ * Unlike init, update warns but continues if legacy files found in non-interactive mode.
388
+ * Returns array of tool IDs that were newly configured during legacy upgrade.
389
+ */
390
+ async handleLegacyCleanup(projectPath, desiredWorkflows, delivery) {
391
+ // Detect legacy artifacts
392
+ const detection = await detectLegacyArtifacts(projectPath);
393
+ if (!detection.hasLegacyArtifacts) {
394
+ return []; // No legacy artifacts found
395
+ }
396
+ // Show what was detected
397
+ console.log();
398
+ console.log(formatDetectionSummary(detection));
399
+ console.log();
400
+ const canPrompt = isInteractive();
401
+ if (this.force) {
402
+ // --force flag: proceed with cleanup automatically
403
+ await this.performLegacyCleanup(projectPath, detection);
404
+ // Then upgrade legacy tools to new skills
405
+ return this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
406
+ }
407
+ if (!canPrompt) {
408
+ // Non-interactive mode without --force: warn and continue
409
+ // (Unlike init, update doesn't abort - user may just want to update skills)
410
+ console.log(chalk.yellow('⚠ Run with --force to auto-cleanup legacy files, or run interactively.'));
411
+ console.log();
412
+ return [];
413
+ }
414
+ // Interactive mode: prompt for confirmation
415
+ const { confirm } = await import('@inquirer/prompts');
416
+ const shouldCleanup = await confirm({
417
+ message: 'Upgrade and clean up legacy files?',
418
+ default: true,
419
+ });
420
+ if (shouldCleanup) {
421
+ await this.performLegacyCleanup(projectPath, detection);
422
+ // Then upgrade legacy tools to new skills
423
+ return this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
424
+ }
425
+ else {
426
+ console.log(chalk.dim('Skipping legacy cleanup. Continuing with skill update...'));
427
+ console.log();
428
+ return [];
429
+ }
430
+ }
431
+ /**
432
+ * Perform cleanup of legacy artifacts.
433
+ */
434
+ async performLegacyCleanup(projectPath, detection) {
435
+ const spinner = ora('Cleaning up legacy files...').start();
436
+ const result = await cleanupLegacyArtifacts(projectPath, detection);
437
+ spinner.succeed('Legacy files cleaned up');
438
+ const summary = formatCleanupSummary(result);
439
+ if (summary) {
440
+ console.log();
441
+ console.log(summary);
442
+ }
443
+ console.log();
444
+ }
445
+ /**
446
+ * Upgrade legacy tools to new skills system.
447
+ * Returns array of tool IDs that were newly configured.
448
+ */
449
+ async upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery) {
450
+ // Get tools that had legacy artifacts
451
+ const legacyTools = getToolsFromLegacyArtifacts(detection);
452
+ if (legacyTools.length === 0) {
453
+ return [];
454
+ }
455
+ // Get currently configured tools
456
+ const configuredTools = getConfiguredToolsForProfileSync(projectPath);
457
+ const configuredSet = new Set(configuredTools);
458
+ // Filter to tools that aren't already configured
459
+ const unconfiguredLegacyTools = legacyTools.filter((t) => !configuredSet.has(t));
460
+ if (unconfiguredLegacyTools.length === 0) {
461
+ return [];
462
+ }
463
+ // Get valid tools (those with skillsDir)
464
+ const validToolIds = new Set(getToolsWithSkillsDir());
465
+ const validUnconfiguredTools = unconfiguredLegacyTools.filter((t) => validToolIds.has(t));
466
+ if (validUnconfiguredTools.length === 0) {
467
+ return [];
468
+ }
469
+ // Show what tools were detected from legacy artifacts
470
+ console.log(chalk.bold('Tools detected from legacy artifacts:'));
471
+ for (const toolId of validUnconfiguredTools) {
472
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
473
+ console.log(` • ${tool?.name || toolId}`);
474
+ }
475
+ console.log();
476
+ let selectedTools;
477
+ if (this.force || !canPrompt) {
478
+ // Non-interactive with --force: auto-select detected tools
479
+ selectedTools = validUnconfiguredTools;
480
+ console.log(`Setting up skills for: ${selectedTools.join(', ')}`);
481
+ }
482
+ else {
483
+ // Interactive mode: prompt for tool selection with detected tools pre-selected
484
+ const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
485
+ const sortedChoices = validUnconfiguredTools.map((toolId) => {
486
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
487
+ return {
488
+ name: tool?.name || toolId,
489
+ value: toolId,
490
+ configured: false,
491
+ preSelected: true, // Pre-select all detected legacy tools
492
+ };
493
+ });
494
+ selectedTools = await searchableMultiSelect({
495
+ message: 'Select tools to set up with the new skill system:',
496
+ pageSize: 15,
497
+ choices: sortedChoices,
498
+ validate: (_selected) => true, // Allow empty selection (user can skip)
499
+ });
500
+ if (selectedTools.length === 0) {
501
+ console.log(chalk.dim('Skipping tool setup.'));
502
+ console.log();
503
+ return [];
504
+ }
505
+ }
506
+ // Create skills/commands for selected tools using effective profile+delivery.
507
+ const newlyConfigured = [];
508
+ const shouldGenerateSkills = delivery !== 'commands';
509
+ const shouldGenerateCommands = delivery !== 'skills';
510
+ const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
511
+ const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
512
+ for (const toolId of selectedTools) {
513
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
514
+ if (!tool?.skillsDir)
515
+ continue;
516
+ const spinner = ora(`Setting up ${tool.name}...`).start();
517
+ try {
518
+ const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
519
+ // Create skill files when delivery includes skills
520
+ if (shouldGenerateSkills) {
521
+ for (const { template, dirName } of skillTemplates) {
522
+ const skillDir = path.join(skillsDir, dirName);
523
+ const skillFile = path.join(skillDir, 'SKILL.md');
524
+ // Use hyphen-based command references for tools where filename = command name
525
+ const transformer = tool.value === 'pi' ? transformToHyphenCommands : undefined;
526
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
527
+ await FileSystemUtils.writeFile(skillFile, skillContent);
528
+ }
529
+ }
530
+ // Create commands when delivery includes commands
531
+ if (shouldGenerateCommands) {
532
+ const adapter = CommandAdapterRegistry.get(tool.value);
533
+ if (adapter) {
534
+ const generatedCommands = generateCommands(commandContents, adapter);
535
+ for (const cmd of generatedCommands) {
536
+ const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
537
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
538
+ }
539
+ }
540
+ }
541
+ spinner.succeed(`Setup complete for ${tool.name}`);
542
+ newlyConfigured.push(toolId);
543
+ }
544
+ catch (error) {
545
+ spinner.fail(`Failed to set up ${tool.name}`);
546
+ console.log(chalk.red(` ${error instanceof Error ? error.message : String(error)}`));
547
+ }
548
+ }
549
+ if (newlyConfigured.length > 0) {
550
+ console.log();
551
+ }
552
+ return newlyConfigured;
553
+ }
554
+ }
555
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Validation threshold constants
3
+ */
4
+ export declare const MIN_WHY_SECTION_LENGTH = 50;
5
+ export declare const MIN_PURPOSE_LENGTH = 50;
6
+ export declare const MAX_WHY_SECTION_LENGTH = 1000;
7
+ export declare const MAX_REQUIREMENT_TEXT_LENGTH = 500;
8
+ export declare const MAX_DELTAS_PER_CHANGE = 10;
9
+ export declare const VALIDATION_MESSAGES: {
10
+ readonly SCENARIO_EMPTY: "Scenario text cannot be empty";
11
+ readonly REQUIREMENT_EMPTY: "Requirement text cannot be empty";
12
+ readonly REQUIREMENT_NO_SHALL: "Requirement must contain SHALL or MUST keyword";
13
+ readonly REQUIREMENT_NO_SCENARIOS: "Requirement must have at least one scenario";
14
+ readonly SPEC_NAME_EMPTY: "Spec name cannot be empty";
15
+ readonly SPEC_PURPOSE_EMPTY: "Purpose section cannot be empty";
16
+ readonly SPEC_NO_REQUIREMENTS: "Spec must have at least one requirement";
17
+ readonly CHANGE_NAME_EMPTY: "Change name cannot be empty";
18
+ readonly CHANGE_WHY_TOO_SHORT: "Why section must be at least 50 characters";
19
+ readonly CHANGE_WHY_TOO_LONG: "Why section should not exceed 1000 characters";
20
+ readonly CHANGE_WHAT_EMPTY: "What Changes section cannot be empty";
21
+ readonly CHANGE_NO_DELTAS: "Change must have at least one delta";
22
+ readonly CHANGE_TOO_MANY_DELTAS: "Consider splitting changes with more than 10 deltas";
23
+ readonly DELTA_SPEC_EMPTY: "Spec name cannot be empty";
24
+ readonly DELTA_DESCRIPTION_EMPTY: "Delta description cannot be empty";
25
+ readonly PURPOSE_TOO_BRIEF: "Purpose section is too brief (less than 50 characters)";
26
+ readonly REQUIREMENT_TOO_LONG: "Requirement text is very long (>500 characters). Consider breaking it down.";
27
+ readonly DELTA_DESCRIPTION_TOO_BRIEF: "Delta description is too brief";
28
+ readonly DELTA_MISSING_REQUIREMENTS: "Delta should include requirements";
29
+ readonly GUIDE_NO_DELTAS: "No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one \"#### Scenario:\" block. Tip: run \"synarcx change show <change-id> --json --deltas-only\" to inspect parsed deltas.";
30
+ readonly GUIDE_MISSING_SPEC_SECTIONS: "Missing required sections. Expected headers: \"## Purpose\" and \"## Requirements\". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...";
31
+ readonly GUIDE_MISSING_CHANGE_SECTIONS: "Missing required sections. Expected headers: \"## Why\" and \"## What Changes\". Ensure deltas are documented in specs/ using delta headers.";
32
+ readonly GUIDE_SCENARIO_FORMAT: "Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...";
33
+ };
34
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Validation threshold constants
3
+ */
4
+ // Minimum character lengths
5
+ export const MIN_WHY_SECTION_LENGTH = 50;
6
+ export const MIN_PURPOSE_LENGTH = 50;
7
+ // Maximum character/item limits
8
+ export const MAX_WHY_SECTION_LENGTH = 1000;
9
+ export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
10
+ export const MAX_DELTAS_PER_CHANGE = 10;
11
+ // Validation messages
12
+ export const VALIDATION_MESSAGES = {
13
+ // Required content
14
+ SCENARIO_EMPTY: 'Scenario text cannot be empty',
15
+ REQUIREMENT_EMPTY: 'Requirement text cannot be empty',
16
+ REQUIREMENT_NO_SHALL: 'Requirement must contain SHALL or MUST keyword',
17
+ REQUIREMENT_NO_SCENARIOS: 'Requirement must have at least one scenario',
18
+ SPEC_NAME_EMPTY: 'Spec name cannot be empty',
19
+ SPEC_PURPOSE_EMPTY: 'Purpose section cannot be empty',
20
+ SPEC_NO_REQUIREMENTS: 'Spec must have at least one requirement',
21
+ CHANGE_NAME_EMPTY: 'Change name cannot be empty',
22
+ CHANGE_WHY_TOO_SHORT: `Why section must be at least ${MIN_WHY_SECTION_LENGTH} characters`,
23
+ CHANGE_WHY_TOO_LONG: `Why section should not exceed ${MAX_WHY_SECTION_LENGTH} characters`,
24
+ CHANGE_WHAT_EMPTY: 'What Changes section cannot be empty',
25
+ CHANGE_NO_DELTAS: 'Change must have at least one delta',
26
+ CHANGE_TOO_MANY_DELTAS: `Consider splitting changes with more than ${MAX_DELTAS_PER_CHANGE} deltas`,
27
+ DELTA_SPEC_EMPTY: 'Spec name cannot be empty',
28
+ DELTA_DESCRIPTION_EMPTY: 'Delta description cannot be empty',
29
+ // Warnings
30
+ PURPOSE_TOO_BRIEF: `Purpose section is too brief (less than ${MIN_PURPOSE_LENGTH} characters)`,
31
+ REQUIREMENT_TOO_LONG: `Requirement text is very long (>${MAX_REQUIREMENT_TEXT_LENGTH} characters). Consider breaking it down.`,
32
+ DELTA_DESCRIPTION_TOO_BRIEF: 'Delta description is too brief',
33
+ DELTA_MISSING_REQUIREMENTS: 'Delta should include requirements',
34
+ // Guidance snippets (appended to primary messages for remediation)
35
+ GUIDE_NO_DELTAS: 'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "synarcx change show <change-id> --json --deltas-only" to inspect parsed deltas.',
36
+ GUIDE_MISSING_SPEC_SECTIONS: 'Missing required sections. Expected headers: "## Purpose" and "## Requirements". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...',
37
+ GUIDE_MISSING_CHANGE_SECTIONS: 'Missing required sections. Expected headers: "## Why" and "## What Changes". Ensure deltas are documented in specs/ using delta headers.',
38
+ GUIDE_SCENARIO_FORMAT: 'Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
39
+ };
40
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,18 @@
1
+ export type ValidationLevel = 'ERROR' | 'WARNING' | 'INFO';
2
+ export interface ValidationIssue {
3
+ level: ValidationLevel;
4
+ path: string;
5
+ message: string;
6
+ line?: number;
7
+ column?: number;
8
+ }
9
+ export interface ValidationReport {
10
+ valid: boolean;
11
+ issues: ValidationIssue[];
12
+ summary: {
13
+ errors: number;
14
+ warnings: number;
15
+ info: number;
16
+ };
17
+ }
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map