specweave 1.0.301 → 1.0.303

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 (41) hide show
  1. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +6 -0
  2. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
  3. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +29 -1
  4. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  5. package/dist/plugins/specweave-github/lib/github-feature-sync.js +212 -2
  6. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  7. package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
  8. package/dist/src/cli/commands/refresh-plugins.js +9 -0
  9. package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
  10. package/dist/src/config/types.d.ts +2 -2
  11. package/dist/src/core/increment/increment-utils.d.ts +27 -4
  12. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  13. package/dist/src/core/increment/increment-utils.js +44 -17
  14. package/dist/src/core/increment/increment-utils.js.map +1 -1
  15. package/dist/src/core/increment/template-creator.d.ts +26 -0
  16. package/dist/src/core/increment/template-creator.d.ts.map +1 -1
  17. package/dist/src/core/increment/template-creator.js +179 -20
  18. package/dist/src/core/increment/template-creator.js.map +1 -1
  19. package/dist/src/importers/import-to-increment.d.ts +111 -0
  20. package/dist/src/importers/import-to-increment.d.ts.map +1 -0
  21. package/dist/src/importers/import-to-increment.js +223 -0
  22. package/dist/src/importers/import-to-increment.js.map +1 -0
  23. package/dist/src/importers/increment-external-ref-detector.d.ts +78 -0
  24. package/dist/src/importers/increment-external-ref-detector.d.ts.map +1 -0
  25. package/dist/src/importers/increment-external-ref-detector.js +130 -0
  26. package/dist/src/importers/increment-external-ref-detector.js.map +1 -0
  27. package/dist/src/init/research/types.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/plugins/specweave/hooks/stop-auto-v5.sh +2 -2
  30. package/plugins/specweave/hooks/stop-sync.sh +10 -5
  31. package/plugins/specweave/hooks/user-prompt-submit.sh +27 -5
  32. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +6 -3
  33. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +4 -3
  34. package/plugins/specweave/skills/import/SKILL.md +186 -0
  35. package/plugins/specweave/skills/increment/SKILL.md +30 -16
  36. package/plugins/specweave/skills/pm/SKILL.md +29 -2
  37. package/plugins/specweave/skills/pm/phases/00-deep-interview.md +12 -0
  38. package/plugins/specweave-github/lib/github-feature-sync-cli.js +5 -0
  39. package/plugins/specweave-github/lib/github-feature-sync-cli.ts +7 -1
  40. package/plugins/specweave-github/lib/github-feature-sync.js +225 -2
  41. package/plugins/specweave-github/lib/github-feature-sync.ts +290 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-to-increment.d.ts","sourceRoot":"","sources":["../../../src/importers/import-to-increment.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,qCAAqC;IACrC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,EAAE,YAAY,CAAC;IACnB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,gCAAgC;IAChC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,yBAAyB;IACzB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,WAAW,CAA+B;gBAEtC,OAAO,EAAE,wBAAwB;IAM7C;;;;;OAKG;IACH,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI;IAMjD;;;;;;OAMG;IACG,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAgErE;;;;;;OAMG;IACG,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0CzE;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAoBzB"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Import-to-Increment Converter
3
+ *
4
+ * Bridges the gap between ExternalItem (imported from GitHub/JIRA/ADO)
5
+ * and SpecWeave increment creation with platform-specific suffixes.
6
+ *
7
+ * @module import-to-increment
8
+ * @since 1.0.272
9
+ */
10
+ import { IncrementNumberManager } from '../core/increment/increment-utils.js';
11
+ import { createIncrementTemplates } from '../core/increment/template-creator.js';
12
+ import { IncrementExternalRefDetector, formatExternalRef } from './increment-external-ref-detector.js';
13
+ import { SUFFIX_MAP } from '../sync/types.js';
14
+ /**
15
+ * Converts external items (from GitHub/JIRA/ADO) into SpecWeave increments
16
+ * with platform-specific suffixes (G/J/A) and duplicate prevention.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const converter = new ImportToIncrementConverter({
21
+ * projectRoot: '/path/to/project',
22
+ * projectId: 'my-app',
23
+ * });
24
+ *
25
+ * const result = await converter.createIncrement(externalItem);
26
+ * console.log(result.incrementId); // "0271G-fix-login-bug"
27
+ * ```
28
+ */
29
+ export class ImportToIncrementConverter {
30
+ constructor(options) {
31
+ this.projectRoot = options.projectRoot;
32
+ this.projectId = options.projectId;
33
+ this.refDetector = new IncrementExternalRefDetector(options.projectRoot);
34
+ }
35
+ /**
36
+ * Check if an external item has already been imported as an increment.
37
+ *
38
+ * @param item - External item to check
39
+ * @returns Existing increment ID if found, null otherwise
40
+ */
41
+ checkDuplicate(item) {
42
+ const externalRef = this.buildExternalRef(item);
43
+ const match = this.refDetector.hasRef(externalRef);
44
+ return match ? match.incrementId : null;
45
+ }
46
+ /**
47
+ * Create a single increment from an external item.
48
+ *
49
+ * @param item - External item to import
50
+ * @returns Created increment details
51
+ * @throws Error if item is a duplicate (use checkDuplicate first) or creation fails
52
+ */
53
+ async createIncrement(item) {
54
+ const externalRef = this.buildExternalRef(item);
55
+ // Check for duplicates
56
+ const existing = this.refDetector.hasRef(externalRef);
57
+ if (existing) {
58
+ throw new Error(`Duplicate import: ${externalRef} already imported as ${existing.incrementId} (${existing.location})`);
59
+ }
60
+ // Generate increment ID with platform suffix
61
+ const slug = slugify(item.title);
62
+ const platformSuffix = SUFFIX_MAP[item.platform];
63
+ const incrementId = IncrementNumberManager.generateIncrementId(slug, {
64
+ platformSuffix,
65
+ projectRoot: this.projectRoot,
66
+ projectId: this.projectId,
67
+ });
68
+ // Build external source info for template creator
69
+ const externalSource = {
70
+ platform: item.platform,
71
+ externalId: externalRef,
72
+ externalUrl: item.url,
73
+ title: item.title,
74
+ description: item.description,
75
+ acceptanceCriteria: item.acceptanceCriteria,
76
+ labels: item.labels,
77
+ priority: item.priority,
78
+ status: item.status,
79
+ };
80
+ // Map external priority to SpecWeave priority
81
+ const priority = item.priority ?? 'P2';
82
+ // Map external type to SpecWeave type
83
+ const type = mapExternalType(item.type);
84
+ // Create increment via template creator
85
+ const result = await createIncrementTemplates({
86
+ incrementId,
87
+ title: item.title,
88
+ description: item.description,
89
+ projectId: this.projectId || 'default',
90
+ type,
91
+ priority,
92
+ projectRoot: this.projectRoot,
93
+ externalSource,
94
+ });
95
+ if (!result.success) {
96
+ throw new Error(`Failed to create increment ${incrementId}: ${result.error}`);
97
+ }
98
+ return {
99
+ incrementId,
100
+ incrementPath: result.incrementPath,
101
+ externalRef,
102
+ platform: item.platform,
103
+ createdFiles: result.createdFiles,
104
+ };
105
+ }
106
+ /**
107
+ * Batch create increments from multiple external items.
108
+ * Automatically deduplicates and reports skipped items.
109
+ *
110
+ * @param items - External items to import
111
+ * @returns Batch result with created, skipped, and error arrays
112
+ */
113
+ async createIncrements(items) {
114
+ const result = {
115
+ created: [],
116
+ skipped: [],
117
+ errors: [],
118
+ };
119
+ // Pre-check all refs at once for efficiency
120
+ const refs = items.map(item => this.buildExternalRef(item));
121
+ const existingRefs = this.refDetector.checkRefs(refs);
122
+ for (let i = 0; i < items.length; i++) {
123
+ const item = items[i];
124
+ const ref = refs[i];
125
+ // Check duplicate
126
+ const existing = existingRefs.get(ref);
127
+ if (existing) {
128
+ result.skipped.push({
129
+ item,
130
+ externalRef: ref,
131
+ existingIncrementId: existing.incrementId,
132
+ reason: `Already imported as ${existing.incrementId} (${existing.location})`,
133
+ });
134
+ continue;
135
+ }
136
+ // Try to create
137
+ try {
138
+ const imported = await this.createIncrement(item);
139
+ result.created.push(imported);
140
+ }
141
+ catch (error) {
142
+ result.errors.push({
143
+ item,
144
+ error: error instanceof Error ? error.message : String(error),
145
+ });
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+ /**
151
+ * Build a canonical external_ref string from an ExternalItem.
152
+ */
153
+ buildExternalRef(item) {
154
+ switch (item.platform) {
155
+ case 'github': {
156
+ // GitHub IDs are formatted as "github#{owner/repo}#{number}"
157
+ const repo = item.sourceRepo || extractGitHubRepo(item.id);
158
+ const issueNumber = extractGitHubNumber(item.id);
159
+ return formatExternalRef('github', repo, issueNumber);
160
+ }
161
+ case 'jira': {
162
+ // JIRA IDs are the issue key (e.g., "PROJ-123")
163
+ const project = item.jiraProjectKey || extractJiraProject(item.id);
164
+ return formatExternalRef('jira', project, item.id);
165
+ }
166
+ case 'ado': {
167
+ // ADO IDs are work item numbers
168
+ const project = item.adoProjectName || 'default';
169
+ return formatExternalRef('ado', project, item.id);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ /**
175
+ * Convert a title to a kebab-case slug suitable for increment folder names.
176
+ * Limits to 50 chars to keep folder names reasonable.
177
+ */
178
+ function slugify(title) {
179
+ return title
180
+ .toLowerCase()
181
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special chars
182
+ .replace(/\s+/g, '-') // Spaces to hyphens
183
+ .replace(/-+/g, '-') // Collapse multiple hyphens
184
+ .replace(/^-|-$/g, '') // Trim leading/trailing hyphens
185
+ .slice(0, 50) // Limit length
186
+ .replace(/-$/, ''); // Clean trailing hyphen after slice
187
+ }
188
+ /**
189
+ * Map external item type to SpecWeave increment type.
190
+ */
191
+ function mapExternalType(externalType) {
192
+ switch (externalType) {
193
+ case 'bug': return 'bug';
194
+ case 'feature': return 'feature';
195
+ case 'epic': return 'feature';
196
+ case 'user-story': return 'feature';
197
+ case 'task': return 'feature';
198
+ default: return 'feature';
199
+ }
200
+ }
201
+ /**
202
+ * Extract owner/repo from a GitHub external ID.
203
+ * Input format: "github#owner/repo#123" or just a number.
204
+ */
205
+ function extractGitHubRepo(id) {
206
+ const match = id.match(/github#([^#]+)#/);
207
+ return match ? match[1] : 'unknown/unknown';
208
+ }
209
+ /**
210
+ * Extract issue number from a GitHub external ID.
211
+ */
212
+ function extractGitHubNumber(id) {
213
+ const match = id.match(/#(\d+)$/);
214
+ return match ? match[1] : id.replace(/\D/g, '') || '0';
215
+ }
216
+ /**
217
+ * Extract project key from a JIRA issue ID (e.g., "PROJ-123" → "PROJ").
218
+ */
219
+ function extractJiraProject(id) {
220
+ const match = id.match(/^([A-Z]+)-/);
221
+ return match ? match[1] : 'UNKNOWN';
222
+ }
223
+ //# sourceMappingURL=import-to-increment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-to-increment.js","sourceRoot":"","sources":["../../../src/importers/import-to-increment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Increment External Reference Detector
3
+ *
4
+ * Scans ALL increment metadata.json files for `external_ref` fields
5
+ * to prevent duplicate imports from external tools (GitHub/JIRA/ADO).
6
+ *
7
+ * @module increment-external-ref-detector
8
+ * @since 1.0.272
9
+ */
10
+ /**
11
+ * Scan result for an existing external reference.
12
+ */
13
+ export interface ExternalRefMatch {
14
+ /** Increment ID that already has this external ref */
15
+ incrementId: string;
16
+ /** Lifecycle directory where the increment was found */
17
+ location: 'active' | '_archive' | '_abandoned' | '_paused';
18
+ /** The external_ref value from metadata.json */
19
+ externalRef: string;
20
+ }
21
+ /**
22
+ * Detects duplicate external references across all increment directories.
23
+ *
24
+ * Scans metadata.json files in:
25
+ * - .specweave/increments/ (active)
26
+ * - .specweave/increments/_archive/
27
+ * - .specweave/increments/_abandoned/
28
+ * - .specweave/increments/_paused/
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const detector = new IncrementExternalRefDetector('/path/to/project');
33
+ * const existing = detector.hasRef('github#owner/repo#123');
34
+ * if (existing) {
35
+ * console.log(`Already imported as ${existing.incrementId}`);
36
+ * }
37
+ * ```
38
+ */
39
+ export declare class IncrementExternalRefDetector {
40
+ private projectRoot;
41
+ constructor(projectRoot: string);
42
+ /**
43
+ * Build a map of all external references across all increments.
44
+ *
45
+ * @returns Map of external_ref → ExternalRefMatch
46
+ */
47
+ buildRefMap(): Map<string, ExternalRefMatch>;
48
+ /**
49
+ * Check if an external reference already exists in any increment.
50
+ *
51
+ * @param externalRef - External reference to check (e.g., "github#owner/repo#123")
52
+ * @returns The match if found, null otherwise
53
+ */
54
+ hasRef(externalRef: string): ExternalRefMatch | null;
55
+ /**
56
+ * Check multiple external references at once (batch dedup).
57
+ *
58
+ * @param externalRefs - Array of external references to check
59
+ * @returns Map of refs that already exist → their matches
60
+ */
61
+ checkRefs(externalRefs: string[]): Map<string, ExternalRefMatch>;
62
+ }
63
+ /**
64
+ * Generate a canonical external_ref string from platform and issue details.
65
+ *
66
+ * @param platform - Source platform
67
+ * @param identifier - Platform-specific identifier
68
+ * @returns Canonical external_ref string
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * formatExternalRef('github', 'owner/repo', 123) → 'github#owner/repo#123'
73
+ * formatExternalRef('jira', 'PROJ', 'PROJ-456') → 'jira#PROJ#PROJ-456'
74
+ * formatExternalRef('ado', 'org/project', 789) → 'ado#org/project#789'
75
+ * ```
76
+ */
77
+ export declare function formatExternalRef(platform: 'github' | 'jira' | 'ado', context: string, issueId: string | number): string;
78
+ //# sourceMappingURL=increment-external-ref-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"increment-external-ref-detector.d.ts","sourceRoot":"","sources":["../../../src/importers/increment-external-ref-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,QAAQ,EAAE,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;IAC3D,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,4BAA4B;IACvC,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;IAI/B;;;;OAIG;IACH,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAiD5C;;;;;OAKG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAKpD;;;;;OAKG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC;CAajE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,EACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,MAAM,GACvB,MAAM,CAER"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Increment External Reference Detector
3
+ *
4
+ * Scans ALL increment metadata.json files for `external_ref` fields
5
+ * to prevent duplicate imports from external tools (GitHub/JIRA/ADO).
6
+ *
7
+ * @module increment-external-ref-detector
8
+ * @since 1.0.272
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ /**
13
+ * Detects duplicate external references across all increment directories.
14
+ *
15
+ * Scans metadata.json files in:
16
+ * - .specweave/increments/ (active)
17
+ * - .specweave/increments/_archive/
18
+ * - .specweave/increments/_abandoned/
19
+ * - .specweave/increments/_paused/
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const detector = new IncrementExternalRefDetector('/path/to/project');
24
+ * const existing = detector.hasRef('github#owner/repo#123');
25
+ * if (existing) {
26
+ * console.log(`Already imported as ${existing.incrementId}`);
27
+ * }
28
+ * ```
29
+ */
30
+ export class IncrementExternalRefDetector {
31
+ constructor(projectRoot) {
32
+ this.projectRoot = projectRoot;
33
+ }
34
+ /**
35
+ * Build a map of all external references across all increments.
36
+ *
37
+ * @returns Map of external_ref → ExternalRefMatch
38
+ */
39
+ buildRefMap() {
40
+ const refMap = new Map();
41
+ const incrementsDir = path.join(this.projectRoot, '.specweave', 'increments');
42
+ const dirsToScan = [
43
+ { path: incrementsDir, location: 'active' },
44
+ { path: path.join(incrementsDir, '_archive'), location: '_archive' },
45
+ { path: path.join(incrementsDir, '_abandoned'), location: '_abandoned' },
46
+ { path: path.join(incrementsDir, '_paused'), location: '_paused' },
47
+ ];
48
+ for (const { path: dirPath, location } of dirsToScan) {
49
+ if (!fs.existsSync(dirPath))
50
+ continue;
51
+ try {
52
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
53
+ for (const entry of entries) {
54
+ if (!entry.isDirectory())
55
+ continue;
56
+ if (entry.name.startsWith('_'))
57
+ continue; // Skip lifecycle subdirs
58
+ const metadataPath = path.join(dirPath, entry.name, 'metadata.json');
59
+ if (!fs.existsSync(metadataPath))
60
+ continue;
61
+ try {
62
+ const raw = fs.readFileSync(metadataPath, 'utf-8');
63
+ const metadata = JSON.parse(raw);
64
+ if (metadata.external_ref) {
65
+ refMap.set(metadata.external_ref, {
66
+ incrementId: metadata.id || entry.name,
67
+ location,
68
+ externalRef: metadata.external_ref,
69
+ });
70
+ }
71
+ }
72
+ catch {
73
+ // Corrupted metadata.json — skip silently
74
+ continue;
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ // Permission denied or other error — skip
80
+ continue;
81
+ }
82
+ }
83
+ return refMap;
84
+ }
85
+ /**
86
+ * Check if an external reference already exists in any increment.
87
+ *
88
+ * @param externalRef - External reference to check (e.g., "github#owner/repo#123")
89
+ * @returns The match if found, null otherwise
90
+ */
91
+ hasRef(externalRef) {
92
+ const refMap = this.buildRefMap();
93
+ return refMap.get(externalRef) ?? null;
94
+ }
95
+ /**
96
+ * Check multiple external references at once (batch dedup).
97
+ *
98
+ * @param externalRefs - Array of external references to check
99
+ * @returns Map of refs that already exist → their matches
100
+ */
101
+ checkRefs(externalRefs) {
102
+ const refMap = this.buildRefMap();
103
+ const duplicates = new Map();
104
+ for (const ref of externalRefs) {
105
+ const match = refMap.get(ref);
106
+ if (match) {
107
+ duplicates.set(ref, match);
108
+ }
109
+ }
110
+ return duplicates;
111
+ }
112
+ }
113
+ /**
114
+ * Generate a canonical external_ref string from platform and issue details.
115
+ *
116
+ * @param platform - Source platform
117
+ * @param identifier - Platform-specific identifier
118
+ * @returns Canonical external_ref string
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * formatExternalRef('github', 'owner/repo', 123) → 'github#owner/repo#123'
123
+ * formatExternalRef('jira', 'PROJ', 'PROJ-456') → 'jira#PROJ#PROJ-456'
124
+ * formatExternalRef('ado', 'org/project', 789) → 'ado#org/project#789'
125
+ * ```
126
+ */
127
+ export function formatExternalRef(platform, context, issueId) {
128
+ return `${platform}#${context}#${issueId}`;
129
+ }
130
+ //# sourceMappingURL=increment-external-ref-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"increment-external-ref-detector.js","sourceRoot":"","sources":["../../../src/importers/increment-external-ref-detector.ts"],"names":[],"mappings":"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"}
@@ -73,12 +73,12 @@ export declare const VisionInsightsSchema: z.ZodObject<{
73
73
  education: "education";
74
74
  iot: "iot";
75
75
  gaming: "gaming";
76
+ marketplace: "marketplace";
76
77
  "productivity-saas": "productivity-saas";
77
78
  "e-commerce": "e-commerce";
78
79
  "social-network": "social-network";
79
80
  "enterprise-b2b": "enterprise-b2b";
80
81
  "consumer-b2c": "consumer-b2c";
81
- marketplace: "marketplace";
82
82
  blockchain: "blockchain";
83
83
  "ai-ml": "ai-ml";
84
84
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.301",
3
+ "version": "1.0.303",
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",
@@ -168,8 +168,8 @@ for meta in $(find "$INC_DIR" -maxdepth 2 -name "metadata.json" 2>/dev/null); do
168
168
  p=$(count_pending_tasks "$d/tasks.md"); a=$(count_open_acs "$d/spec.md")
169
169
  if [ "$p" -gt 0 ] || [ "$a" -gt 0 ]; then
170
170
  TP=$((TP + p)); TAC=$((TAC + a)); IC=$((IC + 1))
171
- # Extract next pending task title (first [ ] line, strip markdown)
172
- _next_task=$(grep -m1 '\[ \]' "$d/tasks.md" 2>/dev/null | sed 's/.*\] //' | head -c 80 || echo "")
171
+ # Extract next pending task title using helper
172
+ _next_task=$(get_next_task_title "$d/tasks.md")
173
173
  # Count completed tasks for progress fraction
174
174
  _done=$(grep -c '\[x\]' "$d/tasks.md" 2>/dev/null) || _done=0
175
175
  _total=$((_done + p))
@@ -74,15 +74,16 @@ if [ ! -f "$BRIDGE_HANDLER" ]; then
74
74
  fi
75
75
 
76
76
  # Cross-platform timeout wrapper
77
+ # FIXED (v1.0.302): Don't suppress stderr from inner commands
77
78
  run_with_timeout() {
78
79
  local timeout_secs="$1"
79
80
  shift
80
81
  if command -v timeout >/dev/null 2>&1; then
81
- timeout "$timeout_secs" "$@" 2>/dev/null || true
82
+ timeout "$timeout_secs" "$@" || true
82
83
  elif command -v gtimeout >/dev/null 2>&1; then
83
- gtimeout "$timeout_secs" "$@" 2>/dev/null || true
84
+ gtimeout "$timeout_secs" "$@" || true
84
85
  else
85
- "$@" 2>/dev/null || true
86
+ "$@" || true
86
87
  fi
87
88
  }
88
89
 
@@ -194,11 +195,15 @@ for INC_ID in $INCREMENTS_TO_SYNC; do
194
195
 
195
196
  # Route user-story events to github-sync-handler (v1.0.262+)
196
197
  # github-sync-handler understands user-story.completed/reopened with INC_ID:US_ID data
198
+ # FIXED (v1.0.302): Filter by BOTH event type AND increment ID to prevent
199
+ # routing all events to the first matching increment's US event.
197
200
  if [ -f "$GITHUB_SYNC_HANDLER" ] && [ -f "$EVENT_TYPES_FILE" ]; then
198
201
  while IFS='|' read -r _inc_id us_event_type; do
199
202
  [ -z "$us_event_type" ] && continue
200
- # Extract US data (INC_ID:US_ID) from pending.jsonl for this event
201
- US_DATA=$(grep -o "\"type\":\"${us_event_type}\"[^}]*\"data\":\"[^\"]*\"" "$PENDING_FILE" 2>/dev/null \
203
+ # Extract US data (INC_ID:US_ID) from pending.jsonl for THIS increment
204
+ # Must match both the event type AND the increment ID in the data field
205
+ US_DATA=$(grep "\"type\":\"${us_event_type}\"" "$PENDING_FILE" 2>/dev/null \
206
+ | grep "\"data\":\"${INC_ID}:" \
202
207
  | grep -o '"data":"[^"]*"' | cut -d'"' -f4 | head -1)
203
208
  if [ -n "$US_DATA" ]; then
204
209
  log "Routing user-story event to github-sync-handler: $us_event_type $US_DATA"
@@ -1415,9 +1415,11 @@ Then spawn agent: \`Task({ subagent_type: \"${PRIMARY_PLUGIN}:${PRIMARY_SKILL_NA
1415
1415
  # The skill reads the user's prompt from conversation context (it's already there).
1416
1416
 
1417
1417
  # v1.0.243: Smart interview gate — LLM assesses prompt completeness
1418
+ # v1.0.301: Removed question count cap ("2-5") — sw:pm decides count
1419
+ # based on complexity (trivial: 0-3, small: 4-8, medium: 9-18, large: 19-40).
1418
1420
  DEEP_INTERVIEW_MSG=""
1419
1421
  if [[ "$DEEP_INTERVIEW_ENABLED" == "true" ]]; then
1420
- DEEP_INTERVIEW_MSG=" Assess prompt completeness first if gaps exist, ask 2-5 targeted questions before calling."
1422
+ DEEP_INTERVIEW_MSG=" Deep interview enabledsw:pm skill will assess complexity and interview depth."
1421
1423
  fi
1422
1424
 
1423
1425
  MSG="${WIP_WARNING}${AUTOLOAD_PREFIX}SKILL FIRST: \`Skill({ skill: \"sw:increment\" })\` — call BEFORE implementation.
@@ -2092,7 +2094,7 @@ if [[ "$DEEP_INTERVIEW_ENABLED" == "true" ]] && [[ -z "$ACTIVE_INCREMENT" ]]; th
2092
2094
  fi
2093
2095
 
2094
2096
  if [[ "$HAVE_ACTIVE_STATE" != "true" ]]; then
2095
- SMART_INTERVIEW_GATE_MSG="No active increment. Assess prompt completeness for complexity if gaps, ask 2-5 targeted questions. If sufficient, call sw:increment."
2097
+ SMART_INTERVIEW_GATE_MSG="No active increment. Deep interview enabled — assess prompt completeness for complexity. If gaps exist, ask targeted questions (count depends on complexity). If sufficient, call sw:increment."
2096
2098
  fi
2097
2099
  fi
2098
2100
 
@@ -2429,20 +2431,40 @@ _budget_append "$ARCHIVE_SUGGESTION_MSG"
2429
2431
  # ==============================================================================
2430
2432
  # If this turn's context is identical to last turn's, don't re-inject it.
2431
2433
  # Claude already has it in history. Saves ~2500 chars per duplicate turn.
2434
+ #
2435
+ # v1.0.301: SMART_INTERVIEW_GATE_MSG is excluded from the dedup hash.
2436
+ # The gate must fire on EVERY prompt (until an increment is created) so the
2437
+ # LLM keeps assessing prompt completeness across turns. Hashing the full
2438
+ # FINAL_MESSAGE (which includes the gate) would produce the same hash on
2439
+ # consecutive prompts and suppress the gate after the first turn.
2432
2440
  if [[ -n "$FINAL_MESSAGE" ]] && [[ -n "$SW_PROJECT_ROOT" ]]; then
2433
2441
  DEDUP_HASH_FILE="$SW_PROJECT_ROOT/.specweave/state/.context-hash"
2434
2442
  CURRENT_HASH=""
2443
+
2444
+ # Build hash input WITHOUT the gate message so it doesn't trigger dedup
2445
+ DEDUP_INPUT="$FINAL_MESSAGE"
2446
+ if [[ -n "$SMART_INTERVIEW_GATE_MSG" ]]; then
2447
+ # Remove the gate message (with leading \n) from hash input
2448
+ DEDUP_INPUT="${DEDUP_INPUT//\\n${SMART_INTERVIEW_GATE_MSG}/}"
2449
+ DEDUP_INPUT="${DEDUP_INPUT//${SMART_INTERVIEW_GATE_MSG}/}"
2450
+ fi
2451
+
2435
2452
  if command -v md5sum >/dev/null 2>&1; then
2436
- CURRENT_HASH=$(printf '%s' "$FINAL_MESSAGE" | md5sum | cut -d' ' -f1)
2453
+ CURRENT_HASH=$(printf '%s' "$DEDUP_INPUT" | md5sum | cut -d' ' -f1)
2437
2454
  elif command -v md5 >/dev/null 2>&1; then
2438
- CURRENT_HASH=$(printf '%s' "$FINAL_MESSAGE" | md5)
2455
+ CURRENT_HASH=$(printf '%s' "$DEDUP_INPUT" | md5)
2439
2456
  fi
2440
2457
 
2441
2458
  if [[ -n "$CURRENT_HASH" ]]; then
2442
2459
  if [[ -f "$DEDUP_HASH_FILE" ]]; then
2443
2460
  PREV_HASH=$(cat "$DEDUP_HASH_FILE" 2>/dev/null)
2444
2461
  if [[ "$CURRENT_HASH" == "$PREV_HASH" ]]; then
2445
- # Identical to last turn — skip injection
2462
+ if [[ -n "$SMART_INTERVIEW_GATE_MSG" ]]; then
2463
+ # Rest of context is duplicate, but gate must still fire
2464
+ output_approve_with_context "\\n${SMART_INTERVIEW_GATE_MSG}"
2465
+ exit 0
2466
+ fi
2467
+ # No gate — fully identical, skip injection
2446
2468
  echo '{"decision":"approve"}'
2447
2469
  exit 0
2448
2470
  fi
@@ -144,15 +144,18 @@ touch "$THROTTLE_FILE"
144
144
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [github-sync] EXECUTING $INC_ID ($SYNC_TYPE) event=$EVENT_TYPE" >> "$THROTTLE_LOG" 2>/dev/null
145
145
 
146
146
  # Cross-platform timeout wrapper
147
+ # FIXED (v1.0.302): Don't suppress stderr from inner commands — only suppress
148
+ # stderr from timeout/gtimeout binary itself (e.g., "command not found").
149
+ # The caller already redirects stderr to log files via 2>&1.
147
150
  run_with_timeout() {
148
151
  local timeout_secs="$1"
149
152
  shift
150
153
  if command -v timeout >/dev/null 2>&1; then
151
- timeout "$timeout_secs" "$@" 2>/dev/null || true
154
+ timeout "$timeout_secs" "$@" || true
152
155
  elif command -v gtimeout >/dev/null 2>&1; then
153
- gtimeout "$timeout_secs" "$@" 2>/dev/null || true
156
+ gtimeout "$timeout_secs" "$@" || true
154
157
  else
155
- "$@" 2>/dev/null || true
158
+ "$@" || true
156
159
  fi
157
160
  }
158
161
 
@@ -79,15 +79,16 @@ for path in \
79
79
  done
80
80
 
81
81
  # Cross-platform timeout wrapper
82
+ # FIXED (v1.0.302): Don't suppress stderr from inner commands
82
83
  run_with_timeout() {
83
84
  local timeout_secs="$1"
84
85
  shift
85
86
  if command -v timeout >/dev/null 2>&1; then
86
- timeout "$timeout_secs" "$@" 2>/dev/null || true
87
+ timeout "$timeout_secs" "$@" || true
87
88
  elif command -v gtimeout >/dev/null 2>&1; then
88
- gtimeout "$timeout_secs" "$@" 2>/dev/null || true
89
+ gtimeout "$timeout_secs" "$@" || true
89
90
  else
90
- "$@" 2>/dev/null || true
91
+ "$@" || true
91
92
  fi
92
93
  }
93
94