sequant 1.12.0 → 1.13.1
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/README.md +10 -8
- package/dist/bin/cli.js +19 -9
- package/dist/src/commands/doctor.js +42 -20
- package/dist/src/commands/init.js +152 -65
- package/dist/src/commands/logs.js +7 -6
- package/dist/src/commands/run.d.ts +13 -1
- package/dist/src/commands/run.js +122 -32
- package/dist/src/commands/stats.js +67 -48
- package/dist/src/commands/status.js +30 -12
- package/dist/src/commands/sync.d.ts +28 -0
- package/dist/src/commands/sync.js +102 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -0
- package/dist/src/lib/cli-ui.d.ts +196 -0
- package/dist/src/lib/cli-ui.js +544 -0
- package/dist/src/lib/content-analyzer.d.ts +89 -0
- package/dist/src/lib/content-analyzer.js +437 -0
- package/dist/src/lib/phase-signal.d.ts +94 -0
- package/dist/src/lib/phase-signal.js +171 -0
- package/dist/src/lib/phase-spinner.d.ts +146 -0
- package/dist/src/lib/phase-spinner.js +255 -0
- package/dist/src/lib/solve-comment-parser.d.ts +84 -0
- package/dist/src/lib/solve-comment-parser.js +200 -0
- package/dist/src/lib/stack-config.d.ts +51 -0
- package/dist/src/lib/stack-config.js +77 -0
- package/dist/src/lib/stacks.d.ts +52 -0
- package/dist/src/lib/stacks.js +173 -0
- package/dist/src/lib/templates.d.ts +2 -0
- package/dist/src/lib/templates.js +9 -2
- package/dist/src/lib/upstream/assessment.d.ts +70 -0
- package/dist/src/lib/upstream/assessment.js +385 -0
- package/dist/src/lib/upstream/index.d.ts +11 -0
- package/dist/src/lib/upstream/index.js +14 -0
- package/dist/src/lib/upstream/issues.d.ts +38 -0
- package/dist/src/lib/upstream/issues.js +267 -0
- package/dist/src/lib/upstream/relevance.d.ts +50 -0
- package/dist/src/lib/upstream/relevance.js +209 -0
- package/dist/src/lib/upstream/report.d.ts +29 -0
- package/dist/src/lib/upstream/report.js +391 -0
- package/dist/src/lib/upstream/types.d.ts +207 -0
- package/dist/src/lib/upstream/types.js +5 -0
- package/dist/src/lib/workflow/log-writer.d.ts +1 -1
- package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
- package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
- package/dist/src/lib/workflow/qa-cache.js +440 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
- package/dist/src/lib/workflow/run-log-schema.js +12 -1
- package/dist/src/lib/workflow/state-schema.d.ts +4 -4
- package/dist/src/lib/workflow/types.d.ts +4 -0
- package/package.json +6 -1
- package/templates/skills/qa/scripts/quality-checks.sh +509 -53
- package/templates/skills/solve/SKILL.md +375 -83
- package/templates/skills/spec/SKILL.md +107 -5
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Analyzer for Phase Detection
|
|
3
|
+
*
|
|
4
|
+
* Analyzes issue title and body content to detect phase-relevant keywords
|
|
5
|
+
* and patterns. This supplements (not replaces) label-based detection.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { analyzeTitleForPhases, analyzeBodyForPhases, analyzeContentForPhases } from './content-analyzer';
|
|
10
|
+
*
|
|
11
|
+
* const title = "Extract header component from main layout";
|
|
12
|
+
* const body = "Refactor the header.tsx file to create a reusable component...";
|
|
13
|
+
*
|
|
14
|
+
* const signals = analyzeContentForPhases(title, body);
|
|
15
|
+
* // Returns: { phases: ['test'], signals: [...], source: 'content' }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Title keyword patterns for phase detection
|
|
20
|
+
*
|
|
21
|
+
* Based on issue #175 specification:
|
|
22
|
+
* | Pattern | Detection | Suggested Phase |
|
|
23
|
+
* |---------|-----------|-----------------|
|
|
24
|
+
* | `extract`, `component`, `refactor.*ui` | UI work | Add `/test` |
|
|
25
|
+
* | `fix.*unused`, `remove.*variable`, `typo` | Trivial | Note in output |
|
|
26
|
+
* | `auth`, `permission`, `security` | Security-sensitive | Add `/security-review` |
|
|
27
|
+
* | `api`, `endpoint`, `route` | Backend | Skip `/test` |
|
|
28
|
+
*/
|
|
29
|
+
const TITLE_PATTERNS = [
|
|
30
|
+
// UI work patterns → Add /test
|
|
31
|
+
{
|
|
32
|
+
pattern: /\bextract\b/i,
|
|
33
|
+
phase: "test",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
reason: "Component extraction typically requires UI testing",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pattern: /\bcomponent\b/i,
|
|
39
|
+
phase: "test",
|
|
40
|
+
confidence: "medium",
|
|
41
|
+
reason: "Component work typically requires UI testing",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
pattern: /\brefactor.*ui\b/i,
|
|
45
|
+
phase: "test",
|
|
46
|
+
confidence: "high",
|
|
47
|
+
reason: "UI refactoring requires browser testing",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
pattern: /\bui\s*(refactor|change|update)\b/i,
|
|
51
|
+
phase: "test",
|
|
52
|
+
confidence: "high",
|
|
53
|
+
reason: "UI changes require browser testing",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: /\bfrontend\b/i,
|
|
57
|
+
phase: "test",
|
|
58
|
+
confidence: "medium",
|
|
59
|
+
reason: "Frontend work typically requires UI testing",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
pattern: /\bdashboard\b/i,
|
|
63
|
+
phase: "test",
|
|
64
|
+
confidence: "medium",
|
|
65
|
+
reason: "Dashboard changes require browser testing",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
pattern: /\badmin\s*(page|panel|ui)\b/i,
|
|
69
|
+
phase: "test",
|
|
70
|
+
confidence: "medium",
|
|
71
|
+
reason: "Admin UI changes require browser testing",
|
|
72
|
+
},
|
|
73
|
+
// Security-sensitive patterns → Add /security-review
|
|
74
|
+
{
|
|
75
|
+
pattern: /\bauth(entication|orization)?\b/i,
|
|
76
|
+
phase: "security-review",
|
|
77
|
+
confidence: "high",
|
|
78
|
+
reason: "Authentication changes require security review",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pattern: /\bpermission[s]?\b/i,
|
|
82
|
+
phase: "security-review",
|
|
83
|
+
confidence: "high",
|
|
84
|
+
reason: "Permission changes require security review",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
pattern: /\bsecurity\b/i,
|
|
88
|
+
phase: "security-review",
|
|
89
|
+
confidence: "high",
|
|
90
|
+
reason: "Security-related changes require security review",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
pattern: /\brole[s]?\s*(based|access|control)\b/i,
|
|
94
|
+
phase: "security-review",
|
|
95
|
+
confidence: "high",
|
|
96
|
+
reason: "Role-based access changes require security review",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
pattern: /\baccess\s*control\b/i,
|
|
100
|
+
phase: "security-review",
|
|
101
|
+
confidence: "high",
|
|
102
|
+
reason: "Access control changes require security review",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
pattern: /\btoken[s]?\b/i,
|
|
106
|
+
phase: "security-review",
|
|
107
|
+
confidence: "medium",
|
|
108
|
+
reason: "Token handling may require security review",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
pattern: /\bpassword[s]?\b/i,
|
|
112
|
+
phase: "security-review",
|
|
113
|
+
confidence: "high",
|
|
114
|
+
reason: "Password handling requires security review",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
pattern: /\bcredential[s]?\b/i,
|
|
118
|
+
phase: "security-review",
|
|
119
|
+
confidence: "high",
|
|
120
|
+
reason: "Credential handling requires security review",
|
|
121
|
+
},
|
|
122
|
+
// Complex work patterns → Enable quality loop
|
|
123
|
+
{
|
|
124
|
+
pattern: /\brefactor\b/i,
|
|
125
|
+
phase: "quality-loop",
|
|
126
|
+
confidence: "medium",
|
|
127
|
+
reason: "Refactoring benefits from quality loop iterations",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
pattern: /\bmigrat(e|ion)\b/i,
|
|
131
|
+
phase: "quality-loop",
|
|
132
|
+
confidence: "high",
|
|
133
|
+
reason: "Migrations are complex and benefit from quality loop",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
pattern: /\brestructur(e|ing)\b/i,
|
|
137
|
+
phase: "quality-loop",
|
|
138
|
+
confidence: "high",
|
|
139
|
+
reason: "Restructuring is complex and benefits from quality loop",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
pattern: /\bbreaking\s*change\b/i,
|
|
143
|
+
phase: "quality-loop",
|
|
144
|
+
confidence: "high",
|
|
145
|
+
reason: "Breaking changes require careful quality validation",
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
/**
|
|
149
|
+
* Body content patterns for phase detection
|
|
150
|
+
*
|
|
151
|
+
* Based on issue #175 specification:
|
|
152
|
+
* | Pattern | Detection |
|
|
153
|
+
* |---------|-----------|
|
|
154
|
+
* | References `.tsx` files | UI work likely |
|
|
155
|
+
* | References `scripts/` | CLI work, needs `/verify` |
|
|
156
|
+
* | Contains "breaking change" | Complex, enable quality loop |
|
|
157
|
+
*/
|
|
158
|
+
const BODY_PATTERNS = [
|
|
159
|
+
// UI work patterns
|
|
160
|
+
{
|
|
161
|
+
pattern: /\.tsx\b/i,
|
|
162
|
+
phase: "test",
|
|
163
|
+
confidence: "medium",
|
|
164
|
+
reason: "References .tsx files indicating React components",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
pattern: /\.jsx\b/i,
|
|
168
|
+
phase: "test",
|
|
169
|
+
confidence: "medium",
|
|
170
|
+
reason: "References .jsx files indicating React components",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
pattern: /\bcomponents?\//i,
|
|
174
|
+
phase: "test",
|
|
175
|
+
confidence: "medium",
|
|
176
|
+
reason: "References components directory",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
pattern: /\bpages?\//i,
|
|
180
|
+
phase: "test",
|
|
181
|
+
confidence: "low",
|
|
182
|
+
reason: "References pages directory (may be UI)",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
pattern: /\bapp\/.*page\.tsx\b/i,
|
|
186
|
+
phase: "test",
|
|
187
|
+
confidence: "high",
|
|
188
|
+
reason: "References Next.js page component",
|
|
189
|
+
},
|
|
190
|
+
// CLI/Script work patterns
|
|
191
|
+
{
|
|
192
|
+
pattern: /\bscripts\//i,
|
|
193
|
+
phase: "exec",
|
|
194
|
+
confidence: "medium",
|
|
195
|
+
reason: "References scripts directory, may need verify phase",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
pattern: /\bbin\//i,
|
|
199
|
+
phase: "exec",
|
|
200
|
+
confidence: "medium",
|
|
201
|
+
reason: "References bin directory, CLI work",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
pattern: /\bcli\b/i,
|
|
205
|
+
phase: "exec",
|
|
206
|
+
confidence: "low",
|
|
207
|
+
reason: "Mentions CLI functionality",
|
|
208
|
+
},
|
|
209
|
+
// Security patterns
|
|
210
|
+
{
|
|
211
|
+
pattern: /\bauth\//i,
|
|
212
|
+
phase: "security-review",
|
|
213
|
+
confidence: "high",
|
|
214
|
+
reason: "References auth directory",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
pattern: /\bmiddleware\.ts\b/i,
|
|
218
|
+
phase: "security-review",
|
|
219
|
+
confidence: "medium",
|
|
220
|
+
reason: "References middleware (often auth-related)",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
pattern: /\brls\s*(polic|rule)/i,
|
|
224
|
+
phase: "security-review",
|
|
225
|
+
confidence: "high",
|
|
226
|
+
reason: "References RLS (Row Level Security)",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
pattern: /\bserver[-_]?action/i,
|
|
230
|
+
phase: "security-review",
|
|
231
|
+
confidence: "medium",
|
|
232
|
+
reason: "Server actions may require security review",
|
|
233
|
+
},
|
|
234
|
+
// Complexity patterns
|
|
235
|
+
{
|
|
236
|
+
pattern: /\bbreaking\s*change\b/i,
|
|
237
|
+
phase: "quality-loop",
|
|
238
|
+
confidence: "high",
|
|
239
|
+
reason: "Breaking change mentioned in body",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
pattern: /\bmajor\s*(refactor|change|update)\b/i,
|
|
243
|
+
phase: "quality-loop",
|
|
244
|
+
confidence: "high",
|
|
245
|
+
reason: "Major changes benefit from quality loop",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
pattern: /\bcomplex\b/i,
|
|
249
|
+
phase: "quality-loop",
|
|
250
|
+
confidence: "low",
|
|
251
|
+
reason: "Complexity mentioned, may benefit from quality loop",
|
|
252
|
+
},
|
|
253
|
+
];
|
|
254
|
+
/**
|
|
255
|
+
* Trivial work patterns (for noting, not phase changes)
|
|
256
|
+
* These are informational - we note them but don't change phases
|
|
257
|
+
*/
|
|
258
|
+
const TRIVIAL_PATTERNS = [
|
|
259
|
+
/\bfix.*unused\b/i,
|
|
260
|
+
/\bremove.*variable\b/i,
|
|
261
|
+
/\btypo\b/i,
|
|
262
|
+
/\btypos\b/i,
|
|
263
|
+
/\bspelling\b/i,
|
|
264
|
+
/\bwhitespace\b/i,
|
|
265
|
+
/\bformat(ting)?\b/i,
|
|
266
|
+
/\blint(ing)?\b/i,
|
|
267
|
+
];
|
|
268
|
+
/**
|
|
269
|
+
* Analyze issue title for phase-relevant keywords
|
|
270
|
+
*
|
|
271
|
+
* @param title - The issue title
|
|
272
|
+
* @returns Array of detected signals
|
|
273
|
+
*/
|
|
274
|
+
export function analyzeTitleForPhases(title) {
|
|
275
|
+
const signals = [];
|
|
276
|
+
for (const { pattern, phase, confidence, reason } of TITLE_PATTERNS) {
|
|
277
|
+
const match = title.match(pattern);
|
|
278
|
+
if (match) {
|
|
279
|
+
signals.push({
|
|
280
|
+
phase,
|
|
281
|
+
source: "title",
|
|
282
|
+
pattern: pattern.source,
|
|
283
|
+
match: match[0],
|
|
284
|
+
confidence,
|
|
285
|
+
reason,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return signals;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Analyze issue body for phase-relevant patterns
|
|
293
|
+
*
|
|
294
|
+
* @param body - The issue body
|
|
295
|
+
* @returns Array of detected signals
|
|
296
|
+
*/
|
|
297
|
+
export function analyzeBodyForPhases(body) {
|
|
298
|
+
const signals = [];
|
|
299
|
+
for (const { pattern, phase, confidence, reason } of BODY_PATTERNS) {
|
|
300
|
+
const match = body.match(pattern);
|
|
301
|
+
if (match) {
|
|
302
|
+
signals.push({
|
|
303
|
+
phase,
|
|
304
|
+
source: "body",
|
|
305
|
+
pattern: pattern.source,
|
|
306
|
+
match: match[0],
|
|
307
|
+
confidence,
|
|
308
|
+
reason,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return signals;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Check if content indicates trivial work
|
|
316
|
+
*
|
|
317
|
+
* @param title - The issue title
|
|
318
|
+
* @param body - The issue body
|
|
319
|
+
* @returns True if the work appears trivial
|
|
320
|
+
*/
|
|
321
|
+
export function isTrivialWork(title, body) {
|
|
322
|
+
const combined = `${title}\n${body}`;
|
|
323
|
+
for (const pattern of TRIVIAL_PATTERNS) {
|
|
324
|
+
if (pattern.test(combined)) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Analyze issue content (title + body) for phase recommendations
|
|
332
|
+
*
|
|
333
|
+
* This is the main entry point for content analysis.
|
|
334
|
+
* It analyzes both title and body, deduplicates signals,
|
|
335
|
+
* and returns a consolidated result.
|
|
336
|
+
*
|
|
337
|
+
* @param title - The issue title
|
|
338
|
+
* @param body - The issue body
|
|
339
|
+
* @returns Consolidated analysis result with phases and signals
|
|
340
|
+
*/
|
|
341
|
+
export function analyzeContentForPhases(title, body) {
|
|
342
|
+
const titleSignals = analyzeTitleForPhases(title);
|
|
343
|
+
const bodySignals = analyzeBodyForPhases(body);
|
|
344
|
+
// Combine all signals
|
|
345
|
+
const allSignals = [...titleSignals, ...bodySignals];
|
|
346
|
+
// Deduplicate phases (keep highest confidence signal for each phase)
|
|
347
|
+
const phaseMap = new Map();
|
|
348
|
+
const confidenceRank = {
|
|
349
|
+
high: 3,
|
|
350
|
+
medium: 2,
|
|
351
|
+
low: 1,
|
|
352
|
+
};
|
|
353
|
+
for (const signal of allSignals) {
|
|
354
|
+
const existing = phaseMap.get(signal.phase);
|
|
355
|
+
const currentRank = confidenceRank[signal.confidence];
|
|
356
|
+
if (!existing || currentRank > existing.confidence) {
|
|
357
|
+
phaseMap.set(signal.phase, { signal, confidence: currentRank });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Extract phases and quality loop setting
|
|
361
|
+
const phases = [];
|
|
362
|
+
let qualityLoop = false;
|
|
363
|
+
for (const [phase] of phaseMap) {
|
|
364
|
+
if (phase === "quality-loop") {
|
|
365
|
+
qualityLoop = true;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
phases.push(phase);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Build notes
|
|
372
|
+
const notes = [];
|
|
373
|
+
if (isTrivialWork(title, body)) {
|
|
374
|
+
notes.push("Trivial work detected - may not require full workflow");
|
|
375
|
+
}
|
|
376
|
+
if (phases.includes("test")) {
|
|
377
|
+
notes.push("UI/component work detected - browser testing recommended");
|
|
378
|
+
}
|
|
379
|
+
if (phases.includes("security-review")) {
|
|
380
|
+
notes.push("Security-sensitive content detected - security review recommended");
|
|
381
|
+
}
|
|
382
|
+
if (qualityLoop) {
|
|
383
|
+
notes.push("Complex work detected - quality loop recommended");
|
|
384
|
+
}
|
|
385
|
+
if (allSignals.length === 0) {
|
|
386
|
+
notes.push("No phase signals detected from content analysis");
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
phases,
|
|
390
|
+
qualityLoop,
|
|
391
|
+
signals: allSignals,
|
|
392
|
+
notes,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Format content analysis result for display
|
|
397
|
+
*
|
|
398
|
+
* @param result - The analysis result
|
|
399
|
+
* @returns Formatted markdown string
|
|
400
|
+
*/
|
|
401
|
+
export function formatContentAnalysis(result) {
|
|
402
|
+
const lines = [];
|
|
403
|
+
lines.push("## Content Analysis");
|
|
404
|
+
lines.push("");
|
|
405
|
+
if (result.signals.length === 0) {
|
|
406
|
+
lines.push("No phase-relevant patterns detected in title or body.");
|
|
407
|
+
return lines.join("\n");
|
|
408
|
+
}
|
|
409
|
+
lines.push("### Detected Signals");
|
|
410
|
+
lines.push("");
|
|
411
|
+
lines.push("| Source | Pattern | Match | Phase | Confidence | Reason |");
|
|
412
|
+
lines.push("|--------|---------|-------|-------|------------|--------|");
|
|
413
|
+
for (const signal of result.signals) {
|
|
414
|
+
const phaseDisplay = signal.phase === "quality-loop" ? "quality-loop" : `/${signal.phase}`;
|
|
415
|
+
lines.push(`| ${signal.source} | \`${signal.pattern}\` | "${signal.match}" | ${phaseDisplay} | ${signal.confidence} | ${signal.reason} |`);
|
|
416
|
+
}
|
|
417
|
+
lines.push("");
|
|
418
|
+
if (result.phases.length > 0 || result.qualityLoop) {
|
|
419
|
+
lines.push("### Recommendations");
|
|
420
|
+
lines.push("");
|
|
421
|
+
if (result.phases.length > 0) {
|
|
422
|
+
lines.push(`**Additional phases:** ${result.phases.map((p) => `/${p}`).join(", ")}`);
|
|
423
|
+
}
|
|
424
|
+
if (result.qualityLoop) {
|
|
425
|
+
lines.push("**Quality loop:** Recommended based on content complexity");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (result.notes.length > 0) {
|
|
429
|
+
lines.push("");
|
|
430
|
+
lines.push("### Notes");
|
|
431
|
+
lines.push("");
|
|
432
|
+
for (const note of result.notes) {
|
|
433
|
+
lines.push(`- ${note}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return lines.join("\n");
|
|
437
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase Signal Types and Merging
|
|
3
|
+
*
|
|
4
|
+
* Provides types for tracking where phase recommendations come from
|
|
5
|
+
* and logic for merging signals with priority:
|
|
6
|
+
* Labels > Solve Comment > Title > Body
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { PhaseSignal, mergePhaseSignals } from './phase-signal';
|
|
11
|
+
*
|
|
12
|
+
* const signals: PhaseSignal[] = [
|
|
13
|
+
* { phase: 'test', source: 'label', confidence: 'high' },
|
|
14
|
+
* { phase: 'test', source: 'title', confidence: 'medium' },
|
|
15
|
+
* { phase: 'security-review', source: 'body', confidence: 'high' },
|
|
16
|
+
* ];
|
|
17
|
+
*
|
|
18
|
+
* const merged = mergePhaseSignals(signals);
|
|
19
|
+
* // Returns unique phases with highest-priority source for each
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { Phase } from "./workflow/types.js";
|
|
23
|
+
/**
|
|
24
|
+
* Source of a phase signal, ordered by priority (highest first)
|
|
25
|
+
*/
|
|
26
|
+
export type SignalSource = "label" | "solve" | "title" | "body";
|
|
27
|
+
/**
|
|
28
|
+
* Priority order for signal sources (higher = takes precedence)
|
|
29
|
+
*/
|
|
30
|
+
export declare const SIGNAL_PRIORITY: Record<SignalSource, number>;
|
|
31
|
+
/**
|
|
32
|
+
* Confidence level for a signal
|
|
33
|
+
*/
|
|
34
|
+
export type SignalConfidence = "high" | "medium" | "low";
|
|
35
|
+
/**
|
|
36
|
+
* A phase signal with source tracking
|
|
37
|
+
*/
|
|
38
|
+
export interface PhaseSignal {
|
|
39
|
+
/** The phase being recommended */
|
|
40
|
+
phase: Phase | "quality-loop";
|
|
41
|
+
/** Where this signal came from */
|
|
42
|
+
source: SignalSource;
|
|
43
|
+
/** Confidence level of the signal */
|
|
44
|
+
confidence: SignalConfidence;
|
|
45
|
+
/** Human-readable reason for this signal */
|
|
46
|
+
reason?: string;
|
|
47
|
+
/** The pattern or keyword that matched (for content signals) */
|
|
48
|
+
match?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Result of merging phase signals
|
|
52
|
+
*/
|
|
53
|
+
export interface MergedPhaseResult {
|
|
54
|
+
/** Unique phases to include in workflow */
|
|
55
|
+
phases: Phase[];
|
|
56
|
+
/** Whether quality loop should be enabled */
|
|
57
|
+
qualityLoop: boolean;
|
|
58
|
+
/** Map of phase to the signal that contributed it */
|
|
59
|
+
phaseSignals: Map<Phase | "quality-loop", PhaseSignal>;
|
|
60
|
+
/** All original signals (for debugging/display) */
|
|
61
|
+
allSignals: PhaseSignal[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Merge phase signals with priority-based deduplication
|
|
65
|
+
*
|
|
66
|
+
* Priority order: Labels > Solve > Title > Body
|
|
67
|
+
* When multiple signals suggest the same phase, the highest-priority
|
|
68
|
+
* source wins. Signals can only ADD phases, never remove.
|
|
69
|
+
*
|
|
70
|
+
* @param signals - Array of phase signals from various sources
|
|
71
|
+
* @returns Merged result with unique phases and source tracking
|
|
72
|
+
*/
|
|
73
|
+
export declare function mergePhaseSignals(signals: PhaseSignal[]): MergedPhaseResult;
|
|
74
|
+
/**
|
|
75
|
+
* Create a phase signal from a label
|
|
76
|
+
*
|
|
77
|
+
* @param labelName - The GitHub label name
|
|
78
|
+
* @returns Phase signal if the label maps to a phase, null otherwise
|
|
79
|
+
*/
|
|
80
|
+
export declare function signalFromLabel(labelName: string): PhaseSignal | null;
|
|
81
|
+
/**
|
|
82
|
+
* Create phase signals from an array of labels
|
|
83
|
+
*
|
|
84
|
+
* @param labels - Array of GitHub label names
|
|
85
|
+
* @returns Array of phase signals from labels
|
|
86
|
+
*/
|
|
87
|
+
export declare function signalsFromLabels(labels: string[]): PhaseSignal[];
|
|
88
|
+
/**
|
|
89
|
+
* Format merged phase result for display
|
|
90
|
+
*
|
|
91
|
+
* @param result - The merged phase result
|
|
92
|
+
* @returns Formatted markdown string
|
|
93
|
+
*/
|
|
94
|
+
export declare function formatMergedPhases(result: MergedPhaseResult): string;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase Signal Types and Merging
|
|
3
|
+
*
|
|
4
|
+
* Provides types for tracking where phase recommendations come from
|
|
5
|
+
* and logic for merging signals with priority:
|
|
6
|
+
* Labels > Solve Comment > Title > Body
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { PhaseSignal, mergePhaseSignals } from './phase-signal';
|
|
11
|
+
*
|
|
12
|
+
* const signals: PhaseSignal[] = [
|
|
13
|
+
* { phase: 'test', source: 'label', confidence: 'high' },
|
|
14
|
+
* { phase: 'test', source: 'title', confidence: 'medium' },
|
|
15
|
+
* { phase: 'security-review', source: 'body', confidence: 'high' },
|
|
16
|
+
* ];
|
|
17
|
+
*
|
|
18
|
+
* const merged = mergePhaseSignals(signals);
|
|
19
|
+
* // Returns unique phases with highest-priority source for each
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Priority order for signal sources (higher = takes precedence)
|
|
24
|
+
*/
|
|
25
|
+
export const SIGNAL_PRIORITY = {
|
|
26
|
+
label: 4, // Highest priority - explicit labels
|
|
27
|
+
solve: 3, // Solve command analysis
|
|
28
|
+
title: 2, // Title keyword detection
|
|
29
|
+
body: 1, // Body pattern detection (lowest)
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Merge phase signals with priority-based deduplication
|
|
33
|
+
*
|
|
34
|
+
* Priority order: Labels > Solve > Title > Body
|
|
35
|
+
* When multiple signals suggest the same phase, the highest-priority
|
|
36
|
+
* source wins. Signals can only ADD phases, never remove.
|
|
37
|
+
*
|
|
38
|
+
* @param signals - Array of phase signals from various sources
|
|
39
|
+
* @returns Merged result with unique phases and source tracking
|
|
40
|
+
*/
|
|
41
|
+
export function mergePhaseSignals(signals) {
|
|
42
|
+
// Map to track the best signal for each phase
|
|
43
|
+
const phaseSignals = new Map();
|
|
44
|
+
for (const signal of signals) {
|
|
45
|
+
const existing = phaseSignals.get(signal.phase);
|
|
46
|
+
if (!existing) {
|
|
47
|
+
// First signal for this phase
|
|
48
|
+
phaseSignals.set(signal.phase, signal);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Compare priorities - higher wins
|
|
52
|
+
const existingPriority = SIGNAL_PRIORITY[existing.source];
|
|
53
|
+
const newPriority = SIGNAL_PRIORITY[signal.source];
|
|
54
|
+
if (newPriority > existingPriority) {
|
|
55
|
+
phaseSignals.set(signal.phase, signal);
|
|
56
|
+
}
|
|
57
|
+
// If same priority, keep the existing one (first wins)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Extract phases and quality loop setting
|
|
61
|
+
const phases = [];
|
|
62
|
+
let qualityLoop = false;
|
|
63
|
+
for (const [phase] of phaseSignals) {
|
|
64
|
+
if (phase === "quality-loop") {
|
|
65
|
+
qualityLoop = true;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
phases.push(phase);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
phases,
|
|
73
|
+
qualityLoop,
|
|
74
|
+
phaseSignals,
|
|
75
|
+
allSignals: signals,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a phase signal from a label
|
|
80
|
+
*
|
|
81
|
+
* @param labelName - The GitHub label name
|
|
82
|
+
* @returns Phase signal if the label maps to a phase, null otherwise
|
|
83
|
+
*/
|
|
84
|
+
export function signalFromLabel(labelName) {
|
|
85
|
+
const lowerLabel = labelName.toLowerCase();
|
|
86
|
+
// UI/Frontend labels → test phase
|
|
87
|
+
if (["ui", "frontend", "admin"].includes(lowerLabel)) {
|
|
88
|
+
return {
|
|
89
|
+
phase: "test",
|
|
90
|
+
source: "label",
|
|
91
|
+
confidence: "high",
|
|
92
|
+
reason: `Label '${labelName}' indicates UI work requiring browser testing`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Security labels → security-review phase
|
|
96
|
+
if (["security", "auth", "permissions"].includes(lowerLabel)) {
|
|
97
|
+
return {
|
|
98
|
+
phase: "security-review",
|
|
99
|
+
source: "label",
|
|
100
|
+
confidence: "high",
|
|
101
|
+
reason: `Label '${labelName}' indicates security-sensitive changes`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Complex work labels → quality loop
|
|
105
|
+
if (["complex", "refactor", "breaking", "major"].includes(lowerLabel)) {
|
|
106
|
+
return {
|
|
107
|
+
phase: "quality-loop",
|
|
108
|
+
source: "label",
|
|
109
|
+
confidence: "high",
|
|
110
|
+
reason: `Label '${labelName}' indicates complex work benefiting from quality loop`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Backend labels → no specific phase (but note for test skipping)
|
|
114
|
+
if (["backend", "api", "cli"].includes(lowerLabel)) {
|
|
115
|
+
return null; // Backend work doesn't add phases, but we may skip /test
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create phase signals from an array of labels
|
|
121
|
+
*
|
|
122
|
+
* @param labels - Array of GitHub label names
|
|
123
|
+
* @returns Array of phase signals from labels
|
|
124
|
+
*/
|
|
125
|
+
export function signalsFromLabels(labels) {
|
|
126
|
+
const signals = [];
|
|
127
|
+
for (const label of labels) {
|
|
128
|
+
const signal = signalFromLabel(label);
|
|
129
|
+
if (signal) {
|
|
130
|
+
signals.push(signal);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return signals;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Format merged phase result for display
|
|
137
|
+
*
|
|
138
|
+
* @param result - The merged phase result
|
|
139
|
+
* @returns Formatted markdown string
|
|
140
|
+
*/
|
|
141
|
+
export function formatMergedPhases(result) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
lines.push("## Phase Signal Summary");
|
|
144
|
+
lines.push("");
|
|
145
|
+
if (result.allSignals.length === 0) {
|
|
146
|
+
lines.push("No phase signals detected.");
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
149
|
+
lines.push("### Signal Sources");
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push("| Phase | Source | Confidence | Reason |");
|
|
152
|
+
lines.push("|-------|--------|------------|--------|");
|
|
153
|
+
for (const [phase, signal] of result.phaseSignals) {
|
|
154
|
+
const phaseDisplay = phase === "quality-loop" ? "quality-loop" : `/${phase}`;
|
|
155
|
+
const reason = signal.reason || "-";
|
|
156
|
+
lines.push(`| ${phaseDisplay} | ${signal.source} | ${signal.confidence} | ${reason} |`);
|
|
157
|
+
}
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push("### Final Recommendations");
|
|
160
|
+
lines.push("");
|
|
161
|
+
if (result.phases.length > 0) {
|
|
162
|
+
lines.push(`**Phases to add:** ${result.phases.map((p) => `/${p}`).join(", ")}`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
lines.push("**Phases to add:** None");
|
|
166
|
+
}
|
|
167
|
+
if (result.qualityLoop) {
|
|
168
|
+
lines.push("**Quality loop:** Enabled");
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|