Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ reviewmark --silent --log output.log
| `--results <file>` | Write validation results to file (TRX or JUnit format) |
| `--log <file>` | Write output to log file |
| `--definition <file>` | Specify the definition YAML file (default: .reviewmark.yaml) |
| `--depth <#>` | Set the default heading depth for all generated documents |
| `--plan <file>` | Write review plan to the specified Markdown file |
| `--plan-depth <#>` | Set the heading depth for the review plan (default: 1) |
| `--plan-depth <#>` | Heading depth for the review plan (overrides --depth) |
| `--report <file>` | Write review report to the specified Markdown file |
| `--report-depth <#>` | Set the heading depth for the review report (default: 1) |
| `--report-depth <#>` | Heading depth for the review report (overrides --depth) |
Comment thread
Malcolmnixon marked this conversation as resolved.
Outdated
| `--index <glob-path>` | Index PDF evidence files matching the glob path |
| `--dir <directory>` | Set the working directory for file operations |
| `--enforce` | Exit with non-zero code if there are review issues |
Expand Down
5 changes: 3 additions & 2 deletions docs/design/review-mark/cli/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ arguments:
| `ResultsFile` | string? | Path for TRX/JUnit test results output |
| `DefinitionFile` | string? | Path to the `.reviewmark.yaml` configuration |
| `PlanFile` | string? | Output path for the Review Plan document |
| `PlanDepth` | int | Heading depth for the Review Plan |
| `Depth` | int | Default heading depth for all generated documents |
| `PlanDepth` | int | Heading depth for the Review Plan (defaults to `Depth`) |
| `ReportFile` | string? | Output path for the Review Report document |
| `ReportDepth` | int | Heading depth for the Review Report |
| `ReportDepth` | int | Heading depth for the Review Report (defaults to `Depth`) |
| `IndexPaths` | string[]? | Paths to scan when building an evidence index |
| `WorkingDirectory` | string? | Base directory for resolving relative paths |
| `Enforce` | bool | Fail if any review-set is not Current |
Expand Down
17 changes: 15 additions & 2 deletions docs/reqstream/review-mark/cli/cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ sections:
- Cli_LogFlag_WritesOutputToFile
children: [ReviewMark-Context-Output]

- id: ReviewMark-Cmd-Depth
title: The tool shall support --depth flag to set the default Markdown heading depth for all
generated documents.
justification: |
Allows users to set the heading depth once and have it apply to the self-validation
report, review plan, and review report, unless overridden by --plan-depth or --report-depth.
Default depth is 1 when not specified.
tests:
- Cli_DepthFlag_SetsDefaultHeadingDepth
children: [ReviewMark-Context-Parsing]
Comment thread
Malcolmnixon marked this conversation as resolved.

- id: ReviewMark-Cmd-ErrorOutput
title: The tool shall write error messages to stderr.
justification: |
Expand Down Expand Up @@ -119,7 +130,8 @@ sections:
title: The tool shall support --plan-depth flag to set the Markdown heading depth for the review plan.
justification: |
Allows the review plan to be embedded at any heading level within a larger
Markdown document, with a default depth of 1 when not specified.
Markdown document, overriding --depth when specified. Default depth is 1 when
neither --plan-depth nor --depth is specified.
tests:
- Cli_PlanDepthFlag_SetsHeadingDepth
children: [ReviewMark-Context-Parsing]
Expand All @@ -137,7 +149,8 @@ sections:
title: The tool shall support --report-depth flag to set the Markdown heading depth for the review report.
justification: |
Allows the review report to be embedded at any heading level within a larger
Markdown document, with a default depth of 1 when not specified.
Markdown document, overriding --depth when specified. Default depth is 1 when
neither --report-depth nor --depth is specified.
tests:
- Cli_ReportDepthFlag_SetsHeadingDepth
children: [ReviewMark-Context-Parsing]
Expand Down
5 changes: 3 additions & 2 deletions docs/user_guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,11 @@ The following command-line options are supported:
| `--results <file>` | Write validation results to file (TRX or JUnit format) |
| `--log <file>` | Write output to log file |
| `--definition <file>` | Specify the definition YAML file (default: .reviewmark.yaml) |
| `--depth <#>` | Default heading depth for generated documents (default: 1) |
| `--plan <file>` | Write review plan to the specified Markdown file |
| `--plan-depth <#>` | Set the heading depth for the review plan (default: 1) |
| `--plan-depth <#>` | Heading depth for the review plan (overrides --depth) |
| `--report <file>` | Write review report to the specified Markdown file |
| `--report-depth <#>` | Set the heading depth for the review report (default: 1) |
| `--report-depth <#>` | Heading depth for the review report (overrides --depth) |
Comment thread
Malcolmnixon marked this conversation as resolved.
Outdated
| `--index <glob-path>` | Index PDF evidence files matching the glob path |
| `--dir <directory>` | Set the working directory for default paths and glob paths |
| `--enforce` | Exit with non-zero code if there are review issues |
Expand Down
28 changes: 24 additions & 4 deletions src/DemaConsulting.ReviewMark/Cli/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ internal sealed class Context : IDisposable
/// </summary>
public string? PlanFile { get; private init; }

/// <summary>
/// Gets the default heading depth for all generated documents.
/// </summary>
public int Depth { get; private init; } = 1;

/// <summary>
/// Gets the heading depth for the review plan.
/// </summary>
Expand Down Expand Up @@ -168,9 +173,10 @@ public static Context Create(string[] args)
ResultsFile = parser.ResultsFile,
DefinitionFile = parser.DefinitionFile,
PlanFile = parser.PlanFile,
PlanDepth = parser.PlanDepth,
Depth = parser.Depth,
PlanDepth = parser.PlanDepth ?? parser.Depth,
ReportFile = parser.ReportFile,
ReportDepth = parser.ReportDepth,
ReportDepth = parser.ReportDepth ?? parser.Depth,
IndexPaths = parser.IndexPaths.AsReadOnly(),
WorkingDirectory = parser.WorkingDirectory,
Enforce = parser.Enforce,
Expand Down Expand Up @@ -257,10 +263,15 @@ private sealed class ArgumentParser
/// </summary>
public string? PlanFile { get; private set; }

/// <summary>
/// Gets the default heading depth for all generated documents.
/// </summary>
public int Depth { get; private set; } = 1;

/// <summary>
/// Gets the heading depth for the review plan.
/// </summary>
public int PlanDepth { get; private set; } = 1;
public int? PlanDepth { get; private set; }

/// <summary>
/// Gets the report file path.
Expand All @@ -270,7 +281,7 @@ private sealed class ArgumentParser
/// <summary>
/// Gets the heading depth for the review report.
/// </summary>
public int ReportDepth { get; private set; } = 1;
public int? ReportDepth { get; private set; }

/// <summary>
/// Gets the glob paths for PDF evidence files to index.
Expand Down Expand Up @@ -347,6 +358,15 @@ private int ParseArgument(string arg, string[] args, int index)
LogFile = GetRequiredStringArgument(arg, args, index, FilenameArgument);
return index + 1;

case "--depth":
Depth = GetRequiredIntArgument(arg, args, index);
if (Depth > 5)
Comment thread
Malcolmnixon marked this conversation as resolved.
{
throw new ArgumentException($"{arg} cannot be greater than 5", nameof(args));
}
Comment thread
Malcolmnixon marked this conversation as resolved.

return index + 1;

case "--result":
case "--results":
ResultsFile = GetRequiredStringArgument(arg, args, index, "a results filename argument");
Expand Down
5 changes: 3 additions & 2 deletions src/DemaConsulting.ReviewMark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,12 @@ private static void PrintHelp(Context context)
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(" --depth <#> Set the default heading depth for all generated documents");
context.WriteLine(" --definition <file> Specify the definition YAML file (default: .reviewmark.yaml)");
context.WriteLine(" --plan <file> Write review plan to the specified Markdown file");
context.WriteLine(" --plan-depth <#> Set the heading depth for the review plan (default: 1)");
context.WriteLine(" --plan-depth <#> Set the heading depth for the review plan (overrides --depth)");
context.WriteLine(" --report <file> Write review report to the specified Markdown file");
context.WriteLine(" --report-depth <#> Set the heading depth for the review report (default: 1)");
context.WriteLine(" --report-depth <#> Set the heading depth for the review report (overrides --depth)");
Comment thread
Malcolmnixon marked this conversation as resolved.
Outdated
context.WriteLine(" --index <glob-path> Index PDF evidence files matching the glob path");
context.WriteLine(" --dir <directory> Set the working directory (used for default paths and glob scanning)");
context.WriteLine(" Note: explicit paths given to --definition/--plan/--report are used as-is");
Expand Down
40 changes: 39 additions & 1 deletion src/DemaConsulting.ReviewMark/SelfTest/Validation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
RunEnforceTest(context, testResults);
RunElaborateTest(context, testResults);
RunLintTest(context, testResults);
RunDepthTest(context, testResults);

// Calculate totals
var totalTests = testResults.Results.Count;
Expand Down Expand Up @@ -91,7 +92,7 @@
/// <param name="context">The context for output.</param>
private static void PrintValidationHeader(Context context)
{
context.WriteLine("# DEMA Consulting ReviewMark");
context.WriteLine($"{new string('#', context.Depth)} DEMA Consulting ReviewMark");
context.WriteLine("");
Comment thread
Malcolmnixon marked this conversation as resolved.
context.WriteLine("| Information | Value |");
context.WriteLine("| :------------------ | :------------------------------------------------- |");
Expand All @@ -117,7 +118,7 @@

// Run the program capturing output to a log file
int exitCode;
using (var testContext = Context.Create(["--silent", "--log", logFile, "--version"]))

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--silent' 10 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--log' 4 times.

Check warning on line 121 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--silent' 10 times.
{
Program.Run(testContext);
exitCode = testContext.ExitCode;
Expand Down Expand Up @@ -182,7 +183,7 @@

// Run the program to generate the plan file
int exitCode;
using (var testContext = Context.Create(["--silent", "--definition", definitionFile, "--plan", planFile]))

Check warning on line 186 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--definition' 7 times.

Check warning on line 186 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--definition' 7 times.

Check warning on line 186 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--definition' 7 times.

Check warning on line 186 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--definition' 7 times.

Check warning on line 186 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--definition' 7 times.

Check warning on line 186 in src/DemaConsulting.ReviewMark/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--definition' 7 times.
{
Program.Run(testContext);
exitCode = testContext.ExitCode;
Expand Down Expand Up @@ -413,6 +414,43 @@
});
}

/// <summary>
/// Runs a test for the --depth flag setting the default heading depth.
/// </summary>
/// <param name="context">The context for output.</param>
/// <param name="testResults">The test results collection.</param>
private static void RunDepthTest(Context context, DemaConsulting.TestResults.TestResults testResults)
{
RunValidationTest(context, testResults, "ReviewMark_DepthFlag", () =>
{
using var tempDir = new TemporaryDirectory();
var (definitionFile, _) = CreateTestDefinitionFixtures(tempDir.DirectoryPath);
var planFile = PathHelpers.SafePathCombine(tempDir.DirectoryPath, "plan.md");

// Run with --depth 2 and no --plan-depth; plan headings should use ##
int exitCode;
using (var testContext = Context.Create(["--silent", "--definition", definitionFile, "--plan", planFile, "--depth", "2"]))
{
Program.Run(testContext);
exitCode = testContext.ExitCode;
}

if (exitCode != 0)
{
return $"Program exited with code {exitCode}";
}

if (!File.Exists(planFile))
{
return "Plan file was not created";
}

// Verify the plan file uses ## headings (depth 2)
var planContent = File.ReadAllText(planFile);
return planContent.Contains("## Review Coverage") ? null : "Plan file does not contain '## Review Coverage'";
});
}

/// <summary>
/// Runs a single validation test, recording the outcome in the test results collection.
/// </summary>
Expand Down
58 changes: 58 additions & 0 deletions test/DemaConsulting.ReviewMark.Tests/Cli/CliTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,4 +852,62 @@ public void Cli_ReportDepthFlag_SetsHeadingDepth()
}
}
}

/// <summary>
/// Test that --depth flag sets the default heading depth for the generated review plan.
/// </summary>
[TestMethod]
public void Cli_DepthFlag_SetsDefaultHeadingDepth()
{
// Arrange
var defFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".yaml"));
var planFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".md"));

try
{
File.WriteAllText(defFile, """
needs-review:
- "src/**/*.cs"
evidence-source:
type: none
reviews:
- id: Test-Review
title: Test review
paths:
- "src/**/*.cs"
""");

var originalOut = Console.Out;
try
{
using var outWriter = new StringWriter();
Console.SetOut(outWriter);
using var context = Context.Create(["--definition", defFile, "--plan", planFile, "--depth", "2"]);

// Act
Program.Run(context);

// Assert — plan file uses ## (depth 2) headings because --depth 2 sets the default
Assert.AreEqual(0, context.ExitCode);
Assert.IsTrue(File.Exists(planFile), "Plan file was not created");
var planContent = File.ReadAllText(planFile);
StringAssert.Contains(planContent, "## Review Coverage");
}
Comment thread
Malcolmnixon marked this conversation as resolved.
finally
{
Console.SetOut(originalOut);
}
}
finally
{
if (File.Exists(defFile))
{
File.Delete(defFile);
}
if (File.Exists(planFile))
{
File.Delete(planFile);
}
}
}
}
92 changes: 92 additions & 0 deletions test/DemaConsulting.ReviewMark.Tests/Cli/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -768,5 +768,97 @@ public void Context_Create_NoArguments_LintIsFalse()
// Assert — Lint is false when --lint is not specified
Assert.IsFalse(context.Lint);
}

/// <summary>
/// Test that --depth sets Depth, PlanDepth, and ReportDepth to the provided value.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_SetsDepth()
{
// Act - create context specifying a default heading depth of 3
using var context = Context.Create(["--depth", "3"]);

// Assert — Depth, PlanDepth, and ReportDepth are all set to 3 and exit code is 0
Assert.AreEqual(3, context.Depth);
Assert.AreEqual(3, context.PlanDepth);
Assert.AreEqual(3, context.ReportDepth);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test that --depth sets the default but --plan-depth overrides only PlanDepth.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_PlanDepthOverride()
{
// Act - create context with --depth 2 and --plan-depth 4
using var context = Context.Create(["--depth", "2", "--plan-depth", "4"]);

// Assert — Depth is 2, PlanDepth is 4 (overridden), ReportDepth is 2 (from --depth)
Assert.AreEqual(2, context.Depth);
Assert.AreEqual(4, context.PlanDepth);
Assert.AreEqual(2, context.ReportDepth);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test that --depth with a non-numeric value throws ArgumentException.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_WithInvalidValue_ThrowsArgumentException()
{
// Act & Assert - --depth with a non-numeric value should throw with a message referencing --depth
var exception = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth", "not-a-number"]));
Assert.Contains("--depth", exception.Message);
}

/// <summary>
/// Test that --depth with a value of 0 throws ArgumentException.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_WithZeroValue_ThrowsArgumentException()
{
// Act & Assert - --depth requires a positive integer; zero is not valid
var exception = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth", "0"]));
Assert.Contains("--depth", exception.Message);
}

/// <summary>
/// Test that --depth with a value greater than 5 throws ArgumentException.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_WithValueGreaterThanFive_ThrowsArgumentException()
{
// Act & Assert - --depth cannot exceed 5
var exception = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth", "6"]));
Assert.Contains("--depth", exception.Message);
}

/// <summary>
/// Test that --depth without a value throws ArgumentException.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_MissingValue_ThrowsArgumentException()
{
// Act & Assert - --depth with no following value should throw and include the flag name in the message
var exception = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth"]));
Assert.Contains("--depth", exception.Message);
}

/// <summary>
/// Test that --depth sets the default but --report-depth overrides only ReportDepth.
/// </summary>
[TestMethod]
public void Context_Create_DepthFlag_ReportDepthOverride()
{
// Act - create context with --depth 2 and --report-depth 4
using var context = Context.Create(["--depth", "2", "--report-depth", "4"]);

// Assert — Depth is 2, PlanDepth is 2 (from --depth), ReportDepth is 4 (overridden)
Assert.AreEqual(2, context.Depth);
Assert.AreEqual(2, context.PlanDepth);
Assert.AreEqual(4, context.ReportDepth);
Assert.AreEqual(0, context.ExitCode);
}
}

Loading