diff --git a/Luotsi.Cli.Tests/CommandEnvelopeTests.cs b/Luotsi.Cli.Tests/CommandEnvelopeTests.cs index 52b9bd3..511f2a6 100644 --- a/Luotsi.Cli.Tests/CommandEnvelopeTests.cs +++ b/Luotsi.Cli.Tests/CommandEnvelopeTests.cs @@ -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(); @@ -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" && @@ -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] @@ -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( @@ -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)); } diff --git a/Luotsi.Cli/Cli/Envelope/AppCommandHumanFormatter.cs b/Luotsi.Cli/Cli/Envelope/AppCommandHumanFormatter.cs index 15e28f3..9f6d738 100644 --- a/Luotsi.Cli/Cli/Envelope/AppCommandHumanFormatter.cs +++ b/Luotsi.Cli/Cli/Envelope/AppCommandHumanFormatter.cs @@ -244,7 +244,10 @@ private static IReadOnlyList 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"); diff --git a/Luotsi.Cli/Cli/Help.cs b/Luotsi.Cli/Cli/Help.cs index 3f44225..ad7974c 100644 --- a/Luotsi.Cli/Cli/Help.cs +++ b/Luotsi.Cli/Cli/Help.cs @@ -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, diff --git a/Luotsi.Cli/Cli/Routing/AppCommandDispatcher.cs b/Luotsi.Cli/Cli/Routing/AppCommandDispatcher.cs index f371af0..86e16d7 100644 --- a/Luotsi.Cli/Cli/Routing/AppCommandDispatcher.cs +++ b/Luotsi.Cli/Cli/Routing/AppCommandDispatcher.cs @@ -67,7 +67,7 @@ public async Task 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)), diff --git a/Luotsi.Cli/Cli/Routing/QuickstartCommand.cs b/Luotsi.Cli/Cli/Routing/QuickstartCommand.cs index bfc19bd..250dd6d 100644 --- a/Luotsi.Cli/Cli/Routing/QuickstartCommand.cs +++ b/Luotsi.Cli/Cli/Routing/QuickstartCommand.cs @@ -334,11 +334,12 @@ private static QuickstartProofPackResult BuildProofPack( recommendedCommands); } - public static QuickstartVerifyResult Verify(CliOptions options) + public static async Task 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) @@ -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, @@ -369,7 +372,10 @@ public static QuickstartVerifyResult Verify(CliOptions options) readyChecks.Length, blockedChecks.Length, laterChecks.Length, + localProofs.Count, + passedLocalProofCount, nextCommand, + localProofs, readyChecks, blockedChecks, laterChecks, @@ -385,6 +391,45 @@ public static QuickstartVerifyResult Verify(CliOptions options) ]); } + private static async Task> RunLocalProofsAsync(QuickstartResult plan, ArtifactSession artifacts) + { + var results = new List + { + 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(); @@ -673,7 +718,10 @@ internal sealed record QuickstartVerifyResult( int ReadyCount, int BlockedCount, int LaterCount, + int LocalProofCount, + int PassedLocalProofCount, string NextCommand, + IReadOnlyList LocalProofs, IReadOnlyList ReadyChecks, IReadOnlyList BlockedChecks, IReadOnlyList LaterChecks, @@ -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); diff --git a/README.md b/README.md index fa70ca2..966d47c 100644 --- a/README.md +++ b/README.md @@ -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 --package `. 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 --package `. 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: diff --git a/docs/commands.md b/docs/commands.md index 602a6a1..80b4fc5 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -40,7 +40,7 @@ luotsi update [--version ] [--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 `. +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 `. When `doctor --device --package ` 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. diff --git a/website/src/content/docs/docs/getting-started/first-five-minutes.mdx b/website/src/content/docs/docs/getting-started/first-five-minutes.mdx index 5f4d1ad..a623db6 100644 --- a/website/src/content/docs/docs/getting-started/first-five-minutes.mdx +++ b/website/src/content/docs/docs/getting-started/first-five-minutes.mdx @@ -116,7 +116,7 @@ luotsi run --file scenarios/smoke.json --device --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 diff --git a/website/src/content/docs/docs/getting-started/quickstart.mdx b/website/src/content/docs/docs/getting-started/quickstart.mdx index 06ada5f..a37fb26 100644 --- a/website/src/content/docs/docs/getting-started/quickstart.mdx +++ b/website/src/content/docs/docs/getting-started/quickstart.mdx @@ -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`. diff --git a/website/src/content/docs/docs/reference/output-envelopes.mdx b/website/src/content/docs/docs/reference/output-envelopes.mdx index e669db1..4dd8168 100644 --- a/website/src/content/docs/docs/reference/output-envelopes.mdx +++ b/website/src/content/docs/docs/reference/output-envelopes.mdx @@ -34,7 +34,7 @@ When an agent or CI job needs the next action, read command hints in this order: The source tree includes executable examples of this priority order at `examples/agents/extract-next-command.py` and `examples/agents/extract-next-command.mjs`. -Quickstart envelopes additionally include `data.proof_checks[]`. Those checks are readiness evidence gates for the first-five-minute path, not a replacement for the next-command priority order above. Each check includes a status such as `ready_to_run`, `needs_input`, or `ready_after_artifact`. `luotsi quickstart-verify` turns the same proof checks into a `luotsi-quickstart-verify.v1` gate with ready, blocked, and later command groups. +Quickstart envelopes additionally include `data.proof_checks[]`. Those checks are readiness evidence gates for the first-five-minute path, not a replacement for the next-command priority order above. Each check includes a status such as `ready_to_run`, `needs_input`, or `ready_after_artifact`. `luotsi quickstart-verify` turns the same proof checks into a `luotsi-quickstart-verify.v1` gate with ready, blocked, and later command groups, plus `local_proofs[]` for safe checks it completed while writing the command artifact handoff. When an artifact root has a persisted package intake audit, human output for `artifacts info` and `replay capsule` includes a compact `intake:` line with status, share-safety, SHA verification, and package path.