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.
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +6 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +29 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +212 -2
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.js +9 -0
- package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/increment/increment-utils.d.ts +27 -4
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +44 -17
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/template-creator.d.ts +26 -0
- package/dist/src/core/increment/template-creator.d.ts.map +1 -1
- package/dist/src/core/increment/template-creator.js +179 -20
- package/dist/src/core/increment/template-creator.js.map +1 -1
- package/dist/src/importers/import-to-increment.d.ts +111 -0
- package/dist/src/importers/import-to-increment.d.ts.map +1 -0
- package/dist/src/importers/import-to-increment.js +223 -0
- package/dist/src/importers/import-to-increment.js.map +1 -0
- package/dist/src/importers/increment-external-ref-detector.d.ts +78 -0
- package/dist/src/importers/increment-external-ref-detector.d.ts.map +1 -0
- package/dist/src/importers/increment-external-ref-detector.js +130 -0
- package/dist/src/importers/increment-external-ref-detector.js.map +1 -0
- package/dist/src/init/research/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/stop-auto-v5.sh +2 -2
- package/plugins/specweave/hooks/stop-sync.sh +10 -5
- package/plugins/specweave/hooks/user-prompt-submit.sh +27 -5
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +6 -3
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +4 -3
- package/plugins/specweave/skills/import/SKILL.md +186 -0
- package/plugins/specweave/skills/increment/SKILL.md +30 -16
- package/plugins/specweave/skills/pm/SKILL.md +29 -2
- package/plugins/specweave/skills/pm/phases/00-deep-interview.md +12 -0
- package/plugins/specweave-github/lib/github-feature-sync-cli.js +5 -0
- package/plugins/specweave-github/lib/github-feature-sync-cli.ts +7 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +225 -2
- 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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAA2B,MAAM,uCAAuC,CAAC;AAC1G,OAAO,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACvG,OAAO,EAAE,UAAU,EAAiB,MAAM,kBAAkB,CAAC;AAuD7D;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,0BAA0B;IAKrC,YAAY,OAAiC;QAC3C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,4BAA4B,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3E,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,IAAkB;QAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,eAAe,CAAC,IAAkB;QACtC,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAEhD,uBAAuB;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACtD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,qBAAqB,WAAW,wBAAwB,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,QAAQ,GAAG,CACtG,CAAC;QACJ,CAAC;QAED,6CAA6C;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,cAAc,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,sBAAsB,CAAC,mBAAmB,CAAC,IAAI,EAAE;YACnE,cAAc;YACd,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;QAEH,kDAAkD;QAClD,MAAM,cAAc,GAAuB;YACzC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU,EAAE,WAAW;YACvB,WAAW,EAAE,IAAI,CAAC,GAAG;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;YAC3C,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;QAEF,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;QAEvC,sCAAsC;QACtC,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExC,wCAAwC;QACxC,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;YAC5C,WAAW;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;YACtC,IAAI;YACJ,QAAQ;YACR,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,cAAc;SACf,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,8BAA8B,WAAW,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,OAAO;YACL,WAAW;YACX,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,WAAW;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY,EAAE,MAAM,CAAC,YAAY;SAClC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,gBAAgB,CAAC,KAAqB;QAC1C,MAAM,MAAM,GAAsB;YAChC,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,EAAE;SACX,CAAC;QAEF,4CAA4C;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAEtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACtB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAEpB,kBAAkB;YAClB,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;oBAClB,IAAI;oBACJ,WAAW,EAAE,GAAG;oBAChB,mBAAmB,EAAE,QAAQ,CAAC,WAAW;oBACzC,MAAM,EAAE,uBAAuB,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,QAAQ,GAAG;iBAC7E,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,gBAAgB;YAChB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;gBAClD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;oBACjB,IAAI;oBACJ,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;iBAC9D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,gBAAgB,CAAC,IAAkB;QACzC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtB,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,6DAA6D;gBAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC3D,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACjD,OAAO,iBAAiB,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;YACxD,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,gDAAgD;gBAChD,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACnE,OAAO,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,gCAAgC;gBAChC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,IAAI,SAAS,CAAC;gBACjD,OAAO,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK;SACT,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAG,uBAAuB;SACtD,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAY,oBAAoB;SACpD,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAa,4BAA4B;SAC5D,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAW,gCAAgC;SAChE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAoB,eAAe;SAC/C,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAc,oCAAoC;AACzE,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,YAAkC;IACzD,QAAQ,YAAY,EAAE,CAAC;QACrB,KAAK,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC;QACzB,KAAK,SAAS,CAAC,CAAC,OAAO,SAAS,CAAC;QACjC,KAAK,MAAM,CAAC,CAAC,OAAO,SAAS,CAAC;QAC9B,KAAK,YAAY,CAAC,CAAC,OAAO,SAAS,CAAC;QACpC,KAAK,MAAM,CAAC,CAAC,OAAO,SAAS,CAAC;QAC9B,OAAO,CAAC,CAAC,OAAO,SAAS,CAAC;IAC5B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,EAAU;IACnC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,EAAU;IACrC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,EAAU;IACpC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC"}
|
|
@@ -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.
|
|
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
|
|
172
|
-
_next_task=$(
|
|
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" "$@"
|
|
82
|
+
timeout "$timeout_secs" "$@" || true
|
|
82
83
|
elif command -v gtimeout >/dev/null 2>&1; then
|
|
83
|
-
gtimeout "$timeout_secs" "$@"
|
|
84
|
+
gtimeout "$timeout_secs" "$@" || true
|
|
84
85
|
else
|
|
85
|
-
"$@"
|
|
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
|
|
201
|
-
|
|
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="
|
|
1422
|
+
DEEP_INTERVIEW_MSG=" Deep interview enabled — sw: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.
|
|
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' "$
|
|
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' "$
|
|
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
|
-
|
|
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" "$@"
|
|
154
|
+
timeout "$timeout_secs" "$@" || true
|
|
152
155
|
elif command -v gtimeout >/dev/null 2>&1; then
|
|
153
|
-
gtimeout "$timeout_secs" "$@"
|
|
156
|
+
gtimeout "$timeout_secs" "$@" || true
|
|
154
157
|
else
|
|
155
|
-
"$@"
|
|
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" "$@"
|
|
87
|
+
timeout "$timeout_secs" "$@" || true
|
|
87
88
|
elif command -v gtimeout >/dev/null 2>&1; then
|
|
88
|
-
gtimeout "$timeout_secs" "$@"
|
|
89
|
+
gtimeout "$timeout_secs" "$@" || true
|
|
89
90
|
else
|
|
90
|
-
"$@"
|
|
91
|
+
"$@" || true
|
|
91
92
|
fi
|
|
92
93
|
}
|
|
93
94
|
|