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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- Accepted Kilo versions at or above `@kilocode/cli >=7.2.0` instead of
pre-blocking future Kilo releases with the previous `7.2.0`-only gate.
- Kept local verification blockers and mismatches visible in rollback output
so users can see which Kilo layer prevented setup.
- Promoted `moonshotai/Kimi-K2.6` to the recommended validated curated
default, with installer-managed `limit.output = 8192` for Kilo `7.2.0`
compatibility.
Expand Down
45 changes: 30 additions & 15 deletions src/cli/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,7 @@ function renderHumanResult(result: InstallFlowResult): string {
output.push(`Verification target: ${result.verificationTarget.modelRef}`);
}

if ((result.blockers?.length ?? 0) > 0) {
output.push("Blockers:");

for (const blocker of result.blockers ?? []) {
output.push(formatVerificationBlocker(blocker));
}
}

if ((result.mismatches?.length ?? 0) > 0) {
output.push("Mismatches:");

for (const mismatch of result.mismatches ?? []) {
output.push(formatVerificationMismatch(mismatch));
}
}
appendHumanVerificationDetails(output, result);

appendHumanResultNotices(output, result.notices);
output.push("");
Expand All @@ -150,6 +136,11 @@ function renderHumanResult(result: InstallFlowResult): string {
"GonkaGate Kilo setup rolled back after a failed install attempt.",
redactSecretBearingText(result.message),
];
if (result.verificationTarget !== undefined) {
output.push(`Verification target: ${result.verificationTarget.modelRef}`);
}

appendHumanVerificationDetails(output, result);
appendHumanResultNotices(output, result.notices);
output.push("");

Expand All @@ -169,6 +160,30 @@ function renderHumanResult(result: InstallFlowResult): string {
return output.join("\n");
}

function appendHumanVerificationDetails(
output: string[],
result: {
blockers?: readonly EffectiveConfigVerificationBlocker[];
mismatches?: readonly EffectiveConfigVerificationMismatch[];
},
): void {
if ((result.blockers?.length ?? 0) > 0) {
output.push("Blockers:");

for (const blocker of result.blockers ?? []) {
output.push(formatVerificationBlocker(blocker));
}
}

if ((result.mismatches?.length ?? 0) > 0) {
output.push("Mismatches:");

for (const mismatch of result.mismatches ?? []) {
output.push(formatVerificationMismatch(mismatch));
}
}
}

function finalizeCliExecution(
outcome: CliExecutionOutcome,
options: {
Expand Down
3 changes: 3 additions & 0 deletions src/install/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ export interface FailedInstallResult

export interface RolledBackInstallResult
extends InstallFlowResultBase, InstallFlowProgress {
blockers?: readonly EffectiveConfigVerificationBlocker[];
errorCode: "installation_rolled_back";
mismatches?: readonly EffectiveConfigVerificationMismatch[];
ok: false;
status: "rolled_back";
verificationTarget?: EffectiveConfigVerificationTarget;
}

export interface InstalledInstallResult
Expand Down
30 changes: 30 additions & 0 deletions src/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import {
verifyEffectiveKiloConfig,
} from "./verify-effective.js";
import type { ManagedArtifactRollbackAction } from "./contracts/managed-artifact.js";
import type {
EffectiveConfigVerificationBlocker,
EffectiveConfigVerificationMismatch,
EffectiveConfigVerificationTarget,
} from "./contracts/effective-config.js";
import type { CliOptions } from "../cli/contracts.js";
import type { InstallDependencies } from "./deps.js";
import { isInstallError, isInstallErrorCode } from "./errors.js";
Expand Down Expand Up @@ -338,8 +343,11 @@ async function buildInstallFailureResult(
return buildInstallErrorResult(rollbackError, progressState);
}

const verificationDetails = getRolledBackVerificationDetails(error);

return {
...progressState,
...verificationDetails,
context: createInstallFlowContext(),
errorCode: "installation_rolled_back",
message: `Installation failed and installer-owned writes were rolled back. ${formatInstallFailureMessage(
Expand All @@ -350,6 +358,28 @@ async function buildInstallFailureResult(
};
}

function getRolledBackVerificationDetails(error: unknown): {
blockers?: readonly EffectiveConfigVerificationBlocker[];
mismatches?: readonly EffectiveConfigVerificationMismatch[];
verificationTarget?: EffectiveConfigVerificationTarget;
} {
if (isInstallErrorCode(error, "effective_config_blocked")) {
return {
blockers: error.details.blockers,
verificationTarget: error.details.target,
};
}

if (isInstallErrorCode(error, "effective_config_mismatch")) {
return {
mismatches: error.details.mismatches,
verificationTarget: error.details.target,
};
}

return {};
}

function buildInstallErrorResult(
error: unknown,
progressState: InstallProgressState,
Expand Down
30 changes: 30 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,36 @@ test("CLI emits structured JSON failed payloads for resolved-config mismatches",
assert.match(stdout.contents, /"errorCode": "installation_rolled_back"/);
});

test("human-readable rollback output includes verification blockers", async () => {
const stdout = createBufferWriter();
const stderr = createBufferWriter();
const dependencies = createCliDependencies({
env: {
GONKAGATE_API_KEY: "gp-from-env",
KILO_CONFIG: "/workspace/session/kilo.json",
},
repository: true,
seedFiles: [
{
contents: '{\n "model": "openai/gpt-4.1"\n}\n',
path: "/workspace/session/kilo.json",
},
],
});

const result = await run(["--yes"], {
dependencies,
stderr,
stdout,
});

assert.equal(result.exitCode, 1);
assert.match(stdout.contents, /rolled back/i);
assert.match(stdout.contents, /Blockers:/);
assert.match(stdout.contents, /KILO_CONFIG/);
assert.match(stdout.contents, /model/);
});

test("human-readable success output still includes the minimal next step", async () => {
const stdout = createBufferWriter();
const stderr = createBufferWriter();
Expand Down
2 changes: 2 additions & 0 deletions test/install/flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ test("runInstallFlow rolls managed writes back when durable verification fails",

assert.equal(result.status, "rolled_back");
assert.equal(result.errorCode, "installation_rolled_back");
assert.equal(result.blockers?.[0]?.layer, "inferred_non_local");
assert.equal(fs.readText(managedPaths.secretPath), undefined);
assert.equal(fs.readText(managedPaths.userConfigDefaultPath), undefined);
assert.equal(fs.readText(managedPaths.projectConfigDefaultPath), undefined);
Expand Down Expand Up @@ -278,6 +279,7 @@ test("runInstallFlow still rolls managed writes back when KILO_CONFIG blocks dur

assert.equal(result.status, "rolled_back");
assert.equal(result.errorCode, "installation_rolled_back");
assert.equal(result.blockers?.[0]?.layer, "KILO_CONFIG");
assert.equal(fs.readText(managedPaths.secretPath), undefined);
assert.equal(fs.readText(managedPaths.userConfigDefaultPath), undefined);
assert.equal(fs.readText(managedPaths.projectConfigDefaultPath), undefined);
Expand Down
Loading