specweave 1.0.319 → 1.0.320

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.320",
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",
@@ -134,19 +134,31 @@ log "Creating external items for $INC_ID (github=$GH_ENABLED jira=$JIRA_ENABLED
134
134
  # ============================================================================
135
135
 
136
136
  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"
137
+ # Derive specweave package root from script location (works for both dev and npm)
138
+ # Script is at: plugins/specweave/hooks/v2/handlers/ 5 levels up = package root
139
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
140
+ PKG_ROOT="$(cd "$SCRIPT_DIR/../../../../.." 2>/dev/null && pwd)"
141
+
142
+ GITHUB_HANDLER=""
143
+ # 1. Sibling plugin dir (works for both npm and dev: plugins/specweave-github/hooks/)
144
+ CANDIDATE="$SCRIPT_DIR/../../../../specweave-github/hooks/github-auto-create-handler.sh"
145
+ [[ -f "$CANDIDATE" ]] && GITHUB_HANDLER="$CANDIDATE"
146
+ # 2. Fallback: PROJECT_ROOT/plugins/ (dev-only, when running from source repo)
147
+ if [[ -z "$GITHUB_HANDLER" ]]; then
148
+ CANDIDATE="${PROJECT_ROOT}/plugins/specweave-github/hooks/github-auto-create-handler.sh"
149
+ [[ -f "$CANDIDATE" ]] && GITHUB_HANDLER="$CANDIDATE"
143
150
  fi
144
- if [[ -f "$GITHUB_HANDLER" ]]; then
145
- log "Delegating GitHub auto-create to existing handler"
151
+ # 3. Fallback: node_modules/specweave/ (direct npm install)
152
+ if [[ -z "$GITHUB_HANDLER" ]]; then
153
+ CANDIDATE="${PROJECT_ROOT}/node_modules/specweave/plugins/specweave-github/hooks/github-auto-create-handler.sh"
154
+ [[ -f "$CANDIDATE" ]] && GITHUB_HANDLER="$CANDIDATE"
155
+ fi
156
+ if [[ -n "$GITHUB_HANDLER" && -f "$GITHUB_HANDLER" ]]; then
157
+ log "Delegating GitHub auto-create to handler at $GITHUB_HANDLER"
146
158
  bash "$GITHUB_HANDLER" "$INC_ID" &
147
159
  GH_PID=$!
148
160
  else
149
- log "GitHub handler not found at any known path"
161
+ log "GitHub handler not found at any known path (checked sibling, PROJECT_ROOT, node_modules)"
150
162
  fi
151
163
  fi
152
164
 
@@ -157,9 +169,36 @@ fi
157
169
  if [[ "$JIRA_ENABLED" == "true" || "$ADO_ENABLED" == "true" ]]; then
158
170
  command -v node &>/dev/null || { log "Node.js not found. Skipping JIRA/ADO."; exit 0; }
159
171
 
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."
172
+ # Resolve universal-auto-create.js from multiple locations
173
+ CREATE_MODULE=""
174
+ # 0. SPECWEAVE_PKG (set by Claude Code hook infrastructure, most reliable)
175
+ if [[ -n "${SPECWEAVE_PKG:-}" ]]; then
176
+ CANDIDATE="${SPECWEAVE_PKG}/dist/src/core/universal-auto-create.js"
177
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
178
+ fi
179
+ # 1. Package dist/ (derived from script location — works for both npm and dev)
180
+ if [[ -z "$CREATE_MODULE" ]]; then
181
+ CANDIDATE="${PKG_ROOT:-$PROJECT_ROOT}/dist/src/core/universal-auto-create.js"
182
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
183
+ fi
184
+ # 2. Vendor directory (self-contained plugin)
185
+ if [[ -z "$CREATE_MODULE" ]]; then
186
+ CANDIDATE="$HANDLER_DIR/../../lib/vendor/core/universal-auto-create.js"
187
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$(cd "$(dirname "$CANDIDATE")" && pwd)/$(basename "$CANDIDATE")"
188
+ fi
189
+ # 3. node_modules/specweave/ (direct npm install)
190
+ if [[ -z "$CREATE_MODULE" ]]; then
191
+ CANDIDATE="${PROJECT_ROOT}/node_modules/specweave/dist/src/core/universal-auto-create.js"
192
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
193
+ fi
194
+ # 4. Legacy: PROJECT_ROOT/dist/ (dev-only, running from source repo)
195
+ if [[ -z "$CREATE_MODULE" ]]; then
196
+ CANDIDATE="${PROJECT_ROOT}/dist/src/core/universal-auto-create.js"
197
+ [[ -f "$CANDIDATE" ]] && CREATE_MODULE="$CANDIDATE"
198
+ fi
199
+
200
+ if [[ -z "$CREATE_MODULE" ]]; then
201
+ log "Module not found at any known path. Skipping JIRA/ADO. Checked: PKG_ROOT=${PKG_ROOT:-unset}, node_modules, vendor, PROJECT_ROOT"
163
202
  else
164
203
  # Build config JSON for the TypeScript module
165
204
  JIRA_DOMAIN=$(jq -r '.sync.jira.domain // .issueTracker.domain // ""' "$CONFIG_PATH" 2>/dev/null)
@@ -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