@krr2020/taskflow-core 0.1.0-beta.4 → 0.1.0-beta.5
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 +1 -1
- package/dist/cli/index.js +41 -3
- package/dist/commands/base.d.ts +41 -0
- package/dist/commands/base.js +141 -0
- package/dist/commands/configure.d.ts +29 -0
- package/dist/commands/configure.js +187 -0
- package/dist/commands/init.js +15 -1
- package/dist/commands/prd/create.d.ts +1 -1
- package/dist/commands/prd/create.js +26 -8
- package/dist/commands/tasks/generate.d.ts +1 -1
- package/dist/commands/tasks/generate.js +81 -55
- package/dist/commands/upgrade.js +35 -2
- package/dist/commands/workflow/check.d.ts +16 -0
- package/dist/commands/workflow/check.js +355 -32
- package/dist/commands/workflow/do.js +157 -62
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/lib/config-paths.js +6 -1
- package/dist/lib/file-validator.d.ts +119 -0
- package/dist/lib/file-validator.js +291 -0
- package/dist/lib/log-parser.d.ts +91 -0
- package/dist/lib/log-parser.js +178 -0
- package/dist/lib/retrospective.d.ts +27 -0
- package/dist/lib/retrospective.js +110 -0
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/types.js +12 -8
- package/dist/llm/base.d.ts +52 -0
- package/dist/llm/base.js +35 -0
- package/dist/llm/factory.d.ts +39 -0
- package/dist/llm/factory.js +102 -0
- package/dist/llm/index.d.ts +7 -0
- package/dist/llm/index.js +7 -0
- package/dist/llm/model-selector.d.ts +71 -0
- package/dist/llm/model-selector.js +139 -0
- package/dist/llm/providers/anthropic.d.ts +31 -0
- package/dist/llm/providers/anthropic.js +116 -0
- package/dist/llm/providers/index.d.ts +6 -0
- package/dist/llm/providers/index.js +6 -0
- package/dist/llm/providers/ollama.d.ts +28 -0
- package/dist/llm/providers/ollama.js +91 -0
- package/dist/llm/providers/openai-compatible.d.ts +30 -0
- package/dist/llm/providers/openai-compatible.js +93 -0
- package/dist/schemas/config.d.ts +82 -0
- package/dist/schemas/config.js +35 -0
- package/package.json +43 -43
- package/dist/lib/package-manager.d.ts +0 -17
- package/dist/lib/package-manager.js +0 -53
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Check command - Validate and advance task state
|
|
3
3
|
*/
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { readdir } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
4
7
|
import { ConfigLoader } from "../../lib/config-loader.js";
|
|
5
8
|
import { getRefFilePath, REF_FILES } from "../../lib/config-paths.js";
|
|
6
9
|
import { findActiveTask, loadTasksProgress, updateTaskStatus, } from "../../lib/data-access.js";
|
|
7
10
|
import { NoActiveSessionError } from "../../lib/errors.js";
|
|
8
|
-
import {
|
|
11
|
+
import { FileValidator, } from "../../lib/file-validator.js";
|
|
12
|
+
import { LogParser } from "../../lib/log-parser.js";
|
|
13
|
+
import { extractNewPatterns, formatNewPatternForDisplay, processValidationOutput, } from "../../lib/retrospective.js";
|
|
9
14
|
import { runValidations } from "../../lib/validation.js";
|
|
15
|
+
import { Phase } from "../../llm/base.js";
|
|
16
|
+
import { ProviderFactory } from "../../llm/factory.js";
|
|
10
17
|
import { BaseCommand } from "../base.js";
|
|
11
18
|
export class CheckCommand extends BaseCommand {
|
|
12
19
|
async execute() {
|
|
@@ -248,7 +255,7 @@ export class CheckCommand extends BaseCommand {
|
|
|
248
255
|
"",
|
|
249
256
|
"LEARNINGS TRACKING:",
|
|
250
257
|
"────────────────────",
|
|
251
|
-
"Capture only general, project-wide insights: not implementation details (
|
|
258
|
+
"Capture only general, project-wide insights: not implementation details ('I added a function'), not what you did but what you learned, prevent repeated mistakes in future tasks.",
|
|
252
259
|
"",
|
|
253
260
|
"TECH DEBT REPORTING:",
|
|
254
261
|
"────────────────────────",
|
|
@@ -347,51 +354,138 @@ export class CheckCommand extends BaseCommand {
|
|
|
347
354
|
});
|
|
348
355
|
}
|
|
349
356
|
async advanceToCommitting(tasksDir, logsDir, refDir, tasksProgress, taskId, content, validationCommands) {
|
|
350
|
-
//
|
|
357
|
+
// Check if AI validation is enabled
|
|
358
|
+
const configLoader = new ConfigLoader(this.context.projectRoot);
|
|
359
|
+
const config = configLoader.load();
|
|
360
|
+
// Run AI-powered validation if configured
|
|
361
|
+
if (config.ai?.enabled && config.ai.provider) {
|
|
362
|
+
const aiResult = await this.runAIValidation(logsDir, Phase.Analysis);
|
|
363
|
+
if (!aiResult.passed) {
|
|
364
|
+
return aiResult.result;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Run traditional validations
|
|
351
368
|
console.log("\n🧪 Running validations...\n");
|
|
352
369
|
const summary = runValidations(logsDir, taskId, validationCommands);
|
|
353
370
|
// Check for known errors in output
|
|
354
371
|
const retroCheck = processValidationOutput(refDir, summary.allOutput);
|
|
355
372
|
if (!summary.passed) {
|
|
373
|
+
// Get LLM error analysis if available
|
|
374
|
+
let llmGuidance = [
|
|
375
|
+
"Validation Failed - Fix Required",
|
|
376
|
+
"",
|
|
377
|
+
retroCheck.knownErrors.length > 0
|
|
378
|
+
? "KNOWN ERRORS DETECTED:"
|
|
379
|
+
: "Errors found in validation.",
|
|
380
|
+
retroCheck.knownErrors.length > 0
|
|
381
|
+
? "You have made mistakes that are already documented."
|
|
382
|
+
: "",
|
|
383
|
+
retroCheck.knownErrors.length > 0
|
|
384
|
+
? "Check the output above for solutions."
|
|
385
|
+
: "",
|
|
386
|
+
retroCheck.knownErrors.length > 0
|
|
387
|
+
? "STOP REPEATING THESE MISTAKES."
|
|
388
|
+
: "",
|
|
389
|
+
"",
|
|
390
|
+
"How to fix:",
|
|
391
|
+
"1. Read error messages carefully",
|
|
392
|
+
"2. Apply solutions from retrospective (if shown)",
|
|
393
|
+
"3. Fix the code",
|
|
394
|
+
"4. Re-run: taskflow check",
|
|
395
|
+
"",
|
|
396
|
+
"DO NOT bypass validations",
|
|
397
|
+
"DO NOT use 'any' types to suppress errors",
|
|
398
|
+
"DO fix the root cause",
|
|
399
|
+
].join("\n");
|
|
400
|
+
// Parse logs and detect new error patterns
|
|
401
|
+
const logParser = new LogParser();
|
|
402
|
+
const logFiles = await readdir(logsDir);
|
|
403
|
+
const parsedErrors = [];
|
|
404
|
+
for (const logFile of logFiles) {
|
|
405
|
+
if (logFile.endsWith(".log")) {
|
|
406
|
+
try {
|
|
407
|
+
const logPath = path.join(logsDir, logFile);
|
|
408
|
+
const parseResult = await logParser.parseFile(logPath);
|
|
409
|
+
parsedErrors.push(...parseResult.errors);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// Ignore log parsing errors
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Detect NEW error patterns for retrospective
|
|
417
|
+
let newPatternsDetected = [];
|
|
418
|
+
if (parsedErrors.length > 0) {
|
|
419
|
+
const newPatterns = extractNewPatterns(parsedErrors, refDir);
|
|
420
|
+
if (newPatterns.length > 0) {
|
|
421
|
+
newPatternsDetected = newPatterns.map((pattern) => formatNewPatternForDisplay(pattern));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Get AI error analysis if LLM available
|
|
425
|
+
if (this.isLLMAvailable() && parsedErrors.length > 0) {
|
|
426
|
+
try {
|
|
427
|
+
const errors = parsedErrors.map((error) => ({
|
|
428
|
+
file: error.file,
|
|
429
|
+
message: error.message,
|
|
430
|
+
line: error.line ?? undefined,
|
|
431
|
+
code: error.code ?? undefined,
|
|
432
|
+
}));
|
|
433
|
+
const errorAnalysis = await this.getErrorAnalysis(errors);
|
|
434
|
+
if (errorAnalysis) {
|
|
435
|
+
llmGuidance = [
|
|
436
|
+
"AI Error Analysis:",
|
|
437
|
+
"───────────────────",
|
|
438
|
+
errorAnalysis,
|
|
439
|
+
"",
|
|
440
|
+
"How to fix:",
|
|
441
|
+
"1. Review the AI analysis above",
|
|
442
|
+
"2. Apply suggested fixes file-by-file",
|
|
443
|
+
"3. Re-run: taskflow check",
|
|
444
|
+
"",
|
|
445
|
+
retroCheck.knownErrors.length > 0
|
|
446
|
+
? "⚠️ Known errors also detected - see solutions above"
|
|
447
|
+
: "",
|
|
448
|
+
"",
|
|
449
|
+
newPatternsDetected.length > 0
|
|
450
|
+
? "📝 NEW error patterns detected (see warnings below)"
|
|
451
|
+
: "",
|
|
452
|
+
].join("\n");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
// Fall back to standard guidance
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Build warnings with new patterns
|
|
460
|
+
const warnings = [];
|
|
461
|
+
if (newPatternsDetected.length > 0) {
|
|
462
|
+
warnings.push("📝 NEW Error Patterns Detected:");
|
|
463
|
+
warnings.push("");
|
|
464
|
+
for (const patternDisplay of newPatternsDetected) {
|
|
465
|
+
warnings.push(patternDisplay);
|
|
466
|
+
warnings.push("");
|
|
467
|
+
}
|
|
468
|
+
warnings.push("💡 Consider adding these to retrospective: taskflow retro add");
|
|
469
|
+
warnings.push("");
|
|
470
|
+
}
|
|
471
|
+
const failureOptions = {
|
|
472
|
+
aiGuidance: llmGuidance,
|
|
473
|
+
};
|
|
474
|
+
if (warnings.length > 0) {
|
|
475
|
+
failureOptions.warnings = warnings;
|
|
476
|
+
}
|
|
356
477
|
return this.failure("Validation failed", summary.failedChecks.map((check) => `${check} failed`), [
|
|
357
478
|
"Fix the errors shown above and re-run: taskflow check",
|
|
358
479
|
"",
|
|
359
480
|
retroCheck.knownErrors.length > 0
|
|
360
481
|
? "⚠️ Known errors detected - see solutions above"
|
|
361
482
|
: "",
|
|
362
|
-
retroCheck.hasNewErrors
|
|
483
|
+
retroCheck.hasNewErrors && newPatternsDetected.length === 0
|
|
363
484
|
? "⚠️ New error detected - consider adding to retrospective: taskflow retro add"
|
|
364
485
|
: "",
|
|
365
486
|
"",
|
|
366
487
|
`Full logs: ${logsDir}`,
|
|
367
|
-
].join("\n"),
|
|
368
|
-
aiGuidance: [
|
|
369
|
-
"Validation Failed - Fix Required",
|
|
370
|
-
"",
|
|
371
|
-
retroCheck.knownErrors.length > 0
|
|
372
|
-
? "KNOWN ERRORS DETECTED:"
|
|
373
|
-
: "Errors found in validation.",
|
|
374
|
-
retroCheck.knownErrors.length > 0
|
|
375
|
-
? "You have made mistakes that are already documented."
|
|
376
|
-
: "",
|
|
377
|
-
retroCheck.knownErrors.length > 0
|
|
378
|
-
? "Check the output above for solutions."
|
|
379
|
-
: "",
|
|
380
|
-
retroCheck.knownErrors.length > 0
|
|
381
|
-
? "STOP REPEATING THESE MISTAKES."
|
|
382
|
-
: "",
|
|
383
|
-
"",
|
|
384
|
-
"How to fix:",
|
|
385
|
-
"1. Read error messages carefully",
|
|
386
|
-
"2. Apply solutions from retrospective (if shown)",
|
|
387
|
-
"3. Fix the code",
|
|
388
|
-
"4. Re-run: taskflow check",
|
|
389
|
-
"",
|
|
390
|
-
"DO NOT bypass validations",
|
|
391
|
-
"DO NOT use 'any' types to suppress errors",
|
|
392
|
-
"DO fix the root cause",
|
|
393
|
-
].join("\n"),
|
|
394
|
-
});
|
|
488
|
+
].join("\n"), failureOptions);
|
|
395
489
|
}
|
|
396
490
|
// All validations passed - advance to committing
|
|
397
491
|
updateTaskStatus(tasksDir, tasksProgress, taskId, "committing");
|
|
@@ -460,4 +554,233 @@ export class CheckCommand extends BaseCommand {
|
|
|
460
554
|
],
|
|
461
555
|
});
|
|
462
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Run AI-powered validation on changed files
|
|
559
|
+
*/
|
|
560
|
+
async runAIValidation(logsDir, phase) {
|
|
561
|
+
console.log("\n🤖 Running AI-powered file validation...\n");
|
|
562
|
+
try {
|
|
563
|
+
// Get AI config to create provider with correct settings
|
|
564
|
+
const config = this.configLoader.load();
|
|
565
|
+
if (!config?.ai || !config.ai.enabled) {
|
|
566
|
+
throw new Error("AI configuration not enabled");
|
|
567
|
+
}
|
|
568
|
+
if (!config.ai.provider) {
|
|
569
|
+
throw new Error("AI provider not configured");
|
|
570
|
+
}
|
|
571
|
+
// Create provider using actual config (cast to AIConfig)
|
|
572
|
+
const provider = ProviderFactory.createSelector(config.ai);
|
|
573
|
+
const analysisProvider = provider.getProvider(phase);
|
|
574
|
+
// Find modified files (git status, recent changes, etc.)
|
|
575
|
+
const modifiedFiles = await this.findModifiedFiles();
|
|
576
|
+
if (modifiedFiles.length === 0) {
|
|
577
|
+
console.log("No modified files found for validation");
|
|
578
|
+
return {
|
|
579
|
+
passed: true,
|
|
580
|
+
result: this.success("No files to validate", ""),
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
console.log(`Validating ${modifiedFiles.length} file${modifiedFiles.length > 1 ? "s" : ""}...\n`);
|
|
584
|
+
// Create file validator
|
|
585
|
+
const validator = new FileValidator({
|
|
586
|
+
provider: analysisProvider,
|
|
587
|
+
includeContext: true,
|
|
588
|
+
provideFixes: true,
|
|
589
|
+
maxIssues: 10,
|
|
590
|
+
});
|
|
591
|
+
// Validate each file
|
|
592
|
+
const results = [];
|
|
593
|
+
for (const filePath of modifiedFiles) {
|
|
594
|
+
try {
|
|
595
|
+
const result = await validator.validate(filePath);
|
|
596
|
+
results.push(result);
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
console.warn(`Failed to validate ${filePath}: ${error.message}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Check if all files passed
|
|
603
|
+
const allPassed = results.every((r) => r.passed);
|
|
604
|
+
// Format and display results
|
|
605
|
+
const output = this.formatValidationResults(results);
|
|
606
|
+
console.log(output);
|
|
607
|
+
if (allPassed) {
|
|
608
|
+
return {
|
|
609
|
+
passed: true,
|
|
610
|
+
result: this.success("All files passed AI validation", "Ready to proceed with standard validation"),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
// Parse logs for errors to provide context
|
|
614
|
+
const logParser = new LogParser();
|
|
615
|
+
const logFiles = await readdir(logsDir);
|
|
616
|
+
const errors = [];
|
|
617
|
+
for (const logFile of logFiles) {
|
|
618
|
+
if (logFile.endsWith(".log")) {
|
|
619
|
+
try {
|
|
620
|
+
const logPath = path.join(logsDir, logFile);
|
|
621
|
+
const parseResult = await logParser.parseFile(logPath);
|
|
622
|
+
errors.push(...parseResult.errors);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
// Ignore log parsing errors
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Create AI guidance with error context
|
|
630
|
+
const errorSummary = this.createErrorSummary(errors);
|
|
631
|
+
const messages = [
|
|
632
|
+
"Fix issues shown above and re-run: taskflow check",
|
|
633
|
+
"",
|
|
634
|
+
errorSummary,
|
|
635
|
+
"",
|
|
636
|
+
"AI-Powered Validation Benefits:",
|
|
637
|
+
"- Early error detection before compilation",
|
|
638
|
+
"- Context-aware fix suggestions",
|
|
639
|
+
"- Pattern-based issue identification",
|
|
640
|
+
"- Code quality analysis",
|
|
641
|
+
];
|
|
642
|
+
const guidance = [
|
|
643
|
+
"AI Validation Results",
|
|
644
|
+
"────────────────────",
|
|
645
|
+
`Files validated: ${results.length}`,
|
|
646
|
+
`Files passed: ${results.filter((r) => r.passed).length}`,
|
|
647
|
+
`Files failed: ${results.filter((r) => !r.passed).length}`,
|
|
648
|
+
"",
|
|
649
|
+
"Fix Instructions:",
|
|
650
|
+
"1. Review issues shown above for each file",
|
|
651
|
+
"2. Apply suggested fixes",
|
|
652
|
+
"3. Re-run: taskflow check",
|
|
653
|
+
"",
|
|
654
|
+
"If errors persist, check for:",
|
|
655
|
+
"- Incorrect imports or types",
|
|
656
|
+
"- Missing dependencies",
|
|
657
|
+
"- Logic errors or bugs",
|
|
658
|
+
"- Known patterns in retrospective",
|
|
659
|
+
];
|
|
660
|
+
return {
|
|
661
|
+
passed: false,
|
|
662
|
+
result: this.failure("AI validation failed", results.flatMap((r) => r.issues.map((i) => i.message)), messages.join("\n"), {
|
|
663
|
+
aiGuidance: guidance.join("\n"),
|
|
664
|
+
}),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
console.warn(`AI validation failed: ${error.message}`);
|
|
669
|
+
console.log("Falling back to standard validation\n");
|
|
670
|
+
// Return passed to fall back to standard validation
|
|
671
|
+
return {
|
|
672
|
+
passed: true,
|
|
673
|
+
result: this.success("AI validation skipped", "Proceeding with standard validation"),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Find modified files for validation
|
|
679
|
+
*/
|
|
680
|
+
async findModifiedFiles() {
|
|
681
|
+
try {
|
|
682
|
+
// Get modified, added, and untracked files from git
|
|
683
|
+
const output = execSync("git status --porcelain --untracked-files=all", {
|
|
684
|
+
cwd: this.context.projectRoot,
|
|
685
|
+
encoding: "utf-8",
|
|
686
|
+
}).trim();
|
|
687
|
+
if (!output) {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
const files = [];
|
|
691
|
+
const lines = output.split("\n");
|
|
692
|
+
for (const line of lines) {
|
|
693
|
+
// Parse git status output format:
|
|
694
|
+
// XY FILENAME
|
|
695
|
+
// X = status in index, Y = status in working tree
|
|
696
|
+
// Status codes: M (modified), A (added), D (deleted), ?? (untracked), etc.
|
|
697
|
+
const match = line.match(/^(.{2})\s+(.+)$/);
|
|
698
|
+
if (!match)
|
|
699
|
+
continue;
|
|
700
|
+
const [, status, filePath] = match;
|
|
701
|
+
if (!status || !filePath)
|
|
702
|
+
continue;
|
|
703
|
+
// Skip deleted files
|
|
704
|
+
if (status.includes("D"))
|
|
705
|
+
continue;
|
|
706
|
+
// Only validate source files (ts, tsx, js, jsx) but not test files
|
|
707
|
+
const isSourceFile = /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath);
|
|
708
|
+
const isTestFile = /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filePath);
|
|
709
|
+
if (isSourceFile && !isTestFile) {
|
|
710
|
+
// Convert to absolute path
|
|
711
|
+
const absolutePath = path.join(this.context.projectRoot, filePath);
|
|
712
|
+
files.push(absolutePath);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return files;
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
console.warn(`Failed to get modified files: ${error.message}`);
|
|
719
|
+
return [];
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Create error summary from logs and validation results
|
|
724
|
+
*/
|
|
725
|
+
createErrorSummary(errors) {
|
|
726
|
+
if (errors.length === 0) {
|
|
727
|
+
return "No errors found in logs.";
|
|
728
|
+
}
|
|
729
|
+
const grouped = new Map();
|
|
730
|
+
for (const error of errors) {
|
|
731
|
+
if (error.file) {
|
|
732
|
+
const existing = grouped.get(error.file) || [];
|
|
733
|
+
existing.push(error);
|
|
734
|
+
grouped.set(error.file, existing);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const lines = ["Error Summary:", ""];
|
|
738
|
+
for (const [file, fileErrors] of grouped) {
|
|
739
|
+
lines.push(`${file}: ${fileErrors.length} error${fileErrors.length > 1 ? "s" : ""}`);
|
|
740
|
+
}
|
|
741
|
+
return lines.join("\n");
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Format validation results
|
|
745
|
+
*/
|
|
746
|
+
formatValidationResults(results) {
|
|
747
|
+
const lines = [];
|
|
748
|
+
lines.push("=".repeat(80));
|
|
749
|
+
lines.push("AI-Powered Validation Results");
|
|
750
|
+
lines.push("=".repeat(80));
|
|
751
|
+
for (const result of results) {
|
|
752
|
+
lines.push("");
|
|
753
|
+
lines.push(`File: ${result.file}`);
|
|
754
|
+
lines.push(`Status: ${result.passed ? "✓ PASSED" : "✗ FAILED"}`);
|
|
755
|
+
lines.push(`Summary: ${result.summary}`);
|
|
756
|
+
if (result.issues.length > 0) {
|
|
757
|
+
lines.push("");
|
|
758
|
+
lines.push("Issues:");
|
|
759
|
+
for (const issue of result.issues) {
|
|
760
|
+
const icon = issue.severity === "error"
|
|
761
|
+
? "✗"
|
|
762
|
+
: issue.severity === "warning"
|
|
763
|
+
? "⚠"
|
|
764
|
+
: "ℹ";
|
|
765
|
+
lines.push(` ${icon} ${issue.message}`);
|
|
766
|
+
if (issue.line) {
|
|
767
|
+
lines.push(` Line: ${issue.line}`);
|
|
768
|
+
}
|
|
769
|
+
if (issue.suggestedFix) {
|
|
770
|
+
lines.push(` Suggested: ${issue.suggestedFix}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (result.suggestions.length > 0) {
|
|
775
|
+
lines.push("");
|
|
776
|
+
lines.push("Suggestions:");
|
|
777
|
+
for (const suggestion of result.suggestions) {
|
|
778
|
+
lines.push(` • ${suggestion}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
lines.push("");
|
|
783
|
+
lines.push("=".repeat(80));
|
|
784
|
+
return lines.join("\n");
|
|
785
|
+
}
|
|
463
786
|
}
|