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.
Files changed (53) hide show
  1. package/README.md +10 -8
  2. package/dist/bin/cli.js +19 -9
  3. package/dist/src/commands/doctor.js +42 -20
  4. package/dist/src/commands/init.js +152 -65
  5. package/dist/src/commands/logs.js +7 -6
  6. package/dist/src/commands/run.d.ts +13 -1
  7. package/dist/src/commands/run.js +122 -32
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/commands/sync.d.ts +28 -0
  11. package/dist/src/commands/sync.js +102 -0
  12. package/dist/src/index.d.ts +6 -0
  13. package/dist/src/index.js +4 -0
  14. package/dist/src/lib/cli-ui.d.ts +196 -0
  15. package/dist/src/lib/cli-ui.js +544 -0
  16. package/dist/src/lib/content-analyzer.d.ts +89 -0
  17. package/dist/src/lib/content-analyzer.js +437 -0
  18. package/dist/src/lib/phase-signal.d.ts +94 -0
  19. package/dist/src/lib/phase-signal.js +171 -0
  20. package/dist/src/lib/phase-spinner.d.ts +146 -0
  21. package/dist/src/lib/phase-spinner.js +255 -0
  22. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  23. package/dist/src/lib/solve-comment-parser.js +200 -0
  24. package/dist/src/lib/stack-config.d.ts +51 -0
  25. package/dist/src/lib/stack-config.js +77 -0
  26. package/dist/src/lib/stacks.d.ts +52 -0
  27. package/dist/src/lib/stacks.js +173 -0
  28. package/dist/src/lib/templates.d.ts +2 -0
  29. package/dist/src/lib/templates.js +9 -2
  30. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  31. package/dist/src/lib/upstream/assessment.js +385 -0
  32. package/dist/src/lib/upstream/index.d.ts +11 -0
  33. package/dist/src/lib/upstream/index.js +14 -0
  34. package/dist/src/lib/upstream/issues.d.ts +38 -0
  35. package/dist/src/lib/upstream/issues.js +267 -0
  36. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  37. package/dist/src/lib/upstream/relevance.js +209 -0
  38. package/dist/src/lib/upstream/report.d.ts +29 -0
  39. package/dist/src/lib/upstream/report.js +391 -0
  40. package/dist/src/lib/upstream/types.d.ts +207 -0
  41. package/dist/src/lib/upstream/types.js +5 -0
  42. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  43. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  44. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  45. package/dist/src/lib/workflow/qa-cache.js +440 -0
  46. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  47. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  48. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  49. package/dist/src/lib/workflow/types.d.ts +4 -0
  50. package/package.json +6 -1
  51. package/templates/skills/qa/scripts/quality-checks.sh +509 -53
  52. package/templates/skills/solve/SKILL.md +375 -83
  53. 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
+ }