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
31 changes: 30 additions & 1 deletion Luotsi.Cli.Tests/CommandEnvelopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,14 @@ public async Task RunAsync_Quickstart_Human_Writes_First_Run_Plan()
public async Task RunAsync_QuickstartVerify_Groups_Proof_Checks_By_Readiness()
{
var console = new FakeConsole();
var app = new App(new AppDependencies { Console = console });
var fileSystem = new FakeFileSystem();
var timeProvider = new ManualTimeProvider(DateTimeOffset.Parse("2026-05-15T12:00:00Z", null, System.Globalization.DateTimeStyles.RoundtripKind));
var app = new App(new AppDependencies
{
Console = console,
FileSystem = fileSystem,
TimeProvider = timeProvider
});

var exitCode = await app.RunAsync(["quickstart-verify"]);
using var envelope = console.ParseSingleOutputAsJson();
Expand All @@ -359,7 +366,17 @@ public async Task RunAsync_QuickstartVerify_Groups_Proof_Checks_By_Readiness()
Assert.Equal(3, data.GetProperty("ready_count").GetInt32());
Assert.Equal(1, data.GetProperty("blocked_count").GetInt32());
Assert.Equal(1, data.GetProperty("later_count").GetInt32());
Assert.Equal(2, data.GetProperty("local_proof_count").GetInt32());
Assert.Equal(2, data.GetProperty("passed_local_proof_count").GetInt32());
Assert.Equal("luotsi version", data.GetProperty("next_command").GetString());
Assert.Contains(
data.GetProperty("local_proofs").EnumerateArray(),
proof => proof.GetProperty("kind").GetString() == "install" &&
proof.GetProperty("status").GetString() == "passed");
Assert.Contains(
data.GetProperty("local_proofs").EnumerateArray(),
proof => proof.GetProperty("kind").GetString() == "artifact_handoff" &&
proof.GetProperty("status").GetString() == "passed");
Assert.Contains(
data.GetProperty("blocked_checks").EnumerateArray(),
check => check.GetProperty("kind").GetString() == "device_truth" &&
Expand All @@ -368,6 +385,14 @@ public async Task RunAsync_QuickstartVerify_Groups_Proof_Checks_By_Readiness()
data.GetProperty("recommended_commands").EnumerateArray(),
command => command.GetProperty("kind").GetString() == "handoff" &&
command.GetProperty("command").GetString() == "luotsi quickstart --artifacts artifacts/first-run --path scenarios --write-json --write-markdown");

var artifactRoot = envelope.RootElement.GetProperty("artifacts").GetProperty("artifact_root").GetString();
Assert.Equal(Path.Join("/tmp", "luotsi", "20260515-120000-quickstart-verify"), artifactRoot);
Assert.True(fileSystem.FileExists(Path.Join(artifactRoot, "quickstart-plan.json")));
Assert.True(fileSystem.FileExists(Path.Join(artifactRoot, "quickstart-plan.md")));
Assert.True(fileSystem.FileExists(Path.Join(artifactRoot, "evaluation-proof-pack.json")));
Assert.True(fileSystem.FileExists(Path.Join(artifactRoot, "evaluation-proof-pack.md")));
Assert.True(fileSystem.FileExists(Path.Join(artifactRoot, ArtifactSession.ArtifactIndexFileName)));
}

[Fact]
Expand All @@ -385,6 +410,7 @@ public async Task RunAsync_QuickstartVerify_Uses_Device_And_Package_When_Supplie
Assert.Equal(4, data.GetProperty("ready_count").GetInt32());
Assert.Equal(0, data.GetProperty("blocked_count").GetInt32());
Assert.Equal(1, data.GetProperty("later_count").GetInt32());
Assert.Equal(2, data.GetProperty("passed_local_proof_count").GetInt32());
Assert.Equal("luotsi version", data.GetProperty("next_command").GetString());
Assert.Empty(data.GetProperty("blocked_checks").EnumerateArray());
Assert.Contains(
Expand All @@ -409,7 +435,10 @@ public async Task RunAsync_QuickstartVerify_Human_Writes_Proof_Gate()
Assert.Contains(" ready_count: 4", console.OutputLines);
Assert.Contains(" blocked_count: 0", console.OutputLines);
Assert.Contains(" later_count: 1", console.OutputLines);
Assert.Contains(" local_proof_count: 2", console.OutputLines);
Assert.Contains(" passed_local_proof_count: 2", console.OutputLines);
Assert.Contains(" next_command: luotsi version", console.OutputLines);
Assert.Contains(console.OutputLines, static line => line.Contains("local_proofs: 2", StringComparison.Ordinal));
Assert.Contains(console.OutputLines, static line => line.Contains("ready_checks: 4", StringComparison.Ordinal));
Assert.DoesNotContain(console.OutputLines, static line => line.StartsWith("{", StringComparison.Ordinal));
}
Expand Down
3 changes: 3 additions & 0 deletions Luotsi.Cli/Cli/Envelope/AppCommandHumanFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,10 @@ private static IReadOnlyList<string> BuildQuickstartVerifySummary(JsonElement va
AddScalar(lines, value, "ready_count");
AddScalar(lines, value, "blocked_count");
AddScalar(lines, value, "later_count");
AddScalar(lines, value, "local_proof_count");
AddScalar(lines, value, "passed_local_proof_count");
AddScalar(lines, value, "next_command");
AddArraySummary(lines, value, "local_proofs");
AddArraySummary(lines, value, "blocked_checks");
AddArraySummary(lines, value, "ready_checks");
AddArraySummary(lines, value, "later_checks");
Expand Down
4 changes: 3 additions & 1 deletion Luotsi.Cli/Cli/Help.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ agent_prompt that can be handed to an AI operator. proof_checks is the
a status such as ready_to_run, needs_input, or ready_after_artifact. Use
the quickstart-verify command with the same --device, --package, and
--artifacts inputs when you want that checklist grouped into ready, blocked,
and later proof commands before starting the first-run path. Add --human
and later proof commands before starting the first-run path. It also writes
the local plan/proof-pack handoff artifacts and reports the local proofs
that passed. Add --human
when you want a compact terminal plan with the first command, selected
inputs, minute-by-minute next steps, and the handoff proof command.
Add --write-json and --write-markdown to persist quickstart-plan.json,
Expand Down
2 changes: 1 addition & 1 deletion Luotsi.Cli/Cli/Routing/AppCommandDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public async Task<object> ExecuteAsync(string command, CliOptions options, strin
{
"adb" => await AdbSubcommandDispatcher.ExecuteAsync(options, RequireAdbCommandHost(runner, command)).ConfigureAwait(false),
"quickstart" => await QuickstartCommand.RunAsync(options, artifacts).ConfigureAwait(false),
"quickstart-verify" => QuickstartCommand.Verify(options),
"quickstart-verify" => await QuickstartCommand.VerifyAsync(options, artifacts).ConfigureAwait(false),
"version" => await _selfUpdateService.GetVersionInfoAsync().ConfigureAwait(false),
"update" => await _selfUpdateService.UpdateAsync(options, artifacts.Root).ConfigureAwait(false),
"devices" => DeviceInventory.FromDeviceList(await RequireRunner(runner, command).GetDevicesAsync().ConfigureAwait(false)),
Expand Down
59 changes: 57 additions & 2 deletions Luotsi.Cli/Cli/Routing/QuickstartCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,12 @@ private static QuickstartProofPackResult BuildProofPack(
recommendedCommands);
}

public static QuickstartVerifyResult Verify(CliOptions options)
public static async Task<QuickstartVerifyResult> VerifyAsync(CliOptions options, ArtifactSession artifacts)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(artifacts);

var plan = Build(options);
var plan = Build(options, artifacts.Root);
var readyChecks = plan.ProofChecks
.Where(static check => string.Equals(check.Status, "ready_to_run", StringComparison.OrdinalIgnoreCase))
.Select(QuickstartVerifyCheckResult.FromProofCheck)
Expand All @@ -357,6 +358,8 @@ public static QuickstartVerifyResult Verify(CliOptions options)
var nextCommand = readyChecks.FirstOrDefault()?.Command ??
blockedChecks.FirstOrDefault()?.Command ??
plan.FirstCommand;
var localProofs = await RunLocalProofsAsync(plan, artifacts).ConfigureAwait(false);
var passedLocalProofCount = localProofs.Count(static proof => string.Equals(proof.Status, "passed", StringComparison.OrdinalIgnoreCase));

return new QuickstartVerifyResult(
ResultSchemas.QuickstartVerify,
Expand All @@ -369,7 +372,10 @@ public static QuickstartVerifyResult Verify(CliOptions options)
readyChecks.Length,
blockedChecks.Length,
laterChecks.Length,
localProofs.Count,
passedLocalProofCount,
nextCommand,
localProofs,
readyChecks,
blockedChecks,
laterChecks,
Expand All @@ -385,6 +391,45 @@ public static QuickstartVerifyResult Verify(CliOptions options)
]);
}

private static async Task<IReadOnlyList<QuickstartLocalProofResult>> RunLocalProofsAsync(QuickstartResult plan, ArtifactSession artifacts)
{
var results = new List<QuickstartLocalProofResult>
{
new(
"install",
"passed",
"luotsi command envelope is executing and can evaluate the quickstart contract.",
plan.ProofChecks.First(static check => string.Equals(check.Kind, "install", StringComparison.Ordinal)).Command,
null)
};

var resultWithHandoff = plan with
{
Handoff = new QuickstartHandoffResult(
artifacts.Root,
Path.Join(artifacts.Root, JsonFileName),
Path.Join(artifacts.Root, MarkdownFileName),
Path.Join(artifacts.Root, ProofPackJsonFileName),
Path.Join(artifacts.Root, ProofPackMarkdownFileName),
null)
};

await WriteTextArtifactAsync(artifacts, JsonFileName, JsonSerializer.Serialize(resultWithHandoff, AppCommandJson.Options) + Environment.NewLine).ConfigureAwait(false);
await WriteTextArtifactAsync(artifacts, MarkdownFileName, BuildMarkdown(resultWithHandoff)).ConfigureAwait(false);
await WriteTextArtifactAsync(artifacts, ProofPackJsonFileName, JsonSerializer.Serialize(resultWithHandoff.ProofPack, AppCommandJson.Options) + Environment.NewLine).ConfigureAwait(false);
await WriteTextArtifactAsync(artifacts, ProofPackMarkdownFileName, BuildProofPackMarkdown(resultWithHandoff.ProofPack)).ConfigureAwait(false);
await artifacts.RefreshIndexAsync().ConfigureAwait(false);

results.Add(new(
"artifact_handoff",
"passed",
"quickstart-plan.json, quickstart-plan.md, evaluation-proof-pack.json, evaluation-proof-pack.md, and index.md were written in this command artifact root.",
plan.ProofChecks.First(static check => string.Equals(check.Kind, "artifact_handoff", StringComparison.Ordinal)).Command,
artifacts.Root));

return results;
}

private static string BuildMarkdown(QuickstartResult result)
{
var builder = new StringBuilder();
Expand Down Expand Up @@ -673,7 +718,10 @@ internal sealed record QuickstartVerifyResult(
int ReadyCount,
int BlockedCount,
int LaterCount,
int LocalProofCount,
int PassedLocalProofCount,
string NextCommand,
IReadOnlyList<QuickstartLocalProofResult> LocalProofs,
IReadOnlyList<QuickstartVerifyCheckResult> ReadyChecks,
IReadOnlyList<QuickstartVerifyCheckResult> BlockedChecks,
IReadOnlyList<QuickstartVerifyCheckResult> LaterChecks,
Expand All @@ -689,3 +737,10 @@ internal sealed record QuickstartVerifyCheckResult(
public static QuickstartVerifyCheckResult FromProofCheck(QuickstartProofCheckResult check) =>
new(check.Kind, check.Status, check.Command, check.Evidence, check.BlockedReason);
}

internal sealed record QuickstartLocalProofResult(
string Kind,
string Status,
string Evidence,
string Command,
string? ArtifactRoot);
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ After install, use the [First Run](#first-run) commands near the top of this REA

## Workflow quickstart

If you already know the target serial, start from `luotsi quickstart --device <serial> --package <app.id>`. If you do not, use bare `luotsi quickstart`; it starts with `luotsi doctor` so the next command comes from live device selection guidance. Add `--human` when you want compact terminal text, or `--write-json --write-markdown` when you want `quickstart-plan.json`, `quickstart-plan.md`, `evaluation-proof-pack.json`, and `evaluation-proof-pack.md` persisted for a human or AI operator handoff. Treat `proof_checks` as the install/device/artifact/device-truth/replay checklist for deciding whether the first five minutes produced usable evidence; each check says whether it is `ready_to_run`, `needs_input`, or `ready_after_artifact`. Run `luotsi quickstart-verify` with the same inputs when you want that checklist grouped into ready, blocked, and later proof commands before starting. Use the proof pack as the durable evidence-gate handoff before calling the first five minutes production-ready. Then choose the workflow that matches what you are trying to do:
If you already know the target serial, start from `luotsi quickstart --device <serial> --package <app.id>`. If you do not, use bare `luotsi quickstart`; it starts with `luotsi doctor` so the next command comes from live device selection guidance. Add `--human` when you want compact terminal text, or `--write-json --write-markdown` when you want `quickstart-plan.json`, `quickstart-plan.md`, `evaluation-proof-pack.json`, and `evaluation-proof-pack.md` persisted for a human or AI operator handoff. Treat `proof_checks` as the install/device/artifact/device-truth/replay checklist for deciding whether the first five minutes produced usable evidence; each check says whether it is `ready_to_run`, `needs_input`, or `ready_after_artifact`. Run `luotsi quickstart-verify` with the same inputs when you want that checklist grouped into ready, blocked, and later proof commands before starting; it also writes the local plan/proof-pack handoff artifacts and reports the local proofs that passed. Use the proof pack as the durable evidence-gate handoff before calling the first five minutes production-ready. Then choose the workflow that matches what you are trying to do:

1. First-time setup and repair:

Expand Down
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ luotsi update [--version <tag>] [--channel stable|prerelease] [--dry-run] [--det

Use these entry points when you want the shortest path into a real Luotsi workflow instead of scanning the full command surface.

For a machine-readable five-minute plan, run `luotsi quickstart`. Add `--human` when you want the same plan as compact terminal text. Without `--device`, the plan starts with `luotsi doctor` so device selection and the exact selected-device next command come from live guidance. Pass `--device`, `--package`, and `--artifacts` when you already know the target and want the output to contain concrete commands for a specific app. The output includes `proof_checks`, a compact install/device/artifact/device-truth/replay checklist for deciding whether the first five minutes produced usable evidence; every proof check reports a status such as `ready_to_run`, `needs_input`, or `ready_after_artifact`. Run `luotsi quickstart-verify` with the same inputs when you want those checks grouped into ready, blocked, and later proof commands before starting the first-run path. Add `--write-json --write-markdown` to persist `quickstart-plan.json`, `quickstart-plan.md`, `evaluation-proof-pack.json`, and `evaluation-proof-pack.md` in the artifact root for a copy-paste handoff. For the human help topic, run `luotsi help quickstart` or jump directly to a command family with `luotsi help <topic>`.
For a machine-readable five-minute plan, run `luotsi quickstart`. Add `--human` when you want the same plan as compact terminal text. Without `--device`, the plan starts with `luotsi doctor` so device selection and the exact selected-device next command come from live guidance. Pass `--device`, `--package`, and `--artifacts` when you already know the target and want the output to contain concrete commands for a specific app. The output includes `proof_checks`, a compact install/device/artifact/device-truth/replay checklist for deciding whether the first five minutes produced usable evidence; every proof check reports a status such as `ready_to_run`, `needs_input`, or `ready_after_artifact`. Run `luotsi quickstart-verify` with the same inputs when you want those checks grouped into ready, blocked, and later proof commands before starting the first-run path; it also writes the local plan/proof-pack handoff artifacts and reports the local proofs that passed. Add `--write-json --write-markdown` to persist `quickstart-plan.json`, `quickstart-plan.md`, `evaluation-proof-pack.json`, and `evaluation-proof-pack.md` in the artifact root for a copy-paste handoff. For the human help topic, run `luotsi help quickstart` or jump directly to a command family with `luotsi help <topic>`.

When `doctor --device <serial> --package <app.id>` reports `readiness_plan.status: ready`, the `next_command` now routes to `discover` first so a ready app immediately becomes review-required scenario candidates. The same `recommended_commands` list still includes package preflight, inspect, screen-state, persisted quickstart handoff, scenario validation, run, and live view commands.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ luotsi run --file scenarios/smoke.json --device <serial> --artifacts ./artifacts
| node examples/agents/extract-next-command.mjs
```

`luotsi quickstart` also returns `data.proof_checks[]`. Treat those checks as readiness evidence gates, not as the next-command priority order: they name the install, device, artifact handoff, device-truth, and replay proofs that show the first five minutes produced usable evidence. Each proof check carries a status such as `ready_to_run`, `needs_input`, or `ready_after_artifact` so agents can tell which commands still need concrete input. Run `luotsi quickstart-verify` with the same inputs when you want those checks grouped into ready, blocked, and later commands before starting.
`luotsi quickstart` also returns `data.proof_checks[]`. Treat those checks as readiness evidence gates, not as the next-command priority order: they name the install, device, artifact handoff, device-truth, and replay proofs that show the first five minutes produced usable evidence. Each proof check carries a status such as `ready_to_run`, `needs_input`, or `ready_after_artifact` so agents can tell which commands still need concrete input. Run `luotsi quickstart-verify` with the same inputs when you want those checks grouped into ready, blocked, and later commands before starting; it writes the local plan/proof-pack handoff artifacts and reports the local proofs that passed.

## Agent Sessions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ luotsi quickstart --artifacts artifacts/first-run --write-json --write-markdown

The command returns a JSON envelope with a five-minute path, concrete commands, `proof_checks`, an AI-agent prompt, an evaluation proof pack, and the local-first/artifact-first boundary that explains where Luotsi fits beside automation frameworks and hosted device services.
Use `proof_checks` as the compact install/device/artifact/device-truth/replay checklist for deciding whether the first five minutes produced usable evidence; each proof check reports whether it is `ready_to_run`, `needs_input`, or `ready_after_artifact`.
Run `luotsi quickstart-verify` with the same inputs when you want the checklist grouped into ready, blocked, and later proof commands before starting the first-run path.
Run `luotsi quickstart-verify` with the same inputs when you want the checklist grouped into ready, blocked, and later proof commands before starting the first-run path; it also writes the local plan/proof-pack handoff artifacts and reports the local proofs that passed.
Use `--human` when you want the same plan as compact terminal text while you are getting oriented.
Use `--write-json --write-markdown` to persist `quickstart-plan.json`, `quickstart-plan.md`, `evaluation-proof-pack.json`, and `evaluation-proof-pack.md` in the artifact root when a human or AI operator needs a durable handoff.
If you do not pass `--device`, the first command is `luotsi doctor`.
Expand Down
Loading
Loading