Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Building an app that drives a VISA instrument and want to test it without the ha
| `mock scenario` | `list` `create` `remove` `show` `activate` `deactivate` `record` `import` + `scene add` / `scene remove` | Author and capture mock-device scenarios |
| `server` | `add` `remove` `list` `route add` / `route remove` / `route list` `start` `stop` `status` `log` | Gateway-server lifecycle |
| `api` | `start` `stop` `token create` `token list` `token revoke` | Management HTTP JSON API (ADR 0034) + WebSocket subprotocol (ADR 0035) + PAT auth (ADR 0036) |
| top-level | `diagnose` `completion <shell>` | Environment health + shell autocomplete |
| top-level | `doctor` `completion <shell>` | Environment health + shell autocomplete |

## Verbosity & format flags

Expand Down
12 changes: 7 additions & 5 deletions docs/PRD.jp.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ ivicli
├─ server # gateway / remote instrument publishing
├─ logical # logical name management
├─ config # configuration management
├─ diagnose # environment diagnostics
├─ doctor # environment diagnostics
└─ driver # IVI driver management
```

Expand Down Expand Up @@ -360,12 +360,14 @@ route を削除する。

## 6.4 Diagnostics

### diagnose
### doctor

```bash
ivicli diagnose
ivicli doctor
```

後方互換のため `diagnose` を非推奨エイリアスとして残します(0.3.0 で削除予定)。

以下を診断:

* IVI Shared Components
Expand Down Expand Up @@ -648,7 +650,7 @@ CI / AI Agent 向け。
```text
Control Plane
┌─────────────────────────────────────────────────┐
│ config / logical / diagnose / server route │
│ config / logical / doctor / server route │
└─────────────────────────────────────────────────┘
Management API
Expand Down Expand Up @@ -823,7 +825,7 @@ Phase 1:
* visa use/current
* visa query/write/read
* visa status
* diagnose
* doctor
* driver list — ローカルの IVI Configuration Store からインストール
済み IVI ドライバを列挙 (ADR 0045)。機器は通信できているのにドライ
バ DLL が無い / バージョン不一致、といったデバッグで必須。
Expand Down
12 changes: 7 additions & 5 deletions docs/PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ ivicli
├─ server # gateway / remote instrument publishing
├─ logical # logical name management
├─ config # configuration management
├─ diagnose # environment diagnostics
├─ doctor # environment diagnostics
└─ driver # IVI driver management
```

Expand Down Expand Up @@ -356,12 +356,14 @@ Removes a route.

## 6.4 Diagnostics

### diagnose
### doctor

```bash
ivicli diagnose
ivicli doctor
```

`diagnose` is kept as a deprecated alias for backward compatibility; it will be removed at 0.3.0.

Diagnoses the following:

* IVI Shared Components
Expand Down Expand Up @@ -643,7 +645,7 @@ Intended for CI / AI Agents.
```text
Control Plane
┌─────────────────────────────────────────────────┐
│ config / logical / diagnose / server route │
│ config / logical / doctor / server route │
└─────────────────────────────────────────────────┘
Management API
Expand Down Expand Up @@ -818,7 +820,7 @@ Phase 1:
* visa use/current
* visa query/write/read
* visa status
* diagnose
* doctor
* driver list — enumerate installed IVI drivers from the local IVI
Configuration Store (ADR 0045). Essential for debugging when an
instrument is online but the driver assembly is missing or mis-
Expand Down
2 changes: 1 addition & 1 deletion docs/README.jp.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ VISA 計測器を操作するアプリを開発していて、実機を用意せ
| `mock scenario` | `list` `create` `remove` `show` `activate` `deactivate` `record` `import` + `scene add` / `scene remove` | モックデバイス用シナリオの編集と記録 |
| `server` | `add` `remove` `list` `route add` / `route remove` / `route list` `start` `stop` `status` `log` | ゲートウェイサーバのライフサイクル |
| `api` | `start` `stop` `token create` `token list` `token revoke` | Management HTTP JSON API (ADR 0034) + WebSocket サブプロトコル (ADR 0035) + PAT 認証 (ADR 0036) |
| top-level | `diagnose` `completion <shell>` | 環境ヘルスチェック + シェル補完 |
| top-level | `doctor` `completion <shell>` | 環境ヘルスチェック + シェル補完 |

## 詳細度 / フォーマットのフラグ

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0003-architecture-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Assembly layout and dependency direction are established in 0021. This ADR build
Constraints derived from the PRD and 0021:

- Polymorphism over `IIviBackend` as a transport abstraction (HiSLIP / VXI-11 / Socket / Fake / Replay)
- Separation of the Data Plane (visa query/write/read) and the Control Plane (config / server route / diagnose)
- Separation of the Data Plane (visa query/write/read) and the Control Plane (config / server route / doctor)
- Separation of static config (`config.toml`) and dynamic session state (`session.json`)
- SCPI itself distinguishes write (`OUTP ON`) from query (`*IDN?`) by syntax

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static IServiceCollection AddIviCliApplication(this IServiceCollection se
services.AddSingleton<IDeviceStatusProbe, DefaultDeviceStatusProbe>();
services.AddSingleton<StatusDeviceCommandHandler>();
services.AddSingleton<ScanDevicesQueryHandler>();
services.AddSingleton<DiagnoseQueryHandler>();
services.AddSingleton<DoctorQueryHandler>();
services.AddSingleton<ScriptDeviceCommandHandler>();
services.AddSingleton<MonitorDeviceCommandHandler>();
services.AddSingleton<IScriptLinter, DefaultScriptLinter>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace IviCli.Application.Diagnostics;

/// <summary>Query DTO for <c>ivicli diagnose</c> (PRD §6.4).</summary>
public sealed record DiagnoseQuery;
/// <summary>Query DTO for <c>ivicli doctor</c> (PRD §6.4).</summary>
public sealed record DoctorQuery;

/// <summary>The overall diagnostics report.</summary>
/// <param name="Checks">The individual checks performed, in stable order.</param>
Expand All @@ -29,8 +29,8 @@ public enum DiagnosticStatus
Error,
}

/// <summary>Errors that the diagnose query itself can fail with (rare — most issues are reported as DiagnosticCheck entries).</summary>
public abstract record DiagnoseError : IviError
/// <summary>Errors that the doctor query itself can fail with (rare — most issues are reported as DiagnosticCheck entries).</summary>
public abstract record DoctorError : IviError
{
/// <inheritdoc/>
public abstract LogSeverity Severity { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
namespace IviCli.Application.Diagnostics;

/// <summary>
/// Application-layer handler for <c>ivicli diagnose</c> (PRD §6.4). Reports
/// Application-layer handler for <c>ivicli doctor</c> (PRD §6.4). Reports
/// runtime, config, and backend-registration health. Since each check is
/// reported independently, the handler does not fail at the command level;
/// even when every check is in Error state the caller still receives a
/// populated <see cref="DiagnosticsReport"/>.
/// </summary>
public sealed class DiagnoseQueryHandler
public sealed class DoctorQueryHandler
{
private readonly IFileSystem _fs;
private readonly IEnumerable<IIviBackend> _backends;
Expand All @@ -21,7 +21,7 @@ public sealed class DiagnoseQueryHandler
private readonly string _logDirectory;

/// <summary>Creates a new handler.</summary>
public DiagnoseQueryHandler(
public DoctorQueryHandler(
IFileSystem fs,
IEnumerable<IIviBackend> backends,
IEnumerable<IBackendScanner> scanners,
Expand All @@ -36,8 +36,8 @@ DiagnoseHandlerOptions options
}

/// <summary>Runs every diagnostic check and aggregates them.</summary>
public Task<Result<DiagnosticsReport, DiagnoseError>> HandleAsync(
DiagnoseQuery query,
public Task<Result<DiagnosticsReport, DoctorError>> HandleAsync(
DoctorQuery query,
CancellationToken ct
)
{
Expand All @@ -53,7 +53,7 @@ CancellationToken ct
checks.Add(CheckScanners());

return Task.FromResult(
Result.Success<DiagnosticsReport, DiagnoseError>(
Result.Success<DiagnosticsReport, DoctorError>(
new DiagnosticsReport(checks.ToImmutable())
)
);
Expand Down Expand Up @@ -134,7 +134,7 @@ private DiagnosticCheck CheckScanners()
}
}

/// <summary>Composition-time options for <see cref="DiagnoseQueryHandler"/>.</summary>
/// <summary>Composition-time options for <see cref="DoctorQueryHandler"/>.</summary>
/// <param name="ConfigPath">Absolute path to the config.toml file.</param>
/// <param name="LogDirectory">Absolute path to the log output directory.</param>
public sealed record DiagnoseHandlerOptions(string ConfigPath, string LogDirectory);
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,34 @@
namespace IviCli.Cli.Commands;

/// <summary>
/// Wires the <c>ivicli diagnose</c> top-level subcommand.
/// Wires the <c>ivicli doctor</c> top-level subcommand.
/// </summary>
public static class DiagnoseCommand
public static class DoctorCommand
{
/// <summary>Builds the configured <see cref="Command"/>.</summary>
public static Command Build(IServiceProvider services)
{
var jsonOpt = new Option<bool>("--json") { Description = "Emit machine-readable JSON." };

var command = new Command(
"diagnose",
"doctor",
"Report runtime, configuration, and backend-registration health."
);
command.Aliases.Add("diagnose"); // deprecated pre-rename spelling; drop at 0.3.0
command.Options.Add(jsonOpt);

command.SetAction(
async (parseResult, ct) =>
{
var json = parseResult.GetValue(jsonOpt);
var handler = services.GetRequiredService<DiagnoseQueryHandler>();
var logger = services.GetRequiredService<ILogger<DiagnoseQueryHandler>>();
var handler = services.GetRequiredService<DoctorQueryHandler>();
var logger = services.GetRequiredService<ILogger<DoctorQueryHandler>>();

var result = await handler.HandleAsync(new DiagnoseQuery(), ct);
var result = await handler.HandleAsync(new DoctorQuery(), ct);
return result switch
{
Result<DiagnosticsReport, DiagnoseError>.Ok ok => Render(ok.Value, json),
Result<DiagnosticsReport, DiagnoseError>.Error err => Fail(err.Err, logger),
Result<DiagnosticsReport, DoctorError>.Ok ok => Render(ok.Value, json),
Result<DiagnosticsReport, DoctorError>.Error err => Fail(err.Err, logger),
_ => ExitCodeMapper.GenericFailure,
};
}
Expand Down Expand Up @@ -80,15 +81,15 @@ private static int Render(DiagnosticsReport report, bool emitJson)
: ExitCodeMapper.Success;
}

private static int Fail(DiagnoseError error, ILogger logger)
private static int Fail(DoctorError error, ILogger logger)
{
logger.Log(
Logging.SerilogConfiguration.ToLogLevel(error.Severity),
error.Cause,
error.Message,
error.LogArgs.ToArray()
);
Console.Error.WriteLine("error: diagnose failed.");
Console.Error.WriteLine("error: doctor failed.");
return ExitCodeMapper.GenericFailure;
}

Expand Down
2 changes: 1 addition & 1 deletion src/IviCli.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ private static RootCommand BuildRoot(IServiceProvider services)
root.Subcommands.Add(mock);
root.Subcommands.Add(ServerCommand.Build(services));
root.Subcommands.Add(ApiCommand.Build(services));
root.Subcommands.Add(DiagnoseCommand.Build(services));
root.Subcommands.Add(DoctorCommand.Build(services));
root.Subcommands.Add(DriverCommand.Build(services));
root.Subcommands.Add(LogicalCommand.Build(services));
root.Subcommands.Add(CompletionCommand.Build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

namespace IviCli.Application.Tests.Diagnostics;

public class DiagnoseQueryHandlerTests
public class DoctorQueryHandlerTests
{
private static DiagnoseQueryHandler MakeHandler(
private static DoctorQueryHandler MakeHandler(
bool configExists,
bool logDirExists,
IEnumerable<IIviBackend>? backends = null,
Expand All @@ -28,7 +28,7 @@ private static DiagnoseQueryHandler MakeHandler(
fs.AddDirectory(logDir);
}

return new DiagnoseQueryHandler(
return new DoctorQueryHandler(
fs,
backends ?? Array.Empty<IIviBackend>(),
scanners ?? Array.Empty<IBackendScanner>(),
Expand All @@ -48,7 +48,7 @@ public async Task Handle_AllPresent_ReportsAllOk()
);

// When
var result = await handler.HandleAsync(new DiagnoseQuery(), CancellationToken.None);
var result = await handler.HandleAsync(new DoctorQuery(), CancellationToken.None);

// Then
var report = result.ShouldBeOk();
Expand All @@ -63,7 +63,7 @@ public async Task Handle_NoBackends_ReportsError()
var handler = MakeHandler(configExists: true, logDirExists: true);

// When
var result = await handler.HandleAsync(new DiagnoseQuery(), CancellationToken.None);
var result = await handler.HandleAsync(new DoctorQuery(), CancellationToken.None);

// Then
var report = result.ShouldBeOk();
Expand All @@ -83,7 +83,7 @@ public async Task Handle_MissingConfig_ReportsWarning()
);

// When
var result = await handler.HandleAsync(new DiagnoseQuery(), CancellationToken.None);
var result = await handler.HandleAsync(new DoctorQuery(), CancellationToken.None);

// Then
var report = result.ShouldBeOk();
Expand Down
37 changes: 37 additions & 0 deletions tests/IviCli.Cli.Tests/Commands/DoctorCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.CommandLine;
using IviCli.Cli.Commands;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;

namespace IviCli.Cli.Tests.Commands;

public sealed class DoctorCommandTests
{
private static Command BuildDoctor() =>
DoctorCommand.Build(new ServiceCollection().BuildServiceProvider());

[Fact]
public void Command_is_named_doctor()
{
BuildDoctor().Name.ShouldBe("doctor");
}

[Fact]
public void Diagnose_remains_a_deprecated_alias()
{
BuildDoctor().Aliases.ShouldContain("diagnose");
}

[Fact]
public void Parsing_the_alias_resolves_to_the_doctor_command()
{
var root = new RootCommand("test root");
var doctor = BuildDoctor();
root.Subcommands.Add(doctor);

var result = root.Parse("diagnose");

result.Errors.ShouldBeEmpty();
result.CommandResult.Command.ShouldBeSameAs(doctor);
}
}