specweave 0.32.2 → 0.32.5

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 (244) hide show
  1. package/CLAUDE.md +51 -9
  2. package/bin/specweave.js +34 -0
  3. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts +100 -0
  4. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -0
  5. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +291 -0
  6. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -0
  7. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts +103 -0
  8. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -0
  9. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +310 -0
  10. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -0
  11. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts +126 -0
  12. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -0
  13. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +207 -0
  14. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -0
  15. package/dist/src/adapters/codex/README.md +1 -1
  16. package/dist/src/adapters/codex/adapter.js +1 -1
  17. package/dist/src/cli/commands/archive.d.ts +2 -0
  18. package/dist/src/cli/commands/archive.d.ts.map +1 -1
  19. package/dist/src/cli/commands/archive.js +33 -0
  20. package/dist/src/cli/commands/archive.js.map +1 -1
  21. package/dist/src/cli/commands/context.d.ts +92 -0
  22. package/dist/src/cli/commands/context.d.ts.map +1 -0
  23. package/dist/src/cli/commands/context.js +205 -0
  24. package/dist/src/cli/commands/context.js.map +1 -0
  25. package/dist/src/cli/commands/init-multiproject.js +1 -1
  26. package/dist/src/cli/commands/init-multiproject.js.map +1 -1
  27. package/dist/src/cli/commands/init.d.ts.map +1 -1
  28. package/dist/src/cli/commands/init.js +111 -69
  29. package/dist/src/cli/commands/init.js.map +1 -1
  30. package/dist/src/cli/commands/migrate-to-multiproject.js +2 -2
  31. package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
  32. package/dist/src/cli/helpers/init/external-import.d.ts +3 -0
  33. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  34. package/dist/src/cli/helpers/init/external-import.js +17 -4
  35. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  36. package/dist/src/cli/helpers/init/index.d.ts +1 -0
  37. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  38. package/dist/src/cli/helpers/init/index.js +2 -0
  39. package/dist/src/cli/helpers/init/index.js.map +1 -1
  40. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +70 -0
  41. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -1
  42. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +214 -4
  43. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -1
  44. package/dist/src/cli/helpers/init/living-docs-preflight.d.ts +4 -0
  45. package/dist/src/cli/helpers/init/living-docs-preflight.d.ts.map +1 -1
  46. package/dist/src/cli/helpers/init/living-docs-preflight.js +34 -3
  47. package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
  48. package/dist/src/cli/helpers/init/testing-config.d.ts +3 -0
  49. package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -1
  50. package/dist/src/cli/helpers/init/testing-config.js +9 -2
  51. package/dist/src/cli/helpers/init/testing-config.js.map +1 -1
  52. package/dist/src/cli/helpers/init/translation-config.d.ts +3 -0
  53. package/dist/src/cli/helpers/init/translation-config.d.ts.map +1 -1
  54. package/dist/src/cli/helpers/init/translation-config.js +21 -4
  55. package/dist/src/cli/helpers/init/translation-config.js.map +1 -1
  56. package/dist/src/cli/helpers/init/wizard-navigation.d.ts +45 -0
  57. package/dist/src/cli/helpers/init/wizard-navigation.d.ts.map +1 -0
  58. package/dist/src/cli/helpers/init/wizard-navigation.js +97 -0
  59. package/dist/src/cli/helpers/init/wizard-navigation.js.map +1 -0
  60. package/dist/src/core/increment/increment-archiver.d.ts +25 -4
  61. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  62. package/dist/src/core/increment/increment-archiver.js +64 -20
  63. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  64. package/dist/src/core/increment/increment-utils.d.ts +65 -0
  65. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  66. package/dist/src/core/increment/increment-utils.js +114 -0
  67. package/dist/src/core/increment/increment-utils.js.map +1 -1
  68. package/dist/src/core/living-docs/cross-project-sync.d.ts +97 -0
  69. package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -0
  70. package/dist/src/core/living-docs/cross-project-sync.js +135 -0
  71. package/dist/src/core/living-docs/cross-project-sync.js.map +1 -0
  72. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +106 -0
  73. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -0
  74. package/dist/src/core/living-docs/external-sync-orchestrator.js +146 -0
  75. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -0
  76. package/dist/src/core/living-docs/feature-archiver.d.ts +4 -0
  77. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  78. package/dist/src/core/living-docs/feature-archiver.js +32 -10
  79. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  80. package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
  81. package/dist/src/core/living-docs/feature-id-manager.js +7 -3
  82. package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
  83. package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts +38 -0
  84. package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts.map +1 -0
  85. package/dist/src/core/living-docs/governance/ecosystem-detector.js +325 -0
  86. package/dist/src/core/living-docs/governance/ecosystem-detector.js.map +1 -0
  87. package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts +74 -0
  88. package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts.map +1 -0
  89. package/dist/src/core/living-docs/governance/frontend-standards-parser.js +366 -0
  90. package/dist/src/core/living-docs/governance/frontend-standards-parser.js.map +1 -0
  91. package/dist/src/core/living-docs/governance/go-standards-parser.d.ts +64 -0
  92. package/dist/src/core/living-docs/governance/go-standards-parser.d.ts.map +1 -0
  93. package/dist/src/core/living-docs/governance/go-standards-parser.js +229 -0
  94. package/dist/src/core/living-docs/governance/go-standards-parser.js.map +1 -0
  95. package/dist/src/core/living-docs/governance/index.d.ts +50 -0
  96. package/dist/src/core/living-docs/governance/index.d.ts.map +1 -0
  97. package/dist/src/core/living-docs/governance/index.js +56 -0
  98. package/dist/src/core/living-docs/governance/index.js.map +1 -0
  99. package/dist/src/core/living-docs/governance/java-standards-parser.d.ts +89 -0
  100. package/dist/src/core/living-docs/governance/java-standards-parser.d.ts.map +1 -0
  101. package/dist/src/core/living-docs/governance/java-standards-parser.js +356 -0
  102. package/dist/src/core/living-docs/governance/java-standards-parser.js.map +1 -0
  103. package/dist/src/core/living-docs/governance/python-standards-parser.d.ts +83 -0
  104. package/dist/src/core/living-docs/governance/python-standards-parser.d.ts.map +1 -0
  105. package/dist/src/core/living-docs/governance/python-standards-parser.js +347 -0
  106. package/dist/src/core/living-docs/governance/python-standards-parser.js.map +1 -0
  107. package/dist/src/core/living-docs/governance/standards-generator.d.ts +38 -0
  108. package/dist/src/core/living-docs/governance/standards-generator.d.ts.map +1 -0
  109. package/dist/src/core/living-docs/governance/standards-generator.js +476 -0
  110. package/dist/src/core/living-docs/governance/standards-generator.js.map +1 -0
  111. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
  112. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +54 -2
  113. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
  114. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +5 -1
  115. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
  116. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +358 -30
  117. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
  118. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +44 -0
  119. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
  120. package/dist/src/core/living-docs/living-docs-sync.d.ts +7 -3
  121. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  122. package/dist/src/core/living-docs/living-docs-sync.js +94 -10
  123. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  124. package/dist/src/core/living-docs/module-analyzer.d.ts +22 -0
  125. package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
  126. package/dist/src/core/living-docs/module-analyzer.js +123 -19
  127. package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
  128. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +8 -1
  129. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  130. package/dist/src/core/living-docs/sync-helpers/generators.js +18 -1
  131. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  132. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  133. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  134. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  135. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts +3 -1
  136. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts.map +1 -1
  137. package/dist/src/core/living-docs/sync-helpers/parsers.js +24 -2
  138. package/dist/src/core/living-docs/sync-helpers/parsers.js.map +1 -1
  139. package/dist/src/core/living-docs/types.d.ts +6 -0
  140. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  141. package/dist/src/core/living-docs/validators/index.d.ts +7 -0
  142. package/dist/src/core/living-docs/validators/index.d.ts.map +1 -0
  143. package/dist/src/core/living-docs/validators/index.js +7 -0
  144. package/dist/src/core/living-docs/validators/index.js.map +1 -0
  145. package/dist/src/core/living-docs/validators/project-validator.d.ts +92 -0
  146. package/dist/src/core/living-docs/validators/project-validator.d.ts.map +1 -0
  147. package/dist/src/core/living-docs/validators/project-validator.js +142 -0
  148. package/dist/src/core/living-docs/validators/project-validator.js.map +1 -0
  149. package/dist/src/core/llm/provider-factory.js +2 -2
  150. package/dist/src/core/llm/provider-factory.js.map +1 -1
  151. package/dist/src/core/llm/providers/anthropic-provider.js +1 -1
  152. package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
  153. package/dist/src/core/llm/providers/bedrock-provider.js +8 -4
  154. package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
  155. package/dist/src/core/project/project-manager.d.ts.map +1 -1
  156. package/dist/src/core/project/project-manager.js +19 -17
  157. package/dist/src/core/project/project-manager.js.map +1 -1
  158. package/dist/src/core/types/config.d.ts +4 -2
  159. package/dist/src/core/types/config.d.ts.map +1 -1
  160. package/dist/src/core/types/config.js.map +1 -1
  161. package/dist/src/core/types/increment-metadata.d.ts +34 -0
  162. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  163. package/dist/src/importers/jira-importer.d.ts +14 -0
  164. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  165. package/dist/src/importers/jira-importer.js +75 -0
  166. package/dist/src/importers/jira-importer.js.map +1 -1
  167. package/dist/src/integrations/jira/jira-token-provider.d.ts +93 -0
  168. package/dist/src/integrations/jira/jira-token-provider.d.ts.map +1 -0
  169. package/dist/src/integrations/jira/jira-token-provider.js +160 -0
  170. package/dist/src/integrations/jira/jira-token-provider.js.map +1 -0
  171. package/dist/src/sync/ado-reconciler.d.ts +92 -0
  172. package/dist/src/sync/ado-reconciler.d.ts.map +1 -0
  173. package/dist/src/sync/ado-reconciler.js +335 -0
  174. package/dist/src/sync/ado-reconciler.js.map +1 -0
  175. package/dist/src/sync/jira-reconciler.d.ts +106 -0
  176. package/dist/src/sync/jira-reconciler.d.ts.map +1 -0
  177. package/dist/src/sync/jira-reconciler.js +405 -0
  178. package/dist/src/sync/jira-reconciler.js.map +1 -0
  179. package/dist/src/types/model-selection.d.ts +6 -4
  180. package/dist/src/types/model-selection.d.ts.map +1 -1
  181. package/dist/src/types/model-selection.js +3 -1
  182. package/dist/src/types/model-selection.js.map +1 -1
  183. package/dist/src/utils/cross-cutting-detector.d.ts +66 -0
  184. package/dist/src/utils/cross-cutting-detector.d.ts.map +1 -0
  185. package/dist/src/utils/cross-cutting-detector.js +179 -0
  186. package/dist/src/utils/cross-cutting-detector.js.map +1 -0
  187. package/dist/src/utils/external-tool-drift-detector.d.ts +1 -1
  188. package/dist/src/utils/external-tool-drift-detector.d.ts.map +1 -1
  189. package/dist/src/utils/external-tool-drift-detector.js +5 -4
  190. package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
  191. package/dist/src/utils/feature-id-derivation.d.ts +8 -3
  192. package/dist/src/utils/feature-id-derivation.d.ts.map +1 -1
  193. package/dist/src/utils/feature-id-derivation.js +14 -6
  194. package/dist/src/utils/feature-id-derivation.js.map +1 -1
  195. package/dist/src/utils/model-selection.d.ts +3 -4
  196. package/dist/src/utils/model-selection.d.ts.map +1 -1
  197. package/dist/src/utils/model-selection.js +3 -4
  198. package/dist/src/utils/model-selection.js.map +1 -1
  199. package/dist/src/utils/project-detection.d.ts +12 -8
  200. package/dist/src/utils/project-detection.d.ts.map +1 -1
  201. package/dist/src/utils/project-detection.js +13 -19
  202. package/dist/src/utils/project-detection.js.map +1 -1
  203. package/package.json +1 -1
  204. package/plugins/specweave/agents/code-standards-detective/AGENT.md +48 -0
  205. package/plugins/specweave/commands/specweave-costs.md +4 -4
  206. package/plugins/specweave/commands/specweave-do.md +9 -9
  207. package/plugins/specweave/commands/specweave-done.md +13 -0
  208. package/plugins/specweave/commands/specweave-status.md +64 -0
  209. package/plugins/specweave/commands/specweave-validate.md +27 -1
  210. package/plugins/specweave/hooks/hooks.json +11 -1
  211. package/plugins/specweave/hooks/spec-project-validator.sh +81 -25
  212. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +135 -0
  213. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +34 -0
  214. package/plugins/specweave/scripts/read-costs.sh +3 -3
  215. package/plugins/specweave/skills/code-standards-analyzer/SKILL.md +58 -6
  216. package/plugins/specweave/skills/increment-planner/SKILL.md +135 -29
  217. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +4 -2
  218. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +2 -1
  219. package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +1 -1
  220. package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +1 -1
  221. package/plugins/specweave/skills/spec-generator/SKILL.md +78 -7
  222. package/plugins/specweave-ado/commands/cleanup-duplicates.md +212 -0
  223. package/plugins/specweave-ado/commands/reconcile.md +120 -0
  224. package/plugins/specweave-ado/lib/ado-duplicate-detector.js +279 -0
  225. package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +407 -0
  226. package/plugins/specweave-github/agents/github-manager/AGENT.md +2 -2
  227. package/plugins/specweave-infrastructure/skills/hetzner-provisioner/README.md +1 -1
  228. package/plugins/specweave-jira/agents/jira-manager/AGENT.md +1 -1
  229. package/plugins/specweave-jira/agents/jira-multi-project-mapper/AGENT.md +530 -0
  230. package/plugins/specweave-jira/agents/jira-sync-judge/AGENT.md +438 -0
  231. package/plugins/specweave-jira/commands/cleanup-duplicates.md +219 -0
  232. package/plugins/specweave-jira/commands/close.md +297 -0
  233. package/plugins/specweave-jira/commands/create.md +198 -0
  234. package/plugins/specweave-jira/commands/reconcile.md +123 -0
  235. package/plugins/specweave-jira/commands/status.md +215 -0
  236. package/plugins/specweave-jira/lib/jira-duplicate-detector.js +296 -0
  237. package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +434 -0
  238. package/plugins/specweave-jira/lib/jira-permission-gate.js +160 -0
  239. package/plugins/specweave-jira/lib/jira-permission-gate.ts +276 -0
  240. package/plugins/specweave-jira/lib/jira-profile-resolver.js +222 -0
  241. package/plugins/specweave-jira/lib/jira-profile-resolver.ts +427 -0
  242. package/plugins/specweave-jira/reference/jira-specweave-mapping.md +16 -11
  243. package/plugins/specweave-release/commands/specweave-release-npm.md +140 -14
  244. package/plugins/specweave/commands/specweave-switch-project.md +0 -168
@@ -0,0 +1,427 @@
1
+ /**
2
+ * JIRA Profile Resolver
3
+ *
4
+ * Resolves the correct JIRA profile to use for sync operations.
5
+ * Priority:
6
+ * 1. Increment's stored profile (metadata.json -> external_sync.jira.profile)
7
+ * 2. Global defaultProfile (config.json -> sync.defaultProfile)
8
+ * 3. Legacy: Global activeProfile (config.json -> sync.activeProfile)
9
+ *
10
+ * This allows each increment to sync to its designated JIRA project
11
+ * without requiring manual profile switching.
12
+ *
13
+ * NOTE (v0.31.0): Renamed from "activeProfile" to "defaultProfile" for clarity.
14
+ * The default profile is a FALLBACK, not a constraint. Bulk operations
15
+ * iterate ALL profiles.
16
+ *
17
+ * Usage:
18
+ * ```typescript
19
+ * const resolver = await createJiraProfileResolver();
20
+ * const profile = await resolver.resolveProfile('0093-my-increment');
21
+ * // profile contains domain, projectKey, etc.
22
+ * ```
23
+ *
24
+ * @module jira-profile-resolver
25
+ */
26
+
27
+ import { promises as fs } from 'node:fs';
28
+ import * as path from 'node:path';
29
+
30
+ /**
31
+ * JIRA profile configuration
32
+ */
33
+ export interface JiraProfileConfig {
34
+ /**
35
+ * Profile name (for display/logging)
36
+ */
37
+ profileName: string;
38
+
39
+ /**
40
+ * JIRA domain (e.g., company.atlassian.net)
41
+ */
42
+ domain: string;
43
+
44
+ /**
45
+ * JIRA project key (e.g., PROJ)
46
+ */
47
+ projectKey: string;
48
+
49
+ /**
50
+ * Display name (optional)
51
+ */
52
+ displayName?: string;
53
+
54
+ /**
55
+ * Board ID (optional, for 2-level structure)
56
+ */
57
+ boardId?: number;
58
+
59
+ /**
60
+ * Board name (optional)
61
+ */
62
+ boardName?: string;
63
+
64
+ /**
65
+ * Instance type: cloud or server
66
+ */
67
+ instanceType?: 'cloud' | 'server';
68
+ }
69
+
70
+ /**
71
+ * Profile resolution result
72
+ */
73
+ export interface ProfileResolutionResult {
74
+ /**
75
+ * Whether resolution succeeded
76
+ */
77
+ success: boolean;
78
+
79
+ /**
80
+ * Resolved profile config (if success)
81
+ */
82
+ profile?: JiraProfileConfig;
83
+
84
+ /**
85
+ * Source of the profile
86
+ */
87
+ source?: 'increment' | 'global';
88
+
89
+ /**
90
+ * Error message (if failed)
91
+ */
92
+ error?: string;
93
+
94
+ /**
95
+ * Increment ID that was checked
96
+ */
97
+ incrementId?: string;
98
+ }
99
+
100
+ /**
101
+ * Sync profile from config.json
102
+ */
103
+ interface ConfigProfile {
104
+ provider: string;
105
+ displayName?: string;
106
+ config: {
107
+ domain: string;
108
+ projectKey: string;
109
+ boardId?: number;
110
+ boardName?: string;
111
+ instanceType?: 'cloud' | 'server';
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Sync config from config.json
117
+ */
118
+ interface SyncConfig {
119
+ /** @since v0.31.0 - preferred */
120
+ defaultProfile?: string;
121
+ /** @deprecated Use defaultProfile */
122
+ activeProfile?: string;
123
+ profiles?: Record<string, ConfigProfile>;
124
+ }
125
+
126
+ /**
127
+ * Config structure
128
+ */
129
+ interface SpecWeaveConfig {
130
+ sync?: SyncConfig;
131
+ }
132
+
133
+ /**
134
+ * JIRA Profile Resolver
135
+ */
136
+ export class JiraProfileResolver {
137
+ private projectRoot: string;
138
+ private configPath: string;
139
+ private incrementsPath: string;
140
+
141
+ constructor(projectRoot: string = process.cwd()) {
142
+ this.projectRoot = projectRoot;
143
+ this.configPath = path.join(projectRoot, '.specweave', 'config.json');
144
+ this.incrementsPath = path.join(projectRoot, '.specweave', 'increments');
145
+ }
146
+
147
+ /**
148
+ * Resolve the JIRA profile for an increment
149
+ *
150
+ * Priority:
151
+ * 1. Increment's metadata.json -> external_sync.jira.profile
152
+ * 2. Config.json -> sync.defaultProfile (v0.31.0+)
153
+ * 3. Config.json -> sync.activeProfile (legacy fallback)
154
+ *
155
+ * @param incrementId - Increment ID (e.g., "0093-my-feature")
156
+ * @returns Profile resolution result
157
+ */
158
+ async resolveProfile(incrementId: string): Promise<ProfileResolutionResult> {
159
+ // Load global config
160
+ const config = await this.loadConfig();
161
+ if (!config) {
162
+ return {
163
+ success: false,
164
+ error: 'Failed to load .specweave/config.json',
165
+ incrementId,
166
+ };
167
+ }
168
+
169
+ // Try to get increment-specific profile first
170
+ const incrementProfile = await this.getIncrementProfile(incrementId);
171
+
172
+ // Determine which profile to use (defaultProfile preferred over activeProfile)
173
+ const globalDefaultProfile = config.sync?.defaultProfile ?? config.sync?.activeProfile;
174
+ const profileName = incrementProfile || globalDefaultProfile;
175
+
176
+ if (!profileName) {
177
+ return {
178
+ success: false,
179
+ error: 'No JIRA profile configured. Set sync.defaultProfile in config.json or external_sync.jira.profile in increment metadata.',
180
+ incrementId,
181
+ };
182
+ }
183
+
184
+ // Check if profile is for JIRA provider
185
+ const profiles = config.sync?.profiles || {};
186
+ const profileConfig = profiles[profileName] as ConfigProfile | undefined;
187
+
188
+ if (!profileConfig) {
189
+ return {
190
+ success: false,
191
+ error: `Profile "${profileName}" not found in config.sync.profiles`,
192
+ incrementId,
193
+ };
194
+ }
195
+
196
+ if (profileConfig.provider !== 'jira') {
197
+ return {
198
+ success: false,
199
+ error: `Profile "${profileName}" is not a JIRA profile (provider: ${profileConfig.provider})`,
200
+ incrementId,
201
+ };
202
+ }
203
+
204
+ // Validate required fields
205
+ if (!profileConfig.config?.domain || !profileConfig.config?.projectKey) {
206
+ return {
207
+ success: false,
208
+ error: `Profile "${profileName}" missing required fields (domain, projectKey)`,
209
+ incrementId,
210
+ };
211
+ }
212
+
213
+ return {
214
+ success: true,
215
+ profile: {
216
+ profileName,
217
+ domain: profileConfig.config.domain,
218
+ projectKey: profileConfig.config.projectKey,
219
+ displayName: profileConfig.displayName,
220
+ boardId: profileConfig.config.boardId,
221
+ boardName: profileConfig.config.boardName,
222
+ instanceType: profileConfig.config.instanceType ?? 'cloud',
223
+ },
224
+ source: incrementProfile ? 'increment' : 'global',
225
+ incrementId,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Get increment's stored JIRA profile name
231
+ *
232
+ * @param incrementId - Increment ID
233
+ * @returns Profile name or null if not set
234
+ */
235
+ async getIncrementProfile(incrementId: string): Promise<string | null> {
236
+ const metadataPath = path.join(this.incrementsPath, incrementId, 'metadata.json');
237
+
238
+ try {
239
+ const content = await fs.readFile(metadataPath, 'utf-8');
240
+ const metadata = JSON.parse(content);
241
+
242
+ // Check for profile in external_sync.jira.profile (standardized naming)
243
+ // Also support legacy external_ids.jira for backwards compatibility
244
+ return metadata?.external_sync?.jira?.profile
245
+ || metadata?.external_ids?.jira?.profile
246
+ || null;
247
+ } catch {
248
+ // Metadata not found or invalid - return null
249
+ return null;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Get increment's stored JIRA issue key
255
+ *
256
+ * @param incrementId - Increment ID
257
+ * @returns Issue key (e.g., PROJ-123) or null if not linked
258
+ */
259
+ async getIncrementIssueKey(incrementId: string): Promise<string | null> {
260
+ const metadataPath = path.join(this.incrementsPath, incrementId, 'metadata.json');
261
+
262
+ try {
263
+ const content = await fs.readFile(metadataPath, 'utf-8');
264
+ const metadata = JSON.parse(content);
265
+
266
+ // Check both standardized and legacy paths
267
+ return metadata?.external_sync?.jira?.issueKey
268
+ || metadata?.external_ids?.jira?.epic
269
+ || metadata?.external_ids?.jira?.issueKey
270
+ || null;
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Set increment's JIRA profile
278
+ *
279
+ * Stores the profile name in the increment's metadata.json
280
+ *
281
+ * @param incrementId - Increment ID
282
+ * @param profileName - Profile name to set
283
+ */
284
+ async setIncrementProfile(incrementId: string, profileName: string): Promise<void> {
285
+ const metadataPath = path.join(this.incrementsPath, incrementId, 'metadata.json');
286
+
287
+ // Load existing metadata
288
+ let metadata: Record<string, unknown> = {};
289
+ try {
290
+ const content = await fs.readFile(metadataPath, 'utf-8');
291
+ metadata = JSON.parse(content);
292
+ } catch {
293
+ // Start with empty metadata if not found
294
+ }
295
+
296
+ // Set external_sync.jira.profile (standardized naming)
297
+ if (!metadata.external_sync) {
298
+ metadata.external_sync = {};
299
+ }
300
+ if (!(metadata.external_sync as Record<string, unknown>).jira) {
301
+ (metadata.external_sync as Record<string, unknown>).jira = {};
302
+ }
303
+ ((metadata.external_sync as Record<string, unknown>).jira as Record<string, unknown>).profile = profileName;
304
+
305
+ // Write back
306
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf-8');
307
+ }
308
+
309
+ /**
310
+ * Set increment's JIRA issue key
311
+ *
312
+ * @param incrementId - Increment ID
313
+ * @param issueKey - JIRA issue key (e.g., PROJ-123)
314
+ */
315
+ async setIncrementIssueKey(incrementId: string, issueKey: string): Promise<void> {
316
+ const metadataPath = path.join(this.incrementsPath, incrementId, 'metadata.json');
317
+
318
+ // Load existing metadata
319
+ let metadata: Record<string, unknown> = {};
320
+ try {
321
+ const content = await fs.readFile(metadataPath, 'utf-8');
322
+ metadata = JSON.parse(content);
323
+ } catch {
324
+ // Start with empty metadata if not found
325
+ }
326
+
327
+ // Set external_sync.jira.issueKey (standardized naming)
328
+ if (!metadata.external_sync) {
329
+ metadata.external_sync = {};
330
+ }
331
+ if (!(metadata.external_sync as Record<string, unknown>).jira) {
332
+ (metadata.external_sync as Record<string, unknown>).jira = {};
333
+ }
334
+ const jiraSync = (metadata.external_sync as Record<string, unknown>).jira as Record<string, unknown>;
335
+ jiraSync.issueKey = issueKey;
336
+ jiraSync.lastSyncedAt = new Date().toISOString();
337
+
338
+ // Write back
339
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf-8');
340
+ }
341
+
342
+ /**
343
+ * List available JIRA profiles
344
+ *
345
+ * @returns Array of profile names
346
+ */
347
+ async listJiraProfiles(): Promise<string[]> {
348
+ const config = await this.loadConfig();
349
+ if (!config?.sync?.profiles) {
350
+ return [];
351
+ }
352
+
353
+ const profiles = config.sync.profiles as Record<string, ConfigProfile>;
354
+ return Object.entries(profiles)
355
+ .filter(([_, p]) => p.provider === 'jira')
356
+ .map(([name]) => name);
357
+ }
358
+
359
+ /**
360
+ * Get profile details by name
361
+ *
362
+ * @param profileName - Profile name
363
+ * @returns Profile config or null
364
+ */
365
+ async getProfileByName(profileName: string): Promise<JiraProfileConfig | null> {
366
+ const config = await this.loadConfig();
367
+ if (!config?.sync?.profiles) {
368
+ return null;
369
+ }
370
+
371
+ const profileConfig = config.sync.profiles[profileName] as ConfigProfile | undefined;
372
+ if (!profileConfig || profileConfig.provider !== 'jira') {
373
+ return null;
374
+ }
375
+
376
+ return {
377
+ profileName,
378
+ domain: profileConfig.config.domain,
379
+ projectKey: profileConfig.config.projectKey,
380
+ displayName: profileConfig.displayName,
381
+ boardId: profileConfig.config.boardId,
382
+ boardName: profileConfig.config.boardName,
383
+ instanceType: profileConfig.config.instanceType ?? 'cloud',
384
+ };
385
+ }
386
+
387
+ /**
388
+ * Load config.json
389
+ */
390
+ private async loadConfig(): Promise<SpecWeaveConfig | null> {
391
+ try {
392
+ const content = await fs.readFile(this.configPath, 'utf-8');
393
+ return JSON.parse(content) as SpecWeaveConfig;
394
+ } catch {
395
+ return null;
396
+ }
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Create a JiraProfileResolver instance
402
+ *
403
+ * @param projectRoot - Project root directory (defaults to cwd)
404
+ * @returns JiraProfileResolver instance
405
+ */
406
+ export async function createJiraProfileResolver(
407
+ projectRoot: string = process.cwd()
408
+ ): Promise<JiraProfileResolver> {
409
+ return new JiraProfileResolver(projectRoot);
410
+ }
411
+
412
+ /**
413
+ * Quick resolve: Get JIRA profile for an increment
414
+ *
415
+ * @param incrementId - Increment ID
416
+ * @param projectRoot - Project root directory
417
+ * @returns Profile resolution result
418
+ */
419
+ export async function resolveJiraProfile(
420
+ incrementId: string,
421
+ projectRoot: string = process.cwd()
422
+ ): Promise<ProfileResolutionResult> {
423
+ const resolver = new JiraProfileResolver(projectRoot);
424
+ return resolver.resolveProfile(incrementId);
425
+ }
426
+
427
+ export default JiraProfileResolver;
@@ -41,16 +41,21 @@ This is the **MOST CRITICAL** mapping. Never deviate from this 1:1 relationship.
41
41
  ```yaml
42
42
  # .specweave/increments/####-{name}/metadata.json
43
43
  {
44
- "external_ids": {
44
+ "external_sync": {
45
45
  "jira": {
46
- "epic": "PROJ-123",
47
- "epic_url": "https://company.atlassian.net/browse/PROJ-123",
48
- "project_key": "PROJ"
46
+ "profile": "jira-my-project",
47
+ "issueKey": "PROJ-123",
48
+ "issueUrl": "https://company.atlassian.net/browse/PROJ-123",
49
+ "projectKey": "PROJ",
50
+ "lastSyncedAt": "2025-12-07T04:30:00Z"
49
51
  }
50
52
  }
51
53
  }
52
54
  ```
53
55
 
56
+ **NOTE (v0.32.0+)**: Standardized to `external_sync.jira.*` for consistency with ADO (`external_sync.ado.*`).
57
+ Legacy `external_ids.jira.*` is still supported for backwards compatibility.
58
+
54
59
  **Example**:
55
60
  ```
56
61
  Jira Epic: PROJ-123 "User Authentication"
@@ -180,12 +185,12 @@ SpecWeave: .specweave/docs/internal/delivery/release-v1.0.md
180
185
  ```yaml
181
186
  # metadata.json
182
187
  {
183
- "external_ids": {
188
+ "external_sync": {
184
189
  "jira": {
185
190
  "tasks": [
186
- {"id": "T-001", "jira_key": "PROJ-126"},
187
- {"id": "T-002", "jira_key": "PROJ-127"},
188
- {"id": "T-003", "jira_key": "PROJ-128"}
191
+ {"id": "T-001", "issueKey": "PROJ-126"},
192
+ {"id": "T-002", "issueKey": "PROJ-127"},
193
+ {"id": "T-003", "issueKey": "PROJ-128"}
189
194
  ]
190
195
  }
191
196
  }
@@ -264,7 +269,7 @@ SpecWeave: .specweave/docs/internal/architecture/frontend-auth/
264
269
  3. SpecWeave creates new increment:
265
270
  ```bash
266
271
  .specweave/increments/0001-user-authentication/
267
- ├── metadata.json # external_ids.jira.epic = PROJ-123
272
+ ├── metadata.json # external_sync.jira.issueKey = PROJ-123
268
273
  ├── spec.md
269
274
  ├── plan.md
270
275
  └── tasks.md
@@ -301,7 +306,7 @@ SpecWeave: .specweave/docs/internal/architecture/frontend-auth/
301
306
  4. SpecWeave updates `metadata.json`:
302
307
  ```json
303
308
  {
304
- "external_ids": {
309
+ "external_sync": {
305
310
  "jira": {
306
311
  "stories": ["PROJ-124"]
307
312
  }
@@ -330,7 +335,7 @@ SpecWeave: .specweave/docs/internal/architecture/frontend-auth/
330
335
  6. SpecWeave updates `metadata.json`:
331
336
  ```json
332
337
  {
333
- "external_ids": {
338
+ "external_sync": {
334
339
  "jira": {
335
340
  "stories": ["PROJ-124", "PROJ-131"]
336
341
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: specweave-release:npm
3
- description: Bump patch version, auto-commit dirty changes, push to GitHub, build, publish to npmjs.org. Use --ci for GitHub Actions publish. Use --only for local publish without git push. Use --only --local for version bump only.
3
+ description: Bump patch version, auto-commit dirty changes, push to GitHub, build, publish to npmjs.org. Use --quick for save+release (no GH workflow). Use --ci for GitHub Actions publish. Use --only for local publish without git push. Use --only --local for version bump only.
4
4
  ---
5
5
 
6
6
  # /specweave-release:npm - NPM Release Automation
@@ -12,6 +12,7 @@ You are the NPM Release Assistant. Your job is to automate the patch version rel
12
12
  | Command | Flow | Use Case |
13
13
  |---------|------|----------|
14
14
  | `/specweave-release:npm` | Auto-commit → **PUSH** → Bump → Build → **Publish** → Push tag | **DEFAULT: INSTANT RELEASE** |
15
+ | `/specweave-release:npm --quick` | Auto-commit → **PUSH** → Bump → Build → **Publish locally** → NO GH workflow | **QUICK: Save + Local Release** |
15
16
  | `/specweave-release:npm --ci` | Bump → Push → **CI publishes** | Let GitHub Actions handle npm publish |
16
17
  | `/specweave-release:npm --only` | Bump → Build → **Publish locally** → NO push | Quick local release, push later |
17
18
  | `/specweave-release:npm --only --local` | **Bump ONLY** → NO build, NO publish, NO git | FASTEST: Local testing only |
@@ -21,6 +22,7 @@ You are the NPM Release Assistant. Your job is to automate the patch version rel
21
22
  Check flags in the command invocation:
22
23
 
23
24
  ```
25
+ --quick → QUICK MODE: save (commit+push) + local npm publish (NO GH workflow trigger)
24
26
  --ci → CI MODE: push to git, GitHub Actions publishes (requires clean working tree)
25
27
  --only --local → Version bump ONLY (no build, no publish, no git) - FASTEST
26
28
  --only → Direct publish to npm (bypass CI), no git push
@@ -28,12 +30,14 @@ Check flags in the command invocation:
28
30
  ```
29
31
 
30
32
  **Flag Detection Order:**
31
- 1. Check for `--ci` flag → CI MODE (GitHub Actions publishes)
32
- 2. Check for `--only` flag
33
- 3. If `--only` present, check for `--local` flag → LOCAL MODE (fastest)
34
- 4. If `--only` onlyDIRECT MODE
35
- 5. No flags**DEFAULT: INSTANT RELEASE** (auto-commit dirty, push, build, publish)
36
-
33
+ 1. Check for `--quick` flag → QUICK MODE (save + local publish, NO GH workflow)
34
+ 2. Check for `--ci` flag → CI MODE (GitHub Actions publishes)
35
+ 3. Check for `--only` flag
36
+ 4. If `--only` present, check for `--local` flag LOCAL MODE (fastest)
37
+ 5. If `--only` only DIRECT MODE
38
+ 6. No flags → **DEFAULT: INSTANT RELEASE** (auto-commit dirty, push, build, publish)
39
+
40
+ **If `--quick`**: Use QUICK MODE (section "Quick Mode Workflow")
37
41
  **If `--ci`**: Use CI MODE (section "CI Mode Workflow")
38
42
  **If `--only --local`**: Use LOCAL MODE (section "Local Mode Workflow") - FASTEST!
39
43
  **If `--only` only**: Use DIRECT MODE (section "Direct Mode Workflow")
@@ -160,6 +164,124 @@ git push origin develop --follow-tags
160
164
 
161
165
  ---
162
166
 
167
+ ## QUICK MODE WORKFLOW (--quick flag) - SAVE + LOCAL RELEASE
168
+
169
+ Use this workflow when `--quick` flag is detected. This combines `/specweave:save` behavior with local npm publish. **NO GitHub workflow trigger** - everything happens locally.
170
+
171
+ **Use case**: You want to quickly save your work AND release a new patch version without waiting for GitHub Actions. Perfect for:
172
+ - Hotfixes that need immediate npm availability
173
+ - Iterative releases during active development
174
+ - When GitHub Actions are slow or unavailable
175
+
176
+ **Key difference from DEFAULT mode**: Does NOT push the version tag, so GitHub Actions release workflow is NOT triggered.
177
+
178
+ ### 1. Pre-flight Check
179
+
180
+ ```bash
181
+ # Verify we're on develop branch
182
+ git rev-parse --abbrev-ref HEAD
183
+
184
+ # Get current version
185
+ node -p "require('./package.json').version"
186
+ ```
187
+
188
+ **STOP if**: Not on `develop` branch (ask user to switch)
189
+
190
+ ### 2. Auto-Commit Dirty Changes (if any)
191
+
192
+ ```bash
193
+ # Check for uncommitted changes
194
+ git status --porcelain
195
+ ```
196
+
197
+ If dirty, generate smart commit message and commit:
198
+
199
+ ```bash
200
+ git add -A
201
+ git commit -m "[auto-generated message based on changed files]"
202
+ ```
203
+
204
+ **Message generation rules:**
205
+ - `src/**` changes → `fix: update implementation`
206
+ - `plugins/**` changes → `feat(plugins): update plugin`
207
+ - `.specweave/**` changes → `chore: update specweave config`
208
+ - `*.md` changes → `docs: update documentation`
209
+ - Mixed → `chore: update code and documentation`
210
+
211
+ ### 3. Push Dirty Commit to Remote
212
+
213
+ ```bash
214
+ # Push dirty commit to remote - ensures code is safe before release
215
+ git push origin develop
216
+ ```
217
+
218
+ ### 4. Bump Patch Version
219
+
220
+ ```bash
221
+ npm version patch -m "chore: bump version to %s"
222
+ ```
223
+
224
+ This creates a NEW commit + tag locally.
225
+
226
+ ### 5. Build Package
227
+
228
+ ```bash
229
+ npm run rebuild
230
+ ```
231
+
232
+ ### 6. Publish to NPM Locally
233
+
234
+ ```bash
235
+ # Publish directly to npmjs.org (NO GitHub Actions!)
236
+ npm publish --registry https://registry.npmjs.org
237
+ ```
238
+
239
+ ### 7. Push Version Commit ONLY (NO tag!)
240
+
241
+ ```bash
242
+ # Push ONLY the version commit, NOT the tag
243
+ # This prevents GitHub Actions release workflow from triggering
244
+ git push origin develop
245
+ ```
246
+
247
+ **CRITICAL**: Do NOT use `--follow-tags`! We want local npm publish only.
248
+
249
+ ### 8. Report Results
250
+
251
+ ```markdown
252
+ ✅ **Quick release complete!**
253
+
254
+ 📦 **Version**: vX.Y.Z
255
+ 🔗 **NPM**: https://www.npmjs.com/package/specweave
256
+ 🏷️ **Git Tag**: vX.Y.Z (local only - NOT pushed)
257
+
258
+ **What happened**:
259
+ - ✅ Dirty changes auto-committed
260
+ - ✅ Pushed to GitHub (code safe!)
261
+ - ✅ Version bumped to X.Y.Z
262
+ - ✅ Package built
263
+ - ✅ Published to npmjs.org (locally)
264
+ - ⏭️ Tag NOT pushed (no GitHub Actions triggered)
265
+
266
+ **Verify**: `npm view specweave version --registry https://registry.npmjs.org`
267
+
268
+ **If you want to push the tag later** (triggers GH release):
269
+ `git push origin vX.Y.Z`
270
+ ```
271
+
272
+ ## Quick Mode Success Criteria
273
+
274
+ ✅ Any dirty changes auto-committed
275
+ ✅ Dirty commit pushed to remote
276
+ ✅ Version bumped in package.json
277
+ ✅ Git commit and tag created locally
278
+ ✅ Package rebuilt
279
+ ✅ Published to npmjs.org (explicit registry!)
280
+ ✅ Version commit pushed to GitHub
281
+ ⏭️ Tag NOT pushed (no GitHub Actions)
282
+
283
+ ---
284
+
163
285
  ## CI MODE WORKFLOW (--ci flag)
164
286
 
165
287
  Use this workflow when `--ci` flag is detected. Push to git and let GitHub Actions handle npm publish.
@@ -337,9 +459,12 @@ Show the user:
337
459
  ## Quick Reference
338
460
 
339
461
  ```bash
340
- # DEFAULT: Instant release (auto-commits dirty, publishes, pushes)
462
+ # DEFAULT: Instant release (auto-commits dirty, publishes, pushes + tag)
341
463
  /specweave-release:npm
342
464
 
465
+ # QUICK: Save + local release (auto-commits, pushes, publishes - NO GH workflow)
466
+ /specweave-release:npm --quick
467
+
343
468
  # CI release (GitHub Actions handles npm publish) - requires clean tree
344
469
  /specweave-release:npm --ci
345
470
 
@@ -350,12 +475,13 @@ Show the user:
350
475
  /specweave-release:npm --only --local
351
476
  ```
352
477
 
353
- | Scenario | Command | Auto-Commit Dirty? | NPM Published By | Git Pushed |
354
- |----------|---------|-------------------|------------------|------------|
355
- | **INSTANT RELEASE** | (no flags) | ✅ Yes | You (npmjs.org) | ✅ Yes |
356
- | CI release | `--ci` | STOP | GitHub Actions | ✅ Yes |
357
- | Quick local, push later | `--only` | ❌ STOP | You (npmjs.org) | No |
358
- | **FASTEST local test** | `--only --local` | N/A | ❌ None | ❌ No |
478
+ | Scenario | Command | Auto-Commit Dirty? | NPM Published By | Git Pushed | Tag Pushed |
479
+ |----------|---------|-------------------|------------------|------------|------------|
480
+ | **INSTANT RELEASE** | (no flags) | ✅ Yes | You (npmjs.org) | ✅ Yes | ✅ Yes (triggers GH) |
481
+ | **QUICK RELEASE** | `--quick` | Yes | You (npmjs.org) | ✅ Yes | ❌ No (no GH workflow) |
482
+ | CI release | `--ci` | ❌ STOP | GitHub Actions | Yes | ✅ Yes |
483
+ | Quick local, push later | `--only` | STOP | You (npmjs.org) | No | ❌ No |
484
+ | **FASTEST local test** | `--only --local` | N/A | ❌ None | ❌ No | ❌ No |
359
485
 
360
486
  ---
361
487