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,544 @@
1
+ /**
2
+ * Centralized CLI UI module for Sequant
3
+ *
4
+ * Provides consistent styling, spinners, boxes, tables, and branding
5
+ * with graceful fallbacks for non-TTY, CI, and legacy terminals.
6
+ */
7
+ import chalk from "chalk";
8
+ import ora from "ora";
9
+ import boxen from "boxen";
10
+ // cli-table3 uses CommonJS `export =` syntax, default import works with esModuleInterop
11
+ import Table from "cli-table3";
12
+ import gradient from "gradient-string";
13
+ import { isCI, isStdoutTTY } from "./tty.js";
14
+ /**
15
+ * Global UI configuration state
16
+ */
17
+ let config = {
18
+ noColor: false,
19
+ jsonMode: false,
20
+ verbose: false,
21
+ isTTY: isStdoutTTY(),
22
+ isCI: isCI(),
23
+ minimal: false,
24
+ };
25
+ /**
26
+ * Configure UI settings
27
+ *
28
+ * Call this early in CLI startup to set global options.
29
+ */
30
+ export function configureUI(options) {
31
+ config = { ...config, ...options };
32
+ // Check environment variables
33
+ if (process.env.NO_COLOR || process.env.SEQUANT_MINIMAL === "1") {
34
+ config.noColor = !!process.env.NO_COLOR;
35
+ config.minimal = process.env.SEQUANT_MINIMAL === "1";
36
+ }
37
+ // Auto-configure for CI
38
+ if (config.isCI && !options.minimal) {
39
+ config.minimal = true;
40
+ }
41
+ }
42
+ /**
43
+ * Get current UI configuration
44
+ */
45
+ export function getUIConfig() {
46
+ return { ...config };
47
+ }
48
+ /**
49
+ * Check if decorative output should be shown
50
+ */
51
+ function shouldShowDecorative() {
52
+ return !config.jsonMode && !config.minimal && config.isTTY;
53
+ }
54
+ /**
55
+ * Check if animations should be used
56
+ */
57
+ function shouldAnimate() {
58
+ return (!config.jsonMode &&
59
+ !config.verbose &&
60
+ !config.isCI &&
61
+ config.isTTY &&
62
+ !config.noColor);
63
+ }
64
+ // ============================================================================
65
+ // Color Palette
66
+ // ============================================================================
67
+ /**
68
+ * Standardized color palette for consistent styling
69
+ */
70
+ export const colors = {
71
+ // Semantic colors
72
+ success: config.noColor ? (s) => s : chalk.green,
73
+ error: config.noColor ? (s) => s : chalk.red,
74
+ warning: config.noColor ? (s) => s : chalk.yellow,
75
+ info: config.noColor ? (s) => s : chalk.blue,
76
+ muted: config.noColor ? (s) => s : chalk.gray,
77
+ // UI elements
78
+ header: config.noColor ? (s) => s : chalk.blue.bold,
79
+ label: config.noColor ? (s) => s : chalk.cyan,
80
+ value: config.noColor ? (s) => s : chalk.white,
81
+ accent: config.noColor ? (s) => s : chalk.cyan,
82
+ bold: config.noColor ? (s) => s : chalk.bold,
83
+ // Status colors
84
+ pending: config.noColor ? (s) => s : chalk.gray,
85
+ running: config.noColor ? (s) => s : chalk.cyan,
86
+ completed: config.noColor ? (s) => s : chalk.green,
87
+ failed: config.noColor ? (s) => s : chalk.red,
88
+ };
89
+ /**
90
+ * Get color function respecting current config
91
+ */
92
+ function getColor(colorFn) {
93
+ return config.noColor ? (s) => s : colorFn;
94
+ }
95
+ // ============================================================================
96
+ // Windows Compatibility
97
+ // ============================================================================
98
+ /**
99
+ * Detect if running on legacy Windows terminal
100
+ */
101
+ function isLegacyWindows() {
102
+ return (process.platform === "win32" &&
103
+ !process.env.WT_SESSION && // Not Windows Terminal
104
+ !process.env.TERM_PROGRAM // Not VS Code terminal
105
+ );
106
+ }
107
+ // ============================================================================
108
+ // ASCII Logo (Static - No figlet)
109
+ // ============================================================================
110
+ /**
111
+ * Static ASCII logo for SEQUANT branding
112
+ * Pre-generated to avoid ~1MB figlet font bundle
113
+ */
114
+ const ASCII_LOGO = `
115
+ _____ _____ _____ _ _ _____ _ _ _____
116
+ / ___/ ___| _ | | | | _ | \\ | |_ _|
117
+ \\ \`--.\\ \`--.| | | | | | | | | | \\| | | |
118
+ \`--. \\\`--. \\ \\_/ / | | \\_| |_| . \` | | |
119
+ /\\__/ /\\__/ /\\___/\\ |_| |\\___/\\_|\\_/ \\_/
120
+ \\____/\\____/ \\___/
121
+ `.trimStart();
122
+ /**
123
+ * Get the ASCII logo with optional gradient
124
+ */
125
+ export function logo() {
126
+ if (config.jsonMode || config.minimal)
127
+ return "";
128
+ if (config.noColor || !config.isTTY)
129
+ return ASCII_LOGO;
130
+ // Apply gradient for color terminals
131
+ const sequantGradient = gradient(["#00D4FF", "#7B68EE", "#FF6B9D"]);
132
+ return sequantGradient(ASCII_LOGO);
133
+ }
134
+ /**
135
+ * Get the banner (logo + tagline)
136
+ */
137
+ export function banner() {
138
+ if (config.jsonMode || config.minimal)
139
+ return "";
140
+ const logoText = logo();
141
+ const tagline = config.noColor
142
+ ? " Quantize your development workflow"
143
+ : chalk.gray(" Quantize your development workflow");
144
+ return `${logoText}\n${tagline}\n`;
145
+ }
146
+ /**
147
+ * Text-only spinner for verbose mode / non-TTY
148
+ */
149
+ class TextSpinner {
150
+ text;
151
+ isSpinning = false;
152
+ constructor(initialText) {
153
+ this.text = initialText;
154
+ }
155
+ start(text) {
156
+ if (text)
157
+ this.text = text;
158
+ this.isSpinning = true;
159
+ if (!config.jsonMode) {
160
+ console.log(getColor(chalk.cyan)(`\u23F3 ${this.text}`));
161
+ }
162
+ return this;
163
+ }
164
+ succeed(text) {
165
+ if (text)
166
+ this.text = text;
167
+ this.isSpinning = false;
168
+ if (!config.jsonMode) {
169
+ console.log(getColor(chalk.green)(`\u2713 ${this.text}`));
170
+ }
171
+ return this;
172
+ }
173
+ fail(text) {
174
+ if (text)
175
+ this.text = text;
176
+ this.isSpinning = false;
177
+ if (!config.jsonMode) {
178
+ console.log(getColor(chalk.red)(`\u2717 ${this.text}`));
179
+ }
180
+ return this;
181
+ }
182
+ warn(text) {
183
+ if (text)
184
+ this.text = text;
185
+ this.isSpinning = false;
186
+ if (!config.jsonMode) {
187
+ console.log(getColor(chalk.yellow)(`\u26A0 ${this.text}`));
188
+ }
189
+ return this;
190
+ }
191
+ stop() {
192
+ this.isSpinning = false;
193
+ return this;
194
+ }
195
+ }
196
+ /**
197
+ * Animated spinner wrapper
198
+ */
199
+ class AnimatedSpinner {
200
+ spinner;
201
+ constructor(initialText) {
202
+ this.spinner = ora({
203
+ text: initialText,
204
+ color: "cyan",
205
+ spinner: "dots",
206
+ });
207
+ }
208
+ get text() {
209
+ return this.spinner.text;
210
+ }
211
+ set text(value) {
212
+ this.spinner.text = value;
213
+ }
214
+ get isSpinning() {
215
+ return this.spinner.isSpinning;
216
+ }
217
+ start(text) {
218
+ if (text)
219
+ this.spinner.text = text;
220
+ this.spinner.start();
221
+ return this;
222
+ }
223
+ succeed(text) {
224
+ this.spinner.succeed(text);
225
+ return this;
226
+ }
227
+ fail(text) {
228
+ this.spinner.fail(text);
229
+ return this;
230
+ }
231
+ warn(text) {
232
+ this.spinner.warn(text);
233
+ return this;
234
+ }
235
+ stop() {
236
+ this.spinner.stop();
237
+ return this;
238
+ }
239
+ }
240
+ /**
241
+ * Create a spinner with automatic fallback
242
+ *
243
+ * Returns an animated spinner for TTY, text-only for non-TTY/verbose/CI.
244
+ */
245
+ export function spinner(text) {
246
+ if (shouldAnimate()) {
247
+ return new AnimatedSpinner(text);
248
+ }
249
+ return new TextSpinner(text);
250
+ }
251
+ /**
252
+ * Get boxen options for a given style
253
+ */
254
+ function getBoxOptions(style) {
255
+ const baseOptions = {
256
+ padding: 1,
257
+ borderStyle: isLegacyWindows() ? "classic" : "round",
258
+ };
259
+ switch (style) {
260
+ case "success":
261
+ return {
262
+ ...baseOptions,
263
+ borderColor: config.noColor ? undefined : "green",
264
+ };
265
+ case "error":
266
+ return {
267
+ ...baseOptions,
268
+ borderColor: config.noColor ? undefined : "red",
269
+ };
270
+ case "warning":
271
+ return {
272
+ ...baseOptions,
273
+ borderColor: config.noColor ? undefined : "yellow",
274
+ };
275
+ case "info":
276
+ return {
277
+ ...baseOptions,
278
+ borderColor: config.noColor ? undefined : "blue",
279
+ };
280
+ case "header":
281
+ return {
282
+ ...baseOptions,
283
+ borderColor: config.noColor ? undefined : "cyan",
284
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
285
+ };
286
+ default:
287
+ return baseOptions;
288
+ }
289
+ }
290
+ /**
291
+ * Create a boxed message
292
+ */
293
+ export function box(content, style = "default") {
294
+ if (config.jsonMode)
295
+ return "";
296
+ if (!shouldShowDecorative()) {
297
+ // Fallback to simple bordered output
298
+ return content;
299
+ }
300
+ return boxen(content, getBoxOptions(style));
301
+ }
302
+ /**
303
+ * Create a success box with title and message
304
+ */
305
+ export function successBox(title, message) {
306
+ const content = `${getColor(chalk.green.bold)(`\u2705 ${title}`)}\n\n${message}`;
307
+ return box(content, "success");
308
+ }
309
+ /**
310
+ * Create an error box with title and message
311
+ */
312
+ export function errorBox(title, message) {
313
+ const content = `${getColor(chalk.red.bold)(`\u274C ${title}`)}\n\n${message}`;
314
+ return box(content, "error");
315
+ }
316
+ /**
317
+ * Create a warning box with title and message
318
+ */
319
+ export function warningBox(title, message) {
320
+ const content = `${getColor(chalk.yellow.bold)(`\u26A0\uFE0F ${title}`)}\n\n${message}`;
321
+ return box(content, "warning");
322
+ }
323
+ /**
324
+ * Create a header box
325
+ */
326
+ export function headerBox(title) {
327
+ const content = getColor(chalk.bold)(title);
328
+ return box(content, "header");
329
+ }
330
+ /**
331
+ * Create a formatted table
332
+ */
333
+ export function table(rows, options) {
334
+ if (config.jsonMode)
335
+ return "";
336
+ const tableInstance = new Table({
337
+ head: options.columns.map((col) => config.noColor ? col.header : chalk.cyan.bold(col.header)),
338
+ colWidths: options.columns.map((col) => col.width ?? null),
339
+ colAligns: options.columns.map((col) => col.align || "left"),
340
+ style: {
341
+ head: [],
342
+ border: config.noColor ? [] : ["gray"],
343
+ },
344
+ chars: isLegacyWindows()
345
+ ? {
346
+ top: "-",
347
+ "top-mid": "+",
348
+ "top-left": "+",
349
+ "top-right": "+",
350
+ bottom: "-",
351
+ "bottom-mid": "+",
352
+ "bottom-left": "+",
353
+ "bottom-right": "+",
354
+ left: "|",
355
+ "left-mid": "+",
356
+ mid: "-",
357
+ "mid-mid": "+",
358
+ right: "|",
359
+ "right-mid": "+",
360
+ middle: "|",
361
+ }
362
+ : undefined,
363
+ });
364
+ for (const row of rows) {
365
+ tableInstance.push(row.map(String));
366
+ }
367
+ return tableInstance.toString();
368
+ }
369
+ /**
370
+ * Create a simple key-value table
371
+ */
372
+ export function keyValueTable(data) {
373
+ if (config.jsonMode)
374
+ return "";
375
+ const rows = Object.entries(data).map(([key, value]) => [
376
+ config.noColor ? key : chalk.cyan(key),
377
+ String(value),
378
+ ]);
379
+ const tableInstance = new Table({
380
+ style: {
381
+ head: [],
382
+ border: config.noColor ? [] : ["gray"],
383
+ },
384
+ chars: isLegacyWindows()
385
+ ? {
386
+ top: "-",
387
+ "top-mid": "+",
388
+ "top-left": "+",
389
+ "top-right": "+",
390
+ bottom: "-",
391
+ "bottom-mid": "+",
392
+ "bottom-left": "+",
393
+ "bottom-right": "+",
394
+ left: "|",
395
+ "left-mid": "+",
396
+ mid: "-",
397
+ "mid-mid": "+",
398
+ right: "|",
399
+ "right-mid": "+",
400
+ middle: "|",
401
+ }
402
+ : undefined,
403
+ });
404
+ for (const row of rows) {
405
+ tableInstance.push(row);
406
+ }
407
+ return tableInstance.toString();
408
+ }
409
+ /**
410
+ * Get a status icon
411
+ */
412
+ export function statusIcon(type) {
413
+ if (config.noColor) {
414
+ switch (type) {
415
+ case "success":
416
+ return "[OK]";
417
+ case "error":
418
+ return "[FAIL]";
419
+ case "warning":
420
+ return "[WARN]";
421
+ case "pending":
422
+ return "[ ]";
423
+ case "running":
424
+ return "[..]";
425
+ }
426
+ }
427
+ switch (type) {
428
+ case "success":
429
+ return chalk.green("\u2713");
430
+ case "error":
431
+ return chalk.red("\u2717");
432
+ case "warning":
433
+ return chalk.yellow("\u26A0");
434
+ case "pending":
435
+ return chalk.gray("\u25CB");
436
+ case "running":
437
+ return chalk.cyan("\u25D0");
438
+ }
439
+ }
440
+ /**
441
+ * Print a status message with icon
442
+ */
443
+ export function printStatus(type, message) {
444
+ if (config.jsonMode)
445
+ return;
446
+ console.log(`${statusIcon(type)} ${message}`);
447
+ }
448
+ // ============================================================================
449
+ // Layout Utilities
450
+ // ============================================================================
451
+ /**
452
+ * Create a horizontal divider
453
+ */
454
+ export function divider(width = 50) {
455
+ if (config.jsonMode)
456
+ return "";
457
+ const char = isLegacyWindows() ? "-" : "\u2501";
458
+ const line = char.repeat(width);
459
+ return config.noColor ? line : chalk.gray(line);
460
+ }
461
+ /**
462
+ * Create a section header
463
+ */
464
+ export function sectionHeader(title) {
465
+ if (config.jsonMode)
466
+ return "";
467
+ const formattedTitle = config.noColor ? title : chalk.blue.bold(title);
468
+ return `\n${formattedTitle}\n${divider(title.length + 4)}\n`;
469
+ }
470
+ /**
471
+ * Display phase progress as a visual indicator
472
+ */
473
+ export function phaseProgress(phases) {
474
+ if (config.jsonMode)
475
+ return "";
476
+ const icons = phases.map((phase) => {
477
+ switch (phase.status) {
478
+ case "success":
479
+ return config.noColor ? "[OK]" : chalk.green("\u25CF");
480
+ case "failure":
481
+ return config.noColor ? "[X]" : chalk.red("\u2717");
482
+ case "running":
483
+ return config.noColor ? "[..]" : chalk.cyan("\u25D0");
484
+ case "skipped":
485
+ return config.noColor ? "[-]" : chalk.gray("-");
486
+ default:
487
+ return config.noColor ? "[ ]" : chalk.gray("\u25CB");
488
+ }
489
+ });
490
+ const labels = phases.map((phase) => phase.name.charAt(0).toUpperCase());
491
+ return ` Phases: ${icons.join(" ")}\n ${labels.join(" ")}`;
492
+ }
493
+ /**
494
+ * Create a progress bar
495
+ */
496
+ export function progressBar(current, total, width = 20) {
497
+ if (config.jsonMode)
498
+ return "";
499
+ const percentage = total > 0 ? current / total : 0;
500
+ const filled = Math.round(percentage * width);
501
+ const empty = width - filled;
502
+ const filledChar = config.noColor || isLegacyWindows() ? "#" : "\u2588";
503
+ const emptyChar = config.noColor || isLegacyWindows() ? "-" : "\u2591";
504
+ const bar = filledChar.repeat(filled) + emptyChar.repeat(empty);
505
+ return config.noColor
506
+ ? bar
507
+ : chalk.green(filledChar.repeat(filled)) +
508
+ chalk.gray(emptyChar.repeat(empty));
509
+ }
510
+ // ============================================================================
511
+ // Unified UI Namespace
512
+ // ============================================================================
513
+ /**
514
+ * Unified UI object for convenient access to all utilities
515
+ */
516
+ export const ui = {
517
+ // Config
518
+ configure: configureUI,
519
+ getConfig: getUIConfig,
520
+ // Branding
521
+ logo,
522
+ banner,
523
+ // Spinners
524
+ spinner,
525
+ // Boxes
526
+ box,
527
+ successBox,
528
+ errorBox,
529
+ warningBox,
530
+ headerBox,
531
+ // Tables
532
+ table,
533
+ keyValueTable,
534
+ // Status
535
+ statusIcon,
536
+ printStatus,
537
+ // Layout
538
+ divider,
539
+ sectionHeader,
540
+ // Progress
541
+ phaseProgress,
542
+ progressBar,
543
+ };
544
+ export default ui;
@@ -0,0 +1,89 @@
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
+ import type { Phase } from "./workflow/types.js";
19
+ /**
20
+ * A signal detected from content analysis
21
+ */
22
+ export interface ContentSignal {
23
+ /** The phase this signal suggests */
24
+ phase: Phase | "quality-loop";
25
+ /** Where the signal was detected */
26
+ source: "title" | "body";
27
+ /** The pattern or keyword that matched */
28
+ pattern: string;
29
+ /** The actual text that matched */
30
+ match: string;
31
+ /** Confidence level of the signal */
32
+ confidence: "high" | "medium" | "low";
33
+ /** Human-readable reason for this signal */
34
+ reason: string;
35
+ }
36
+ /**
37
+ * Result of content analysis
38
+ */
39
+ export interface ContentAnalysisResult {
40
+ /** Phases suggested by content analysis */
41
+ phases: Phase[];
42
+ /** Whether quality loop should be enabled */
43
+ qualityLoop: boolean;
44
+ /** Individual signals detected */
45
+ signals: ContentSignal[];
46
+ /** Notes about the analysis */
47
+ notes: string[];
48
+ }
49
+ /**
50
+ * Analyze issue title for phase-relevant keywords
51
+ *
52
+ * @param title - The issue title
53
+ * @returns Array of detected signals
54
+ */
55
+ export declare function analyzeTitleForPhases(title: string): ContentSignal[];
56
+ /**
57
+ * Analyze issue body for phase-relevant patterns
58
+ *
59
+ * @param body - The issue body
60
+ * @returns Array of detected signals
61
+ */
62
+ export declare function analyzeBodyForPhases(body: string): ContentSignal[];
63
+ /**
64
+ * Check if content indicates trivial work
65
+ *
66
+ * @param title - The issue title
67
+ * @param body - The issue body
68
+ * @returns True if the work appears trivial
69
+ */
70
+ export declare function isTrivialWork(title: string, body: string): boolean;
71
+ /**
72
+ * Analyze issue content (title + body) for phase recommendations
73
+ *
74
+ * This is the main entry point for content analysis.
75
+ * It analyzes both title and body, deduplicates signals,
76
+ * and returns a consolidated result.
77
+ *
78
+ * @param title - The issue title
79
+ * @param body - The issue body
80
+ * @returns Consolidated analysis result with phases and signals
81
+ */
82
+ export declare function analyzeContentForPhases(title: string, body: string): ContentAnalysisResult;
83
+ /**
84
+ * Format content analysis result for display
85
+ *
86
+ * @param result - The analysis result
87
+ * @returns Formatted markdown string
88
+ */
89
+ export declare function formatContentAnalysis(result: ContentAnalysisResult): string;