specweave 1.0.319 → 1.0.321

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.319",
3
+ "version": "1.0.321",
4
4
  "description": "Spec-driven development framework for AI coding agents. Works with Claude Code, Codex, Antigravity, Cursor, Copilot & more. 100+ skills, 49 CLI commands, verified skill certification, autonomous execution, and living documentation.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -95,6 +95,9 @@ command -v jq >/dev/null 2>&1 || exit 0
95
95
 
96
96
  # Use shared provider detection (supports PROFILES, LEGACY DIRECT, LEGACY PROVIDER formats)
97
97
  HANDLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
98
+ # Derive package root from script location (works for both npm install and dev):
99
+ # handlers/ → v2/ → hooks/ → specweave/ → plugins/ → package root (5 levels up)
100
+ PKG_ROOT="$(cd "$HANDLER_DIR/../../../../.." 2>/dev/null && pwd)"
98
101
  SHARED_LIB="$HANDLER_DIR/../lib/check-provider-enabled.sh"
99
102
  if [[ -f "$SHARED_LIB" ]]; then
100
103
  source "$SHARED_LIB"
@@ -134,19 +137,26 @@ log "Creating external items for $INC_ID (github=$GH_ENABLED jira=$JIRA_ENABLED
134
137
  # ============================================================================
135
138
 
136
139
  if [[ "$GH_ENABLED" == "true" ]]; then
137
- GITHUB_HANDLER="${PROJECT_ROOT}/plugins/specweave-github/hooks/github-auto-create-handler.sh"
138
- # Fallback: handler may be in a sibling plugin directory (umbrella repo layout)
139
- if [[ ! -f "$GITHUB_HANDLER" ]]; then
140
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
141
- FALLBACK_DIR="$(cd "$SCRIPT_DIR/../../../specweave-github/hooks" 2>/dev/null && pwd)"
142
- [[ -n "$FALLBACK_DIR" ]] && GITHUB_HANDLER="$FALLBACK_DIR/github-auto-create-handler.sh"
140
+ GITHUB_HANDLER=""
141
+ # 1. Sibling plugin dir handlers/../../../../ = plugins/ (works for both npm and dev)
142
+ CANDIDATE="$HANDLER_DIR/../../../../specweave-github/hooks/github-auto-create-handler.sh"
143
+ [[ -f "$CANDIDATE" ]] && GITHUB_HANDLER="$CANDIDATE"
144
+ # 2. Fallback: PROJECT_ROOT/plugins/ (dev-only, when running from source repo)
145
+ if [[ -z "$GITHUB_HANDLER" ]]; then
146
+ CANDIDATE="${PROJECT_ROOT}/plugins/specweave-github/hooks/github-auto-create-handler.sh"
147
+ [[ -f "$CANDIDATE" ]] && GITHUB_HANDLER="$CANDIDATE"
143
148
  fi
144
- if [[ -f "$GITHUB_HANDLER" ]]; then
145
- log "Delegating GitHub auto-create to existing handler"
149
+ # 3. Fallback: node_modules/specweave/ (direct npm install)
150
+ if [[ -z "$GITHUB_HANDLER" ]]; then
151
+ CANDIDATE="${PROJECT_ROOT}/node_modules/specweave/plugins/specweave-github/hooks/github-auto-create-handler.sh"
152
+ [[ -f "$CANDIDATE" ]] && GITHUB_HANDLER="$CANDIDATE"
153
+ fi
154
+ if [[ -n "$GITHUB_HANDLER" ]]; then
155
+ log "Delegating GitHub auto-create to handler at $GITHUB_HANDLER"
146
156
  bash "$GITHUB_HANDLER" "$INC_ID" &
147
157
  GH_PID=$!
148
158
  else
149
- log "GitHub handler not found at any known path"
159
+ log "GitHub handler not found at any known path (checked sibling, PROJECT_ROOT, node_modules)"
150
160
  fi
151
161
  fi
152
162
 
@@ -157,9 +167,39 @@ fi
157
167
  if [[ "$JIRA_ENABLED" == "true" || "$ADO_ENABLED" == "true" ]]; then
158
168
  command -v node &>/dev/null || { log "Node.js not found. Skipping JIRA/ADO."; exit 0; }
159
169
 
160
- CREATE_MODULE="$PROJECT_ROOT/dist/src/core/universal-auto-create.js"
161
- if [[ ! -f "$CREATE_MODULE" ]]; then
162
- log "Module not found at $CREATE_MODULE. Skipping JIRA/ADO."
170
+ # Resolve universal-auto-create.js from multiple locations
171
+ CREATE_MODULE=""
172
+ # 0. SPECWEAVE_PKG (set by Claude Code hook infrastructure, most reliable)
173
+ if [[ -n "${SPECWEAVE_PKG:-}" ]]; then
174
+ CANDIDATE="${SPECWEAVE_PKG}/dist/src/core/universal-auto-create.js"
175
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
176
+ fi
177
+ # 1. Package dist/ (derived from script location — works for both npm and dev)
178
+ if [[ -z "$CREATE_MODULE" ]]; then
179
+ CANDIDATE="${PKG_ROOT:-$PROJECT_ROOT}/dist/src/core/universal-auto-create.js"
180
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
181
+ fi
182
+ # 2. Vendor directory (self-contained plugin)
183
+ # handlers/../../../ = plugins/specweave/ (3 levels up), then lib/vendor/
184
+ # NOTE: vendor path only works for GitHub-only users; JIRA/ADO dynamic imports
185
+ # (jira-client.js, ado-client.js) are NOT vendored and will fail if this path is used.
186
+ if [[ -z "$CREATE_MODULE" ]]; then
187
+ CANDIDATE="$HANDLER_DIR/../../../lib/vendor/core/universal-auto-create.js"
188
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$(cd "$(dirname "$CANDIDATE")" && pwd)/$(basename "$CANDIDATE")"
189
+ fi
190
+ # 3. node_modules/specweave/ (direct npm install)
191
+ if [[ -z "$CREATE_MODULE" ]]; then
192
+ CANDIDATE="${PROJECT_ROOT}/node_modules/specweave/dist/src/core/universal-auto-create.js"
193
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
194
+ fi
195
+ # 4. Legacy: PROJECT_ROOT/dist/ (dev-only, running from source repo)
196
+ if [[ -z "$CREATE_MODULE" ]]; then
197
+ CANDIDATE="${PROJECT_ROOT}/dist/src/core/universal-auto-create.js"
198
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
199
+ fi
200
+
201
+ if [[ -z "$CREATE_MODULE" ]]; then
202
+ log "Module not found at any known path. Skipping JIRA/ADO. Checked: PKG_ROOT=${PKG_ROOT:-unset}, node_modules, vendor, PROJECT_ROOT"
163
203
  else
164
204
  # Build config JSON for the TypeScript module
165
205
  JIRA_DOMAIN=$(jq -r '.sync.jira.domain // .issueTracker.domain // ""' "$CONFIG_PATH" 2>/dev/null)
@@ -215,7 +255,9 @@ fi
215
255
 
216
256
  # Wait for GitHub handler if started
217
257
  if [[ -n "${GH_PID:-}" ]]; then
218
- wait "$GH_PID" 2>/dev/null || true
258
+ wait "$GH_PID" 2>/dev/null
259
+ GH_EXIT=$?
260
+ [[ $GH_EXIT -ne 0 ]] && log "GitHub handler exited with code $GH_EXIT (check github-auto-create.log for details)"
219
261
  fi
220
262
 
221
263
  log "Universal auto-create finished for $INC_ID"
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Universal Auto-Create for External Tools
3
+ *
4
+ * Creates per-user-story items in ALL enabled providers (JIRA, ADO).
5
+ * GitHub is handled separately by the existing github-auto-create-handler.sh.
6
+ *
7
+ * Called by universal-auto-create-dispatcher.sh when spec.md is written.
8
+ *
9
+ * @module universal-auto-create
10
+ */
11
+ export interface UniversalAutoCreateConfig {
12
+ sync: {
13
+ jira?: {
14
+ enabled?: boolean;
15
+ };
16
+ ado?: {
17
+ enabled?: boolean;
18
+ };
19
+ };
20
+ jira?: {
21
+ domain?: string;
22
+ projectKey?: string;
23
+ };
24
+ ado?: {
25
+ organization?: string;
26
+ project?: string;
27
+ };
28
+ }
29
+ export interface UserStoryInfo {
30
+ id: string;
31
+ title: string;
32
+ }
33
+ export interface ProviderCreateResult {
34
+ created: Array<{
35
+ usId: string;
36
+ ref: string;
37
+ url: string;
38
+ }>;
39
+ skipped: Array<{
40
+ usId: string;
41
+ reason: string;
42
+ }>;
43
+ errors: Array<{
44
+ usId: string;
45
+ error: string;
46
+ }>;
47
+ }
48
+ export type UniversalAutoCreateResult = {
49
+ jira?: ProviderCreateResult;
50
+ ado?: ProviderCreateResult;
51
+ };
52
+ /**
53
+ * Parse user stories (ID + title) from spec.md content.
54
+ * Matches patterns like "### US-001: User story title"
55
+ */
56
+ export declare function parseUserStories(content: string): UserStoryInfo[];
57
+ /**
58
+ * Create per-user-story items in JIRA and/or ADO for an increment.
59
+ *
60
+ * GitHub is NOT handled here (delegated to existing bash handler).
61
+ * This function is called by the universal-auto-create-dispatcher.sh.
62
+ */
63
+ export declare function createExternalIssuesForIncrement(incrementId: string, specPath: string, metadataPath: string, config: UniversalAutoCreateConfig): Promise<UniversalAutoCreateResult>;
64
+ //# sourceMappingURL=universal-auto-create.d.ts.map
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Universal Auto-Create for External Tools
3
+ *
4
+ * Creates per-user-story items in ALL enabled providers (JIRA, ADO).
5
+ * GitHub is handled separately by the existing github-auto-create-handler.sh.
6
+ *
7
+ * Called by universal-auto-create-dispatcher.sh when spec.md is written.
8
+ *
9
+ * @module universal-auto-create
10
+ */
11
+ import { readFile, writeFile } from 'fs/promises';
12
+ import { existsSync } from 'fs';
13
+ import { deriveFeatureId } from '../utils/feature-id-derivation.js';
14
+ // ============================================================================
15
+ // Helpers
16
+ // ============================================================================
17
+ function emptyResult() {
18
+ return { created: [], skipped: [], errors: [] };
19
+ }
20
+ /**
21
+ * Parse user stories (ID + title) from spec.md content.
22
+ * Matches patterns like "### US-001: User story title"
23
+ */
24
+ export function parseUserStories(content) {
25
+ const stories = [];
26
+ const pattern = /###\s+(US-\d+):\s*(.+)/g;
27
+ let match;
28
+ while ((match = pattern.exec(content)) !== null) {
29
+ stories.push({ id: match[1], title: match[2].trim() });
30
+ }
31
+ return stories;
32
+ }
33
+ // ============================================================================
34
+ // Provider Functions
35
+ // ============================================================================
36
+ async function createJiraUserStories(incrementId, featureId, userStories, existingLinks, config) {
37
+ const result = emptyResult();
38
+ const { JiraClient } = await import('../integrations/jira/jira-client.js');
39
+ const domain = config.jira?.domain || '';
40
+ const projectKey = config.jira?.projectKey || '';
41
+ if (!domain || !projectKey) {
42
+ result.errors.push({ usId: 'config', error: 'JIRA domain or projectKey not configured' });
43
+ return result;
44
+ }
45
+ const client = new JiraClient();
46
+ for (const us of userStories) {
47
+ // Idempotency: skip if already linked
48
+ if (existingLinks[us.id]) {
49
+ result.skipped.push({ usId: us.id, reason: 'already-linked' });
50
+ continue;
51
+ }
52
+ try {
53
+ const summary = `[${featureId}][${us.id}] ${us.title}`;
54
+ const issue = await client.createIssue({
55
+ issueType: 'Story',
56
+ summary,
57
+ labels: ['specweave', 'auto-created'],
58
+ }, projectKey);
59
+ const issueUrl = `https://${domain}/browse/${issue.key}`;
60
+ result.created.push({ usId: us.id, ref: issue.key, url: issueUrl });
61
+ }
62
+ catch (err) {
63
+ const msg = err instanceof Error ? err.message : String(err);
64
+ result.errors.push({ usId: us.id, error: msg });
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+ async function createAdoUserStories(incrementId, featureId, userStories, existingLinks, config) {
70
+ const result = emptyResult();
71
+ const { AdoClient } = await import('../integrations/ado/ado-client.js');
72
+ const { getAdoPat } = await import('../integrations/ado/ado-pat-provider.js');
73
+ const organization = config.ado?.organization || '';
74
+ const project = config.ado?.project || '';
75
+ if (!organization || !project) {
76
+ result.errors.push({ usId: 'config', error: 'ADO organization or project not configured' });
77
+ return result;
78
+ }
79
+ const pat = getAdoPat(organization);
80
+ if (!pat) {
81
+ result.errors.push({ usId: 'config', error: 'AZURE_DEVOPS_PAT not set' });
82
+ return result;
83
+ }
84
+ const client = new AdoClient({ pat, organization, project });
85
+ for (const us of userStories) {
86
+ // Idempotency: skip if already linked
87
+ if (existingLinks[us.id]) {
88
+ result.skipped.push({ usId: us.id, reason: 'already-linked' });
89
+ continue;
90
+ }
91
+ try {
92
+ const title = `[${featureId}][${us.id}] ${us.title}`;
93
+ const workItem = await client.createWorkItem({
94
+ workItemType: 'User Story',
95
+ title,
96
+ tags: ['specweave', 'auto-created'],
97
+ });
98
+ result.created.push({
99
+ usId: us.id,
100
+ ref: String(workItem.id),
101
+ url: workItem.url,
102
+ });
103
+ }
104
+ catch (err) {
105
+ const msg = err instanceof Error ? err.message : String(err);
106
+ result.errors.push({ usId: us.id, error: msg });
107
+ }
108
+ }
109
+ return result;
110
+ }
111
+ // ============================================================================
112
+ // Metadata Update
113
+ // ============================================================================
114
+ async function updateMetadata(metadataPath, results) {
115
+ if (!existsSync(metadataPath))
116
+ return;
117
+ const raw = await readFile(metadataPath, 'utf-8');
118
+ const metadata = JSON.parse(raw);
119
+ if (!metadata.externalLinks) {
120
+ metadata.externalLinks = {};
121
+ }
122
+ const now = new Date().toISOString();
123
+ // Update JIRA links
124
+ if (results.jira && results.jira.created.length > 0) {
125
+ if (!metadata.externalLinks.jira) {
126
+ metadata.externalLinks.jira = { userStories: {} };
127
+ }
128
+ if (!metadata.externalLinks.jira.userStories) {
129
+ metadata.externalLinks.jira.userStories = {};
130
+ }
131
+ for (const item of results.jira.created) {
132
+ metadata.externalLinks.jira.userStories[item.usId] = {
133
+ issueKey: item.ref,
134
+ issueUrl: item.url,
135
+ syncedAt: now,
136
+ };
137
+ }
138
+ }
139
+ // Update ADO links
140
+ if (results.ado && results.ado.created.length > 0) {
141
+ if (!metadata.externalLinks.ado) {
142
+ metadata.externalLinks.ado = { userStories: {} };
143
+ }
144
+ if (!metadata.externalLinks.ado.userStories) {
145
+ metadata.externalLinks.ado.userStories = {};
146
+ }
147
+ for (const item of results.ado.created) {
148
+ metadata.externalLinks.ado.userStories[item.usId] = {
149
+ workItemId: parseInt(item.ref, 10),
150
+ workItemUrl: item.url,
151
+ syncedAt: now,
152
+ };
153
+ }
154
+ }
155
+ await writeFile(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf-8');
156
+ }
157
+ // ============================================================================
158
+ // Main Entry Point
159
+ // ============================================================================
160
+ /**
161
+ * Create per-user-story items in JIRA and/or ADO for an increment.
162
+ *
163
+ * GitHub is NOT handled here (delegated to existing bash handler).
164
+ * This function is called by the universal-auto-create-dispatcher.sh.
165
+ */
166
+ export async function createExternalIssuesForIncrement(incrementId, specPath, metadataPath, config) {
167
+ const result = {};
168
+ // Parse user stories from spec.md
169
+ const content = await readFile(specPath, 'utf-8');
170
+ const userStories = parseUserStories(content);
171
+ if (userStories.length === 0) {
172
+ return result;
173
+ }
174
+ // Derive feature ID
175
+ let featureId;
176
+ try {
177
+ featureId = deriveFeatureId(incrementId);
178
+ }
179
+ catch {
180
+ const numMatch = incrementId.match(/^(\d+)/);
181
+ featureId = numMatch ? `FS-${parseInt(numMatch[1], 10)}` : `FS-${incrementId.substring(0, 3)}`;
182
+ }
183
+ // Load existing external links from metadata (for idempotency)
184
+ let existingJiraLinks = {};
185
+ let existingAdoLinks = {};
186
+ if (existsSync(metadataPath)) {
187
+ try {
188
+ const metaRaw = await readFile(metadataPath, 'utf-8');
189
+ const metadata = JSON.parse(metaRaw);
190
+ existingJiraLinks = metadata.externalLinks?.jira?.userStories || {};
191
+ existingAdoLinks = metadata.externalLinks?.ado?.userStories || {};
192
+ }
193
+ catch {
194
+ // Metadata parse error — proceed without existing links
195
+ }
196
+ }
197
+ // Create in each enabled provider (isolated errors)
198
+ if (config.sync?.jira?.enabled) {
199
+ try {
200
+ result.jira = await createJiraUserStories(incrementId, featureId, userStories, existingJiraLinks, config);
201
+ }
202
+ catch (err) {
203
+ const providerResult = emptyResult();
204
+ providerResult.errors.push({
205
+ usId: 'unknown',
206
+ error: err instanceof Error ? err.message : String(err),
207
+ });
208
+ result.jira = providerResult;
209
+ }
210
+ }
211
+ if (config.sync?.ado?.enabled) {
212
+ try {
213
+ result.ado = await createAdoUserStories(incrementId, featureId, userStories, existingAdoLinks, config);
214
+ }
215
+ catch (err) {
216
+ const providerResult = emptyResult();
217
+ providerResult.errors.push({
218
+ usId: 'unknown',
219
+ error: err instanceof Error ? err.message : String(err),
220
+ });
221
+ result.ado = providerResult;
222
+ }
223
+ }
224
+ // Update metadata with new links
225
+ await updateMetadata(metadataPath, result);
226
+ return result;
227
+ }
228
+ //# sourceMappingURL=universal-auto-create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"universal-auto-create.js","sourceRoot":"","sources":["../../../src/core/universal-auto-create.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Feature ID Derivation Utility
3
+ *
4
+ * Derives feature ID from increment ID using the 1:1 mapping principle:
5
+ * - Each increment maps to exactly one feature folder
6
+ * - Feature ID = FS-{increment_number} or FS-{increment_number}E for external
7
+ *
8
+ * @example
9
+ * deriveFeatureId('0081-ado-repo-cloning') → 'FS-081'
10
+ * deriveFeatureId('0100-some-feature') → 'FS-100'
11
+ * deriveFeatureId('1000-future-feature') → 'FS-1000'
12
+ * deriveFeatureId('0111E-external-issue') → 'FS-111E'
13
+ *
14
+ * @see ADR-0140 for the decision rationale
15
+ */
16
+ /**
17
+ * Derive feature ID from increment ID
18
+ *
19
+ * The feature ID is derived directly from the increment number:
20
+ * - Extract leading digits from increment ID
21
+ * - Format as FS-XXX (minimum 3 digits, more if needed)
22
+ * - Add 'E' suffix for external increments (e.g., 0111E-...)
23
+ *
24
+ * This eliminates the need to store feature_id in metadata.json,
25
+ * as it's 100% derivable from the increment ID.
26
+ *
27
+ * CRITICAL: External increments (with E suffix like 0111E-...)
28
+ * MUST map to external features (FS-111E), not internal ones (FS-111).
29
+ *
30
+ * @param incrementId - Increment ID (e.g., "0081-ado-repo-cloning" or "0111E-external-issue")
31
+ * @returns Feature ID (e.g., "FS-081" or "FS-111E")
32
+ * @throws Error if increment ID format is invalid
33
+ */
34
+ export declare function deriveFeatureId(incrementId: string): string;
35
+ /**
36
+ * Extract increment number from increment ID
37
+ *
38
+ * @param incrementId - Increment ID (e.g., "0081-ado-repo-cloning")
39
+ * @returns Increment number (e.g., 81)
40
+ * @throws Error if increment ID format is invalid
41
+ */
42
+ export declare function extractIncrementNumber(incrementId: string): number;
43
+ /**
44
+ * Validate feature ID format
45
+ *
46
+ * Valid formats:
47
+ * - FS-XXX (internal, 3+ digits)
48
+ * - FS-XXXE (external, 3+ digits with E suffix)
49
+ *
50
+ * @param featureId - Feature ID to validate
51
+ * @returns true if valid, false otherwise
52
+ */
53
+ export declare function isValidFeatureId(featureId: string): boolean;
54
+ /**
55
+ * Check if feature ID is external (imported)
56
+ *
57
+ * External features have an 'E' suffix: FS-042E
58
+ *
59
+ * @param featureId - Feature ID to check
60
+ * @returns true if external, false if internal
61
+ */
62
+ export declare function isExternalFeatureId(featureId: string): boolean;
63
+ //# sourceMappingURL=feature-id-derivation.d.ts.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Feature ID Derivation Utility
3
+ *
4
+ * Derives feature ID from increment ID using the 1:1 mapping principle:
5
+ * - Each increment maps to exactly one feature folder
6
+ * - Feature ID = FS-{increment_number} or FS-{increment_number}E for external
7
+ *
8
+ * @example
9
+ * deriveFeatureId('0081-ado-repo-cloning') → 'FS-081'
10
+ * deriveFeatureId('0100-some-feature') → 'FS-100'
11
+ * deriveFeatureId('1000-future-feature') → 'FS-1000'
12
+ * deriveFeatureId('0111E-external-issue') → 'FS-111E'
13
+ *
14
+ * @see ADR-0140 for the decision rationale
15
+ */
16
+ /**
17
+ * Derive feature ID from increment ID
18
+ *
19
+ * The feature ID is derived directly from the increment number:
20
+ * - Extract leading digits from increment ID
21
+ * - Format as FS-XXX (minimum 3 digits, more if needed)
22
+ * - Add 'E' suffix for external increments (e.g., 0111E-...)
23
+ *
24
+ * This eliminates the need to store feature_id in metadata.json,
25
+ * as it's 100% derivable from the increment ID.
26
+ *
27
+ * CRITICAL: External increments (with E suffix like 0111E-...)
28
+ * MUST map to external features (FS-111E), not internal ones (FS-111).
29
+ *
30
+ * @param incrementId - Increment ID (e.g., "0081-ado-repo-cloning" or "0111E-external-issue")
31
+ * @returns Feature ID (e.g., "FS-081" or "FS-111E")
32
+ * @throws Error if increment ID format is invalid
33
+ */
34
+ export function deriveFeatureId(incrementId) {
35
+ // Match number prefix and optional E suffix
36
+ const match = incrementId.match(/^(\d+)(E)?/);
37
+ if (!match) {
38
+ throw new Error(`Invalid increment ID format: ${incrementId}. Expected format: NNNN-name or NNNNE-name`);
39
+ }
40
+ const num = parseInt(match[1], 10);
41
+ const isExternal = match[2] === 'E';
42
+ // padStart(3, '0') ensures minimum 3 digits (FS-001 to FS-999)
43
+ // Numbers 1000+ naturally have 4+ digits (FS-1000, FS-1001, etc.)
44
+ const featureId = `FS-${String(num).padStart(3, '0')}`;
45
+ return isExternal ? `${featureId}E` : featureId;
46
+ }
47
+ /**
48
+ * Extract increment number from increment ID
49
+ *
50
+ * @param incrementId - Increment ID (e.g., "0081-ado-repo-cloning")
51
+ * @returns Increment number (e.g., 81)
52
+ * @throws Error if increment ID format is invalid
53
+ */
54
+ export function extractIncrementNumber(incrementId) {
55
+ const match = incrementId.match(/^(\d+)/);
56
+ if (!match) {
57
+ throw new Error(`Invalid increment ID format: ${incrementId}. Expected format: NNNN-name`);
58
+ }
59
+ return parseInt(match[1], 10);
60
+ }
61
+ /**
62
+ * Validate feature ID format
63
+ *
64
+ * Valid formats:
65
+ * - FS-XXX (internal, 3+ digits)
66
+ * - FS-XXXE (external, 3+ digits with E suffix)
67
+ *
68
+ * @param featureId - Feature ID to validate
69
+ * @returns true if valid, false otherwise
70
+ */
71
+ export function isValidFeatureId(featureId) {
72
+ return /^FS-\d{3,}E?$/.test(featureId);
73
+ }
74
+ /**
75
+ * Check if feature ID is external (imported)
76
+ *
77
+ * External features have an 'E' suffix: FS-042E
78
+ *
79
+ * @param featureId - Feature ID to check
80
+ * @returns true if external, false if internal
81
+ */
82
+ export function isExternalFeatureId(featureId) {
83
+ return featureId.endsWith('E');
84
+ }
85
+ //# sourceMappingURL=feature-id-derivation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-id-derivation.js","sourceRoot":"","sources":["../../../src/utils/feature-id-derivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,4CAA4C;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,gCAAgC,WAAW,4CAA4C,CAAC,CAAC;IAC3G,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;IAEpC,+DAA+D;IAC/D,kEAAkE;IAClE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IACvD,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,gCAAgC,WAAW,8BAA8B,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,OAAO,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC"}
@@ -22,6 +22,10 @@ hooks:
22
22
  hooks:
23
23
  - type: command
24
24
  command: bash plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh
25
+ - matcher: Skill|Task
26
+ hooks:
27
+ - type: command
28
+ command: bash plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh
25
29
  ---
26
30
 
27
31
  # Plan Product Increment