Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,26 @@ sections:
- Program_Run_WithElaborateFlag_UnknownId_ReportsError
- ReviewMark_Elaborate

- id: ReviewMark-Cmd-Lint
title: The tool shall support --lint flag to validate the definition file and report issues.
justification: |
Users need a way to verify that the .reviewmark.yaml configuration file is valid
before running the main tool, providing clear error messages about the cause and
location of any issues.
tests:
- Context_Create_LintFlag_SetsLintTrue
- Context_Create_NoArguments_LintIsFalse
- Program_Run_WithHelpFlag_IncludesLintOption
- Program_Run_WithLintFlag_ValidConfig_ReportsSuccess
- Program_Run_WithLintFlag_MissingConfig_ReportsError
- Program_Run_WithLintFlag_DuplicateIds_ReportsError
- Program_Run_WithLintFlag_UnknownSourceType_ReportsError
- Program_Run_WithLintFlag_CorruptedYaml_ReportsError
- Program_Run_WithLintFlag_MissingEvidenceSource_ReportsError
- ReviewMarkConfiguration_Load_InvalidYaml_ErrorIncludesFilenameAndLine
- ReviewMarkConfiguration_Load_MissingEvidenceSource_ErrorIncludesFilename
- ReviewMark_Lint

- title: Configuration Reading
requirements:
- id: ReviewMark-Config-Reading
Expand Down Expand Up @@ -321,6 +341,7 @@ sections:
- "windows@ReviewMark_Enforce"
- "windows@ReviewMark_WorkingDirectoryOverride"
- "windows@ReviewMark_Elaborate"
- "windows@ReviewMark_Lint"

- id: ReviewMark-Platform-Linux
title: The tool shall build and run on Linux platforms.
Expand All @@ -336,6 +357,7 @@ sections:
- "ubuntu@ReviewMark_Enforce"
- "ubuntu@ReviewMark_WorkingDirectoryOverride"
- "ubuntu@ReviewMark_Elaborate"
- "ubuntu@ReviewMark_Lint"

- id: ReviewMark-Platform-MacOS
title: The tool shall build and run on macOS platforms.
Expand All @@ -351,6 +373,7 @@ sections:
- "macos@ReviewMark_Enforce"
- "macos@ReviewMark_WorkingDirectoryOverride"
- "macos@ReviewMark_Elaborate"
- "macos@ReviewMark_Lint"

- id: ReviewMark-Platform-Net8
title: The tool shall support .NET 8 runtime.
Expand All @@ -365,6 +388,7 @@ sections:
- "dotnet8.x@ReviewMark_Enforce"
- "dotnet8.x@ReviewMark_WorkingDirectoryOverride"
- "dotnet8.x@ReviewMark_Elaborate"
- "dotnet8.x@ReviewMark_Lint"

- id: ReviewMark-Platform-Net9
title: The tool shall support .NET 9 runtime.
Expand All @@ -379,6 +403,7 @@ sections:
- "dotnet9.x@ReviewMark_Enforce"
- "dotnet9.x@ReviewMark_WorkingDirectoryOverride"
- "dotnet9.x@ReviewMark_Elaborate"
- "dotnet9.x@ReviewMark_Lint"

- id: ReviewMark-Platform-Net10
title: The tool shall support .NET 10 runtime.
Expand All @@ -393,6 +418,7 @@ sections:
- "dotnet10.x@ReviewMark_Enforce"
- "dotnet10.x@ReviewMark_WorkingDirectoryOverride"
- "dotnet10.x@ReviewMark_Elaborate"
- "dotnet10.x@ReviewMark_Lint"

- title: OTS Software
requirements:
Expand Down
15 changes: 15 additions & 0 deletions src/DemaConsulting.ReviewMark/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ internal sealed class Context : IDisposable
/// </summary>
public bool Validate { get; private init; }

/// <summary>
/// Gets a value indicating whether the lint flag was specified.
/// </summary>
public bool Lint { get; private init; }

/// <summary>
/// Gets the validation results file path.
/// </summary>
Expand Down Expand Up @@ -159,6 +164,7 @@ public static Context Create(string[] args)
Help = parser.Help,
Silent = parser.Silent,
Validate = parser.Validate,
Lint = parser.Lint,
ResultsFile = parser.ResultsFile,
DefinitionFile = parser.DefinitionFile,
PlanFile = parser.PlanFile,
Expand Down Expand Up @@ -226,6 +232,11 @@ private sealed class ArgumentParser
/// </summary>
public bool Validate { get; private set; }

/// <summary>
/// Gets a value indicating whether the lint flag was specified.
/// </summary>
public bool Lint { get; private set; }

/// <summary>
/// Gets the log file path.
/// </summary>
Expand Down Expand Up @@ -328,6 +339,10 @@ private int ParseArgument(string arg, string[] args, int index)
Validate = true;
return index;

case "--lint":
Lint = true;
return index;

case "--log":
LogFile = GetRequiredStringArgument(arg, args, index, FilenameArgument);
return index + 1;
Expand Down
68 changes: 67 additions & 1 deletion src/DemaConsulting.ReviewMark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,14 @@ public static void Run(Context context)
return;
}

// Priority 4: Main tool functionality
// Priority 4: Lint
if (context.Lint)
{
RunLintLogic(context);
return;
}

// Priority 5: Main tool functionality
RunToolLogic(context);
}

Expand Down Expand Up @@ -140,6 +147,7 @@ private static void PrintHelp(Context context)
context.WriteLine(" -?, -h, --help Display this help message");
context.WriteLine(" --silent Suppress console output");
context.WriteLine(" --validate Run self-validation");
context.WriteLine(" --lint Lint the definition file and report issues");
context.WriteLine(" --results <file> Write validation results to file (.trx or .xml)");
context.WriteLine(" --log <file> Write output to log file");
context.WriteLine(" --definition <file> Specify the definition YAML file (default: .reviewmark.yaml)");
Expand All @@ -154,6 +162,64 @@ private static void PrintHelp(Context context)
context.WriteLine(" --elaborate <id> Print a Markdown elaboration of the specified review set");
}

/// <summary>
/// Runs the lint logic to validate the definition file.
/// </summary>
/// <param name="context">The context containing command line arguments and program state.</param>
private static void RunLintLogic(Context context)
{
// Determine the definition file path (explicit or default)
var directory = context.WorkingDirectory ?? Directory.GetCurrentDirectory();
var definitionFile = context.DefinitionFile ?? PathHelpers.SafePathCombine(directory, ".reviewmark.yaml");

context.WriteLine($"Linting '{definitionFile}'...");

// Try to load the configuration file
ReviewMarkConfiguration config;
try
{
config = ReviewMarkConfiguration.Load(definitionFile);
}
catch (InvalidOperationException ex)
{
context.WriteError($"Error: {ex.Message}");
return;
}
Comment on lines +177 to +187
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RunLintLogic calls ReviewMarkConfiguration.Load and returns immediately on the first semantic validation error (e.g., missing evidence-source), which prevents reporting additional issues that are still detectable in the same file (like duplicate reviews[*].id). If the intent is to report all issues in one pass, consider adding a dedicated lint/validation path that parses into a raw model and accumulates errors without throwing, then prints them all before exiting non-zero.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback


// Perform semantic validation: check for unknown evidence-source type
var hasErrors = false;
if (!string.Equals(config.EvidenceSource.Type, "url", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(config.EvidenceSource.Type, "fileshare", StringComparison.OrdinalIgnoreCase))
{
context.WriteError(
$"Error: evidence-source type '{config.EvidenceSource.Type}' is not supported (must be 'url' or 'fileshare').");
hasErrors = true;
}

// Perform semantic validation: check for duplicate review set IDs
var seenIds = new Dictionary<string, int>(StringComparer.Ordinal);
for (var i = 0; i < config.Reviews.Count; i++)
{
var review = config.Reviews[i];
if (seenIds.TryGetValue(review.Id, out var firstIndex))
{
context.WriteError(
$"Error: reviews[{i}] has duplicate ID '{review.Id}' (first defined at reviews[{firstIndex}]).");
hasErrors = true;
}
else
{
seenIds[review.Id] = i;
}
}

// Report overall result
if (!hasErrors)
{
context.WriteLine($"'{definitionFile}' is valid.");
}
}

/// <summary>
/// Runs the main tool logic.
/// </summary>
Expand Down
Loading
Loading