@kici-dev/compiler 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +37 -7
- package/dist/commands/approve.d.ts +12 -0
- package/dist/commands/approve.js +5 -2
- package/dist/commands/compile.js +4 -2
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/endpoints.js +4 -6
- package/dist/commands/held-run-client.d.ts +21 -1
- package/dist/commands/held-run-client.js +34 -15
- package/dist/commands/hook.js +22 -20
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -1
- package/dist/commands/init.d.ts +9 -2
- package/dist/commands/init.js +43 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/orchestrators.js +3 -2
- package/dist/commands/reject.d.ts +12 -0
- package/dist/commands/reject.js +5 -2
- package/dist/commands/report/collect.d.ts +82 -0
- package/dist/commands/report/collect.js +234 -0
- package/dist/commands/report/identity.d.ts +48 -0
- package/dist/commands/report/identity.js +49 -0
- package/dist/commands/report/index.d.ts +63 -0
- package/dist/commands/report/index.js +119 -0
- package/dist/commands/report/upload.d.ts +38 -0
- package/dist/commands/report/upload.js +64 -0
- package/dist/commands/run-hold-watch.js +2 -2
- package/dist/commands/run.js +6 -3
- package/dist/commands/runs/show.js +80 -1
- package/dist/commands/types.js +51 -8
- package/dist/execution/sdk-alias.js +4 -2
- package/dist/fixtures/compiler.js +2 -1
- package/dist/format.js +3 -3
- package/dist/generators/secrets-dts.d.ts +8 -2
- package/dist/generators/secrets-dts.js +3 -2
- package/dist/hooks/installer.js +2 -1
- package/dist/llm-context/llms-architecture.txt +35 -13
- package/dist/llm-context/llms-cli.txt +142 -28
- package/dist/llm-context/llms-features-execution.txt +2017 -0
- package/dist/llm-context/llms-features.txt +298 -1483
- package/dist/llm-context/llms-full.txt +3008 -1698
- package/dist/llm-context/llms-getting-started.txt +145 -12
- package/dist/llm-context/llms-patterns.txt +176 -1
- package/dist/llm-context/llms-providers.txt +11 -27
- package/dist/llm-context/llms-sdk-runtime.txt +25 -4
- package/dist/llm-context/llms-sdk.txt +31 -1
- package/dist/llm-context/llms.txt +22 -14
- package/dist/local-plane/paths.d.ts +15 -0
- package/dist/local-plane/paths.js +22 -1
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/local-plane/port-holder.js +1 -1
- package/dist/local-plane/postgres.d.ts +3 -16
- package/dist/local-plane/postgres.js +10 -15
- package/dist/lockfile/generator.d.ts +12 -0
- package/dist/lockfile/generator.js +47 -14
- package/dist/postinstall.js +2 -1
- package/dist/remote/config.d.ts +2 -15
- package/dist/remote/config.js +2 -16
- package/dist/remote/dashboard-client.d.ts +39 -0
- package/dist/remote/dashboard-client.js +41 -0
- package/dist/remote/oauth.js +7 -5
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/dry-run.js +4 -2
- package/dist/test-runner/git-detector.js +2 -1
- package/dist/test-runner/job-executor.js +2 -1
- package/dist/test-runner/payload-builder.js +11 -17
- package/dist/types.d.ts +33 -3
- package/dist/validation/validator.js +23 -6
- package/package.json +16 -11
- package/sbom.spdx.json +953 -901
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import "../../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { DashboardClient } from "../../remote/dashboard-client.js";
|
|
3
|
+
import * as fs$1 from "node:fs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
//#region src/commands/report/upload.ts
|
|
6
|
+
/**
|
|
7
|
+
* Upload a report bundle privately to KiCI.
|
|
8
|
+
*
|
|
9
|
+
* Three steps, and the split is the point: the Platform mints a presigned PUT,
|
|
10
|
+
* the bytes go straight from this machine to object storage, and a confirm call
|
|
11
|
+
* lets the Platform verify what actually landed. The bundle never transits the
|
|
12
|
+
* control plane, and the Platform never takes the client's word for the size.
|
|
13
|
+
*
|
|
14
|
+
* Upload is always opt-in (`--upload`). A bundle contains diagnostic data from
|
|
15
|
+
* the customer's own machine, so sending it is a separate, explicit act from
|
|
16
|
+
* producing it.
|
|
17
|
+
*/
|
|
18
|
+
/** sha256 of the file, which the Platform records alongside the report. */
|
|
19
|
+
function sha256Of(body) {
|
|
20
|
+
return createHash("sha256").update(body).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
async function defaultPutBytes(url, body) {
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
method: "PUT",
|
|
25
|
+
body: new Uint8Array(body),
|
|
26
|
+
headers: { "Content-Length": String(body.byteLength) }
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) throw new Error(`Bundle upload failed (${res.status} ${res.statusText}).`);
|
|
29
|
+
}
|
|
30
|
+
async function defaultDeps() {
|
|
31
|
+
const client = await DashboardClient.load();
|
|
32
|
+
return {
|
|
33
|
+
createIssueReport: client.createIssueReport.bind(client),
|
|
34
|
+
confirmIssueReport: client.confirmIssueReport.bind(client),
|
|
35
|
+
putBytes: defaultPutBytes
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Upload one bundle and return the reference id to quote to support.
|
|
40
|
+
*
|
|
41
|
+
* Reads the file once and derives both the size and the digest from those same
|
|
42
|
+
* bytes, so what is promised at presign time is necessarily what is uploaded —
|
|
43
|
+
* a stat-then-read would race a file still being written.
|
|
44
|
+
*/
|
|
45
|
+
async function uploadReportBundle(zipPath, meta, deps) {
|
|
46
|
+
const resolved = deps ?? await defaultDeps();
|
|
47
|
+
const body = fs$1.readFileSync(zipPath);
|
|
48
|
+
const { ref, uploadUrl } = await resolved.createIssueReport({
|
|
49
|
+
bundleId: meta.bundleId,
|
|
50
|
+
byteSize: body.byteLength,
|
|
51
|
+
sha256: sha256Of(body),
|
|
52
|
+
message: meta.message,
|
|
53
|
+
email: meta.email
|
|
54
|
+
});
|
|
55
|
+
await resolved.putBytes(uploadUrl, body);
|
|
56
|
+
return {
|
|
57
|
+
ref,
|
|
58
|
+
status: (await resolved.confirmIssueReport(ref)).status
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { sha256Of, uploadReportBundle };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -66,7 +66,7 @@ async function handleNewHolds(args) {
|
|
|
66
66
|
printHold(action.hold, out);
|
|
67
67
|
ctx = ctx ?? await resolveCtx();
|
|
68
68
|
if (!ctx) {
|
|
69
|
-
out(pc.dim(`Run held; approve via \`kici approve ${action.hold.runId}\`.`));
|
|
69
|
+
out(pc.dim(`Run held; approve via \`kici approve ${action.hold.runId} --hold ${action.hold.id}\`.`));
|
|
70
70
|
continue;
|
|
71
71
|
}
|
|
72
72
|
if (args.approveAll) {
|
|
@@ -74,7 +74,7 @@ async function handleNewHolds(args) {
|
|
|
74
74
|
continue;
|
|
75
75
|
}
|
|
76
76
|
if (action.kind === "notify") {
|
|
77
|
-
out(pc.dim(`Run held; approve via the dashboard or \`kici approve ${action.hold.runId}\`.`));
|
|
77
|
+
out(pc.dim(`Run held; approve via the dashboard or \`kici approve ${action.hold.runId} --hold ${action.hold.id}\`.`));
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
const approved = await args.confirm("Approve this gate?");
|
package/dist/commands/run.js
CHANGED
|
@@ -303,8 +303,10 @@ async function renderResults(results, options) {
|
|
|
303
303
|
const junitXml = formatJunitResult(runResults);
|
|
304
304
|
await writeFile(options.junit, junitXml);
|
|
305
305
|
if (!options.quiet) logger.info(pc.green(`JUnit XML written to ${options.junit}`));
|
|
306
|
-
} else if (!options.quiet || results.some((r) => r.status !== "success" && r.status !== "accepted"))
|
|
307
|
-
|
|
306
|
+
} else if (!options.quiet || results.some((r) => r.status !== "success" && r.status !== "accepted")) {
|
|
307
|
+
if (runResults.length > 1) logger.info(formatMultiFixtureSummary(runResults));
|
|
308
|
+
else displayRemoteResults(results);
|
|
309
|
+
}
|
|
308
310
|
return results.every((r) => r.status === "accepted" || r.status === "success");
|
|
309
311
|
}
|
|
310
312
|
/**
|
|
@@ -537,10 +539,11 @@ async function watchRunHolds(runId, seenHolds, getCtx, setCtx, options) {
|
|
|
537
539
|
const holds = await listHeldRunsForRun(ctx, runId);
|
|
538
540
|
if (holds.length === 0) return;
|
|
539
541
|
const quiet = Boolean(options.quiet);
|
|
542
|
+
const isTty = quiet ? false : Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
540
543
|
await handleNewHolds({
|
|
541
544
|
holds,
|
|
542
545
|
seen: seenHolds,
|
|
543
|
-
isTty
|
|
546
|
+
isTty,
|
|
544
547
|
output: quiet ? (line) => void process.stderr.write(line + "\n") : (line) => logger.info(line),
|
|
545
548
|
approveAll: Boolean(options.approveAll),
|
|
546
549
|
confirm: (message) => confirm({
|
|
@@ -2,6 +2,7 @@ import "../../rolldown-runtime-ClRpJifh.js";
|
|
|
2
2
|
import { DashboardClient, DashboardClientError } from "../../remote/dashboard-client.js";
|
|
3
3
|
import { RunHistory } from "../../remote/history.js";
|
|
4
4
|
import { colorStatus, formatDuration as formatDuration$1, relativeTime } from "../../remote/render.js";
|
|
5
|
+
import { HeldRunRequestError, listHeldRunsForRun, resolveHeldRunContext } from "../held-run-client.js";
|
|
5
6
|
import pc from "picocolors";
|
|
6
7
|
import { logger, toErrorMessage } from "@kici-dev/core";
|
|
7
8
|
//#region src/commands/runs/show.ts
|
|
@@ -17,33 +18,111 @@ async function runsShowCommand(runId, options = {}) {
|
|
|
17
18
|
throw err;
|
|
18
19
|
}
|
|
19
20
|
const detail = await client.getRunDetail(runId);
|
|
21
|
+
const holds = await loadHolds(runId);
|
|
20
22
|
if (options.json) {
|
|
21
23
|
console.log(JSON.stringify({
|
|
22
24
|
run,
|
|
23
|
-
detail
|
|
25
|
+
detail,
|
|
26
|
+
heldRuns: holds.holds,
|
|
27
|
+
...holds.unavailable && { heldRunsUnavailable: holds.unavailable }
|
|
24
28
|
}, null, 2));
|
|
25
29
|
return true;
|
|
26
30
|
}
|
|
27
31
|
printHeader(run);
|
|
32
|
+
printInitFailure(detail.initFailure);
|
|
28
33
|
printJobs(detail.jobs);
|
|
34
|
+
printHolds(holds);
|
|
29
35
|
return true;
|
|
30
36
|
} catch (err) {
|
|
31
37
|
logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
|
|
32
38
|
return false;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Read the approval holds recorded for a run.
|
|
43
|
+
*
|
|
44
|
+
* The holds are extra detail on a run this command can already display, so a
|
|
45
|
+
* failure here degrades to a note rather than failing the command: an operator
|
|
46
|
+
* asking why a job did not run still gets the run, its jobs and its init
|
|
47
|
+
* failure. The auth context is resolved quietly for the same reason — a config
|
|
48
|
+
* that cannot reach the held-runs API is not an error for `runs show`.
|
|
49
|
+
*/
|
|
50
|
+
async function loadHolds(runId) {
|
|
51
|
+
try {
|
|
52
|
+
const ctx = await resolveHeldRunContext({ quiet: true });
|
|
53
|
+
if (!ctx) return {
|
|
54
|
+
holds: [],
|
|
55
|
+
unavailable: "not authenticated for held-run lookup",
|
|
56
|
+
unavailableSilently: true
|
|
57
|
+
};
|
|
58
|
+
return { holds: await listHeldRunsForRun(ctx, runId) };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const denied = err instanceof HeldRunRequestError && err.isPermissionDenied;
|
|
61
|
+
return {
|
|
62
|
+
holds: [],
|
|
63
|
+
unavailable: toErrorMessage(err),
|
|
64
|
+
...denied && { unavailableSilently: true }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
35
68
|
function printHeader(run) {
|
|
36
69
|
const f = (v) => v == null ? "—" : v;
|
|
37
70
|
console.log(pc.bold(`\nRun ${run.runId}`) + ` ${colorStatus(run.status)}`);
|
|
38
71
|
console.log(pc.gray(` workflow=${f(run.workflowName)} repo=${f(run.repoIdentifier)} branch=${f(run.ref)} sha=${f(run.sha)} trigger=${f(run.triggerEvent)} by=${f(run.triggeredBy)}`));
|
|
39
72
|
console.log(pc.gray(` started=${relativeTime(run.startedAt ?? void 0)}`));
|
|
40
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The run-scoped init failure — why the run never started a step (an
|
|
76
|
+
* unresolvable lock file, a secret it could not read, a gate it never passed).
|
|
77
|
+
* Nothing is printed when the run started normally.
|
|
78
|
+
*/
|
|
79
|
+
function printInitFailure(initFailure) {
|
|
80
|
+
if (!initFailure) return;
|
|
81
|
+
console.log(`\n ${pc.yellow("Init failure")} ${pc.gray(`(${initFailure.category})`)}`);
|
|
82
|
+
console.log(` ${initFailure.message}`);
|
|
83
|
+
}
|
|
41
84
|
function printJobs(jobs) {
|
|
42
85
|
for (const j of jobs) {
|
|
43
86
|
console.log(`\n ${pc.bold(j.jobName)} ${colorStatus(j.status)} ` + pc.gray(j.durationMs ? formatDuration$1(j.durationMs) : ""));
|
|
87
|
+
printJobFailure(j);
|
|
44
88
|
for (const s of j.steps ?? []) console.log(pc.gray(" └─ ") + `${s.stepName} ${colorStatus(s.status)} ` + pc.gray(`${s.durationMs ? formatDuration$1(s.durationMs) : ""}${s.exitCode != null ? ` exit=${s.exitCode}` : ""}`));
|
|
45
89
|
}
|
|
46
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Why a single job failed: its job-scoped init failure (a job the context rules
|
|
93
|
+
* rejected carries `context_rules` plus the context and rule that rejected it),
|
|
94
|
+
* or its plain error message. The init failure wins when both are present —
|
|
95
|
+
* the two carry the same text on a rejected job, and the category is the part
|
|
96
|
+
* that tells the reader whether a gate or a step failed.
|
|
97
|
+
*/
|
|
98
|
+
function printJobFailure(job) {
|
|
99
|
+
if (job.initFailure) {
|
|
100
|
+
console.log(` ${pc.yellow("init failure")} ${pc.gray(`(${job.initFailure.category})`)}: ` + job.initFailure.message);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (job.errorMessage) console.log(` ${pc.red("error")}: ${job.errorMessage}`);
|
|
104
|
+
}
|
|
105
|
+
/** The approval holds recorded against the run, or a note when unreadable. */
|
|
106
|
+
function printHolds(result) {
|
|
107
|
+
if (result.unavailable) {
|
|
108
|
+
if (!result.unavailableSilently) console.log(pc.gray(`\n Approval-hold detail unavailable: ${result.unavailable}`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (result.holds.length === 0) return;
|
|
112
|
+
console.log(`\n ${pc.bold(`Approval holds (${result.holds.length})`)}`);
|
|
113
|
+
for (const h of result.holds) {
|
|
114
|
+
const parts = [
|
|
115
|
+
h.jobId || "(workflow)",
|
|
116
|
+
h.holdType ?? "—",
|
|
117
|
+
h.contextName ? `context=${h.contextName}` : void 0,
|
|
118
|
+
h.queueType ? `queue=${h.queueType}` : void 0,
|
|
119
|
+
colorStatus(h.status)
|
|
120
|
+
].filter((p) => p != null);
|
|
121
|
+
console.log(` ${parts.join(" ")}`);
|
|
122
|
+
if (h.reason) console.log(pc.gray(` reason: ${h.reason}`));
|
|
123
|
+
if (h.expiresAt) console.log(pc.gray(` expires: ${relativeTime(h.expiresAt)}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
47
126
|
async function showLocalFallback(runId, json) {
|
|
48
127
|
const history = new RunHistory();
|
|
49
128
|
await history.load();
|
package/dist/commands/types.js
CHANGED
|
@@ -8,6 +8,32 @@ import fs from "node:fs/promises";
|
|
|
8
8
|
import { toErrorMessage } from "@kici-dev/core";
|
|
9
9
|
//#region src/commands/types.ts
|
|
10
10
|
/**
|
|
11
|
+
* Error kinds meaning the Platform is genuinely unreachable or the CLI is not
|
|
12
|
+
* configured to reach it — not authenticated, no active org, network down, or
|
|
13
|
+
* orchestrator offline. For these, `kici types` writes a valid empty stub so
|
|
14
|
+
* the declaration file always exists (typecheck degrades to "no known keys"
|
|
15
|
+
* rather than "module has no exported member") instead of failing.
|
|
16
|
+
*
|
|
17
|
+
* An authenticated-but-rejected response (`unauthorized`, `forbidden`, …) is
|
|
18
|
+
* deliberately excluded: that is a real credential/permission problem the user
|
|
19
|
+
* must see, and writing a stub would mask it.
|
|
20
|
+
*/
|
|
21
|
+
const UNREACHABLE_ERROR_KINDS = [
|
|
22
|
+
"not_logged_in",
|
|
23
|
+
"no_active_org",
|
|
24
|
+
"orchestrator_offline",
|
|
25
|
+
"http"
|
|
26
|
+
];
|
|
27
|
+
/** True if `p` exists (any type). Used to avoid clobbering a real snapshot. */
|
|
28
|
+
async function fileExists(p) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(p);
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
11
37
|
* Generate TypeScript declarations for context secrets.
|
|
12
38
|
*
|
|
13
39
|
* Fetches context metadata (with secret key names) through the Platform
|
|
@@ -18,23 +44,40 @@ import { toErrorMessage } from "@kici-dev/core";
|
|
|
18
44
|
* @returns true on success, false on error
|
|
19
45
|
*/
|
|
20
46
|
async function typesCommand(options = {}) {
|
|
47
|
+
const kiciDir = options.kiciDir ?? ".kici";
|
|
48
|
+
const typesDir = path.join(kiciDir, "types");
|
|
49
|
+
const outputPath = path.join(typesDir, "secrets.d.ts");
|
|
21
50
|
try {
|
|
22
51
|
const config = await loadGlobalConfig();
|
|
52
|
+
const metadata = (await DashboardClient.fromConfig(config).listContexts(true)).map((e) => ({
|
|
53
|
+
name: e.name,
|
|
54
|
+
keys: e.secretKeys ?? []
|
|
55
|
+
}));
|
|
56
|
+
const source = config.platformEndpoint ?? config.endpoint ?? "kici Platform";
|
|
23
57
|
const dtsContent = generateSecretsDts({
|
|
24
|
-
contexts:
|
|
25
|
-
|
|
26
|
-
keys: e.secretKeys ?? []
|
|
27
|
-
})),
|
|
28
|
-
endpoint: (config.platformEndpoint ?? config.endpoint ?? "kici Platform").replace(/\/+$/, "")
|
|
58
|
+
contexts: metadata,
|
|
59
|
+
endpoint: source.replace(/\/+$/, "")
|
|
29
60
|
});
|
|
30
|
-
const kiciDir = options.kiciDir ?? ".kici";
|
|
31
|
-
const typesDir = path.join(kiciDir, "types");
|
|
32
61
|
await fs.mkdir(typesDir, { recursive: true });
|
|
33
|
-
const outputPath = path.join(typesDir, "secrets.d.ts");
|
|
34
62
|
await fs.writeFile(outputPath, dtsContent, "utf-8");
|
|
35
63
|
if (!options.quiet) console.log(pc.green("Types generated") + pc.dim(` ${outputPath}`));
|
|
36
64
|
return true;
|
|
37
65
|
} catch (err) {
|
|
66
|
+
if (err instanceof DashboardClientError && UNREACHABLE_ERROR_KINDS.includes(err.kind)) {
|
|
67
|
+
if (await fileExists(outputPath)) {
|
|
68
|
+
console.error(pc.yellow(`${err.message} Keeping the existing ${outputPath}; run \`kici types\` when authenticated to refresh it.`));
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const stub = generateSecretsDts({
|
|
72
|
+
contexts: [],
|
|
73
|
+
endpoint: "",
|
|
74
|
+
offline: true
|
|
75
|
+
});
|
|
76
|
+
await fs.mkdir(typesDir, { recursive: true });
|
|
77
|
+
await fs.writeFile(outputPath, stub, "utf-8");
|
|
78
|
+
console.error(pc.yellow(`${err.message} Wrote an offline type stub to ${outputPath}; run \`kici types\` when authenticated to populate it.`));
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
38
81
|
if (err instanceof DashboardClientError) {
|
|
39
82
|
console.error(pc.red(err.message));
|
|
40
83
|
return false;
|
|
@@ -19,7 +19,8 @@ import { readFile } from "node:fs/promises";
|
|
|
19
19
|
*/
|
|
20
20
|
async function getSdkPathFromPackageJson() {
|
|
21
21
|
try {
|
|
22
|
-
const
|
|
22
|
+
const pkgPath = path.resolve(process.cwd(), ".kici", "package.json");
|
|
23
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
23
24
|
const sdkPath = JSON.parse(content).kici?.sdkPath;
|
|
24
25
|
if (!sdkPath) return null;
|
|
25
26
|
return path.resolve(sdkPath);
|
|
@@ -36,7 +37,8 @@ async function getSdkPathFromPackageJson() {
|
|
|
36
37
|
*/
|
|
37
38
|
async function isInKiciRepo() {
|
|
38
39
|
try {
|
|
39
|
-
const
|
|
40
|
+
const rootPkgPath = path.resolve(process.cwd(), "package.json");
|
|
41
|
+
const content = await readFile(rootPkgPath, "utf-8");
|
|
40
42
|
return JSON.parse(content).kici?.development === true;
|
|
41
43
|
} catch {
|
|
42
44
|
return false;
|
|
@@ -119,7 +119,8 @@ async function ensureRuntimeSymlinks(filePath) {
|
|
|
119
119
|
continue;
|
|
120
120
|
} catch {}
|
|
121
121
|
try {
|
|
122
|
-
const
|
|
122
|
+
const entry = import.meta.resolve(pkg);
|
|
123
|
+
const pkgDir = await findPackageRoot(fileURLToPath(entry));
|
|
123
124
|
const linkParent = path.dirname(linkPath);
|
|
124
125
|
if (linkParent !== nodeModulesDir) await fs.mkdir(linkParent, { recursive: true });
|
|
125
126
|
await fs.symlink(pkgDir, linkPath);
|
package/dist/format.js
CHANGED
|
@@ -6,12 +6,12 @@ import "./rolldown-runtime-ClRpJifh.js";
|
|
|
6
6
|
function formatRelativeTime(isoDate) {
|
|
7
7
|
const ms = Date.now() - new Date(isoDate).getTime();
|
|
8
8
|
if (ms < 0) return "just now";
|
|
9
|
-
const minutes = Math.floor(ms /
|
|
9
|
+
const minutes = Math.floor(ms / 6e4);
|
|
10
10
|
if (minutes < 1) return "just now";
|
|
11
11
|
if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
12
|
-
const hours = Math.floor(ms /
|
|
12
|
+
const hours = Math.floor(ms / 36e5);
|
|
13
13
|
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
14
|
-
const days = Math.floor(ms /
|
|
14
|
+
const days = Math.floor(ms / 864e5);
|
|
15
15
|
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
16
16
|
}
|
|
17
17
|
//#endregion
|
|
@@ -10,9 +10,16 @@ export interface ContextMetadata {
|
|
|
10
10
|
name: string;
|
|
11
11
|
keys: string[];
|
|
12
12
|
}
|
|
13
|
-
interface GenerateSecretsDtsOptions {
|
|
13
|
+
export interface GenerateSecretsDtsOptions {
|
|
14
14
|
contexts: ContextMetadata[];
|
|
15
15
|
endpoint: string;
|
|
16
|
+
/**
|
|
17
|
+
* Mark the output as an offline stub written when the Platform could not be
|
|
18
|
+
* reached (unauthenticated, offline, or no active org). The header names it a
|
|
19
|
+
* stub instead of a source URL so a reader can tell it from a real snapshot,
|
|
20
|
+
* and the caller passes `contexts: []` so it augments no known keys.
|
|
21
|
+
*/
|
|
22
|
+
offline?: boolean;
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
18
25
|
* Generate a .d.ts string from context metadata.
|
|
@@ -24,5 +31,4 @@ interface GenerateSecretsDtsOptions {
|
|
|
24
31
|
* @returns The .d.ts file content as a string
|
|
25
32
|
*/
|
|
26
33
|
export declare function generateSecretsDts(options: GenerateSecretsDtsOptions): string;
|
|
27
|
-
export {};
|
|
28
34
|
//# sourceMappingURL=secrets-dts.d.ts.map
|
|
@@ -34,10 +34,11 @@ function buildKeySourceMap(contexts) {
|
|
|
34
34
|
* @returns The .d.ts file content as a string
|
|
35
35
|
*/
|
|
36
36
|
function generateSecretsDts(options) {
|
|
37
|
-
const { contexts, endpoint } = options;
|
|
37
|
+
const { contexts, endpoint, offline } = options;
|
|
38
38
|
const lines = [];
|
|
39
39
|
lines.push("// @generated by kici types -- DO NOT EDIT");
|
|
40
|
-
lines.push(
|
|
40
|
+
if (offline) lines.push("// Source: offline stub -- Platform unreachable, no known secret keys");
|
|
41
|
+
else lines.push(`// Source: ${endpoint}`);
|
|
41
42
|
lines.push("// Run `kici types` to refresh");
|
|
42
43
|
lines.push("");
|
|
43
44
|
lines.push("declare module '@kici-dev/sdk' {");
|
package/dist/hooks/installer.js
CHANGED
|
@@ -252,7 +252,8 @@ repos:
|
|
|
252
252
|
*/
|
|
253
253
|
async function resolveCommonGitDir(gitDir) {
|
|
254
254
|
try {
|
|
255
|
-
const
|
|
255
|
+
const commondirPath = path.join(gitDir, "commondir");
|
|
256
|
+
const commondir = (await readFile(commondirPath, "utf-8")).trim();
|
|
256
257
|
return path.resolve(gitDir, commondir);
|
|
257
258
|
} catch {
|
|
258
259
|
return gitDir;
|
|
@@ -316,10 +316,13 @@ The `ProviderRegistry` maps routing keys to provider bundles. Each routing key (
|
|
|
316
316
|
- `FileContentsFetcher` -- reads arbitrary repository files at a ref, for the declarative content-requirements (`requires`) filter
|
|
317
317
|
- `CloneTokenProvider` -- generates clone tokens for agents
|
|
318
318
|
- `RepoUrlBuilder` -- builds clone URLs and raw file URLs
|
|
319
|
-
- `ContributorResolver` -- resolves contributor permissions for trust-tier gating
|
|
320
319
|
- `CheckStatusPoster` -- posts check statuses (approval/hold) to the git provider
|
|
321
320
|
|
|
322
|
-
A
|
|
321
|
+
A bundle also carries a `hasForkModel` flag, set for a provider whose head ref can live outside the base repository. It is what admits a pull-request event to the org fork switch. GitHub sets it; a generic source (whose trust boundary is its verification secret) and a local source (whose trust boundary is on-disk ownership) do not.
|
|
322
|
+
|
|
323
|
+
A GitHub App source populates all seven. The file-contents capability arrives as a per-delivery factory rather than a prebuilt instance: a GitHub client is scoped to one installation, and the installation id is known only once the delivery's credentials are resolved.
|
|
324
|
+
|
|
325
|
+
A plain generic webhook source carries only the normalizer, because it has no repository API to fetch a lock file or post a check against. The pipeline skips the stages whose interface is absent rather than failing the delivery.
|
|
323
326
|
|
|
324
327
|
Provider registrations are managed via the `sources` database table, not via `SharedConfig`. When the orchestrator connects to the Platform relay, it reads source records from the DB and sends `source.register` messages. Changes to sources (add/remove) are detected via PostgreSQL LISTEN/NOTIFY on the `sources_change` channel and pushed to the Platform via `source.secrets` and `source.register`/`source.deregister`.
|
|
325
328
|
|
|
@@ -439,7 +442,18 @@ Orchestrator Agent Sandbox (child pro
|
|
|
439
442
|
|
|
440
443
|
### Agent pipeline
|
|
441
444
|
|
|
442
|
-
The agent delegates job execution to an `ExecutionSandbox` (container, bare-metal, or firecracker). The sandbox runs customer code in an isolated child process -- never in the agent's V8 isolate.
|
|
445
|
+
The agent delegates job execution to an `ExecutionSandbox` (container, bare-metal, or firecracker). The sandbox runs customer code in an isolated child process -- never in the agent's V8 isolate.
|
|
446
|
+
|
|
447
|
+
Six job types are handled. Only the first uses a sandbox; the other five run in-process, because they never execute customer workflow steps:
|
|
448
|
+
|
|
449
|
+
- **Execution jobs** -- the standard sandbox path.
|
|
450
|
+
- **Init-only jobs** -- dynamic field resolution.
|
|
451
|
+
- **Global-eval-round jobs** -- filters and generators for the candidate global workflows of one workflow repo.
|
|
452
|
+
- **DynamicJobFn evaluation jobs** -- runtime job generation.
|
|
453
|
+
- **Bring-up jobs** -- init-runner SSH bring-up. No clone, no sandbox.
|
|
454
|
+
- **Build-only jobs** -- cache population.
|
|
455
|
+
|
|
456
|
+
See [Job execution lifecycle](https://docs.kici.dev/architecture/execution/job-execution/) for details.
|
|
443
457
|
|
|
444
458
|
1. **Report running** -- Send `job.status: running` immediately upon accepting the dispatch
|
|
445
459
|
2. **Sandbox selection** -- Determine execution mode (container, bare-metal, firecracker) from job config and environment
|
|
@@ -512,7 +526,7 @@ Build Job Dispatch --> Build Agent (kici:role:builder + matching kici:os:/kici:a
|
|
|
512
526
|
| |-- Pack .kici/ source (portable tar.gz, excludes node_modules)
|
|
513
527
|
| |-- Pack .kici/node_modules (portable tar.gz)
|
|
514
528
|
| |-- Upload source tarball to cache (source/{contentHash}.tar.gz)
|
|
515
|
-
| |-- Upload deps tarball to cache (deps/{plat}-{arch}/{
|
|
529
|
+
| |-- Upload deps tarball to cache (deps/{plat}-{arch}/{depsHash}.tar.gz)
|
|
516
530
|
| |-- Upload deps companion .hash file
|
|
517
531
|
| |-- Report success (cache.upload.complete × 2)
|
|
518
532
|
| |
|
|
@@ -586,7 +600,7 @@ Dep cache misses alone do **not** trigger a build job. Deps are platform-specifi
|
|
|
586
600
|
|
|
587
601
|
### Cross-source / no-contentHash workflows
|
|
588
602
|
|
|
589
|
-
- **Lock files without `contentHash`** (schema v1) skip the source cache entirely; agents compile from source. Regenerate lock files with `kici compile` to enable caching. The current lock file schema version is
|
|
603
|
+
- **Lock files without `contentHash`** (schema v1) skip the source cache entirely; agents compile from source. Regenerate lock files with `kici compile` to enable caching. The current lock file schema version is 39.
|
|
590
604
|
- **Cross-source / global-workflow dispatch** (a workflow registered against source A fired by a webhook on source B) bypasses both caches. The registration's lock file entry still carries `contentHash`, but the cross-source path always clone-and-installs — the eval temp dir doesn't ship `@kici-dev/sdk`. The execution agent still verifies `contentHash` against the cloned source for drift detection.
|
|
591
605
|
|
|
592
606
|
### Build deduplication
|
|
@@ -626,7 +640,10 @@ Both source and dep caches use `S3CacheStorage` as the sole backend. The `CacheS
|
|
|
626
640
|
Cache keys reflect that source tarballs and deps have different platform characteristics:
|
|
627
641
|
|
|
628
642
|
- **Source:** `source/{contentHash}.tar.gz` — platform-agnostic. Raw TypeScript source is identical regardless of CPU architecture, so one entry is shared across all platforms. `contentHash` is the per-workflow hash from the lock file (`SHA-256(COMPILE_SCHEMA_VERSION + ":" + rawSource [+ "\0" + assetDigest])`, where `COMPILE_SCHEMA_VERSION = 5` and line endings are normalized to LF so the hash agrees across platforms).
|
|
629
|
-
- **Deps:** `deps/{platform}-{arch}/{
|
|
643
|
+
- **Deps:** `deps/{platform}-{arch}/{depsHash}.tar.gz`, with a
|
|
644
|
+
`deps/{platform}-{arch}/{lockfileHash}.hash` pointer holding that hash — the
|
|
645
|
+
tarball is addressed by its own content, so two builds sharing a lock file
|
|
646
|
+
cannot leave a tarball and an integrity hash that disagree. Platform-specific. Native dependencies in `node_modules` differ across architectures, so each platform/arch combination gets its own cache entry.
|
|
630
647
|
|
|
631
648
|
The orchestrator derives the target platform/arch for dep cache lookups by probing `AgentRegistry.findAvailable()` with the workflow's first job's `runsOn` labels to find a representative matching agent, then using that agent's platform and arch. Falls back to `linux/x64` if no matching agents are registered.
|
|
632
649
|
|
|
@@ -1304,7 +1321,7 @@ The orchestrator is the execution brain. It decides what to run and dispatches w
|
|
|
1304
1321
|
- **Job queue** -- PostgreSQL-backed FIFO queue for reliable dispatch.
|
|
1305
1322
|
- **Webhook pipeline** -- Dedup, event mapping, lock file fetch, trigger matching, and job dispatch in a single pipeline.
|
|
1306
1323
|
- **Multi-orchestrator clustering** -- Optional peer-to-peer coordination via direct WebSocket connections. Enables cross-architecture job routing (e.g., x64 coordinator reroutes arm64 jobs to a peer), high availability, and dedicated coordinator topologies. Uses Raft consensus for leader election (orphan recovery). See [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/).
|
|
1307
|
-
- **Auto-scaler** -- Optional pluggable module for ephemeral agent provisioning.
|
|
1324
|
+
- **Auto-scaler** -- Optional pluggable module for ephemeral agent provisioning. Four backends are configurable: containers (Docker/Podman), bare-metal processes, Firecracker microVMs, and the event backend, which performs no local compute -- it emits reserved scale-up / scale-down events that a customer-authored provisioning workflow consumes to boot and tear down a cloud instance. Spawns agents on demand when no matching agent is connected, with label-based routing, two-level capacity limits (global + per-backend), warm pools, YAML configuration (`scalers.d/` directory support), and SIGHUP reload. Disabled by default -- orchestrator works without it.
|
|
1308
1325
|
- **Independent database** -- Has its own PostgreSQL database separate from the Platform. Stores execution runs/jobs/steps, dispatch queue, webhook secrets, dedup cache, and scaler state. The orchestrator's `execution_runs` and `execution_jobs` are the authoritative source of truth. The Platform receives execution status updates via WebSocket messages (`execution.status`, `job.status.forward`).
|
|
1309
1326
|
|
|
1310
1327
|
> Source: `packages/orchestrator/src/pipeline/processor.ts` (webhook pipeline), `packages/orchestrator/src/cluster/` (P2P coordination), `packages/orchestrator/src/scaler/` (auto-scaler module), `packages/orchestrator/src/server.ts` (Platform/hybrid entry point)
|
|
@@ -1314,6 +1331,7 @@ The orchestrator is the execution brain. It decides what to run and dispatches w
|
|
|
1314
1331
|
The agent is the execution worker. It runs on customer infrastructure and has full access to customer code.
|
|
1315
1332
|
|
|
1316
1333
|
- **Repository cloning** -- Clones the target repo with token-based auth (token in HTTP headers, not URLs, to prevent leakage).
|
|
1334
|
+
- **Git credential helper** -- Registers a credential helper for the job's git operations. Every network operation asks the orchestrator's broker for a credential, so a token is minted seconds before use rather than held for the life of the job. Write access is opt-in and time-boxed: `ctx.repo.withWrite(...)` adds a repository-scoped grant for the duration of its callback and revokes it afterwards, with a TTL backstop.
|
|
1317
1335
|
- **Step execution** -- Runs steps in declaration order with full `StepContext` (zx shell, logger, environment, workflow/job metadata). Steps wrapped in a `parallel()` group run concurrently behind a `maxParallel` window, and each child reports as its own observable step with its own logs, status, timing, and retry.
|
|
1318
1336
|
- **Execution sandboxes** -- Runs the workflow runner as a separate child process with a sanitized environment, in one of three sandboxes: bare metal (process fork, with optional bubblewrap namespace isolation), a container runtime (the whole job lifecycle runs inside a disposable container), or inside a Firecracker microVM. Agent-internal credentials never reach customer workflow code.
|
|
1319
1337
|
- **Log streaming** -- Chunked log streaming back to the orchestrator with configurable size limits.
|
|
@@ -1329,11 +1347,14 @@ The agent is the execution worker. It runs on customer infrastructure and has fu
|
|
|
1329
1347
|
Shared business logic used by all three tiers. Single source of truth for cross-tier concerns. Has no internal `@kici-dev/*` dependencies -- only a handful of third-party libraries.
|
|
1330
1348
|
|
|
1331
1349
|
- Protocol message schemas (Zod-based, direction-specific unions including dashboard REST-over-WS, browser live streaming, the test-relay control plane, log pull, run events, peer-to-peer, cluster join, and source registration)
|
|
1332
|
-
- Provider interfaces (WebhookNormalizer, LockFileFetcher, ChangedFilesFetcher, FileContentsFetcher, CloneTokenProvider, RepoUrlBuilder,
|
|
1350
|
+
- Provider interfaces (WebhookNormalizer, LockFileFetcher, ChangedFilesFetcher, FileContentsFetcher, CloneTokenProvider, RepoUrlBuilder, CheckStatusPoster), plus the deprecated `ContributorResolver` the pipeline no longer calls
|
|
1351
|
+
- Git credential vocabulary (forge names plus the credential reference, grant, request, and result shapes the SDK declares and the orchestrator's broker resolves) and the agent→orchestrator relay protocol its credential helper calls. See [Git credentials](https://docs.kici.dev/user/patterns/git-credentials/)
|
|
1333
1352
|
- Trigger matching engine (branch, path, event evaluation)
|
|
1353
|
+
- Content-requirement matcher (the declarative `requires` filter -- pure data describing a query over the bytes of one source file at the event's ref, interpreted by the orchestrator via the `FileContentsFetcher` so no author code runs there) and the shared text-match vocabulary (`contains` / `notContains` / `matches` / `notMatches`) it shares with the commit-message trigger filter
|
|
1334
1354
|
- Dispatch inputs (input descriptors, extraction from the trigger event, and coercion to typed values)
|
|
1335
1355
|
- Matrix expansion and fanout (combination expansion with include/exclude, job-name suffix formatting, and materialization of one matrix or multi-host job into N dispatchable children)
|
|
1336
|
-
- Execution status vocabulary (run/job/step status enums + terminal-state sets; lifecycle owned by the orchestrator's execution tracker)
|
|
1356
|
+
- Execution status vocabulary (run/job/step status enums + terminal-state sets; lifecycle owned by the orchestrator's execution tracker) and its presentation layer (the total precedence order that decides which status wins a roll-up, legacy-spelling resolution, and the per-status failure classification every consumer asks about)
|
|
1357
|
+
- Job-kind discriminator, alongside the status enums. It separates a `standard` job running steps from an invoke `gate` and from the `proxy` job that mirrors a summoned run
|
|
1337
1358
|
- Check mode (the idempotent run modes `apply` / `check` / `check-fail-on-drift` and the per-step outcome vocabulary)
|
|
1338
1359
|
- Webhook signature verification (HMAC-SHA256, timing-safe)
|
|
1339
1360
|
- WebSocket close codes (unified across all tiers)
|
|
@@ -1344,12 +1365,13 @@ Shared business logic used by all three tiers. Single source of truth for cross-
|
|
|
1344
1365
|
- Approval requirements (normalized approver clauses shared by the orchestrator gate, the resolver, the held-run store, and the agent step round-trip)
|
|
1345
1366
|
- Build provenance (in-toto statement schema, DSSE envelope, attestation bundle, verification)
|
|
1346
1367
|
- Artifact name contract (the shared filesystem/URL-safe name schema the orchestrator, agent, and SDK all validate against)
|
|
1347
|
-
- Developer MCP tool schemas (argument schemas for the AI-agent tool surface)
|
|
1368
|
+
- Developer MCP tool schemas (argument schemas for the AI-agent tool surface) and the untrusted-content fence that wraps every repository- or contributor-supplied value an agent reads in a per-response random nonce, so log lines and error text cannot be read as instructions
|
|
1348
1369
|
- Developer-operations contract (one row per workflow-developer operation declaring which entrypoints expose it -- the shared REST API behind the web UI and the `kici` CLI, the AI-agent tool surface, and a curated UI flag -- asserted against each real surface by congruence tests)
|
|
1349
1370
|
- Label utilities (platform label derivation, runsOn normalization, `kici:*` set-only reserved namespace, role labels)
|
|
1350
1371
|
- Host inventory (the canonical queryable host-roster schema shared by the orchestrator's roster store, the agent-facing inventory API, and the SDK's `ctx.kici.inventory`)
|
|
1351
1372
|
- Audit policy and retention (per-action access-log sampling, warm-retention windows for cold-store eligibility, federated activity row schema)
|
|
1352
|
-
- Scaler backend type enum (`container`, `bare-metal`, `firecracker`, `kubernetes`)
|
|
1373
|
+
- Scaler backend type enum (`container`, `bare-metal`, `firecracker`, `kubernetes`, `event`) and the reserved `kici.` event-name prefix that keeps a user step from forging a system event
|
|
1374
|
+
- Job resource vocabulary (the requests/limits shape the SDK accepts, the compiler validates and emits, the orchestrator uses for capacity math and kernel-side enforcement, and the dashboard displays)
|
|
1353
1375
|
- Registration trigger type enum (registerable trigger discriminator)
|
|
1354
1376
|
- Sandbox capability set (the Linux capability names a container sandbox may add or drop, shared by the SDK validator, the compiler, and the dispatch resolver)
|
|
1355
1377
|
- Plan tier vocabulary (the hosted plan tiers and the purchasable subset, shared by the Platform and the browser dashboard)
|
|
@@ -1375,7 +1397,7 @@ It also runs the **local dev plane** -- an on-demand, fully local execution stac
|
|
|
1375
1397
|
|
|
1376
1398
|
### `@kici-dev/core`
|
|
1377
1399
|
|
|
1378
|
-
Light shared utilities with no server-side dependencies
|
|
1400
|
+
Light shared utilities with no server-side dependencies. It provides JSON-structured logging, error helpers, async-local-storage request context, and human-readable formatting (`formatBytes`/`formatDuration`/`formatUptime`). It also provides cryptographic helpers (`sha256`/`sha256File`/`deriveSharedSecret` plus symmetric encrypt/decrypt), retry-backoff computation, and the shared diagnostics-result contract. The rest of its surface ships as subpath entry points: the temp-directory allocator and its garbage collector, package-manager detection, CI-environment detection, and the idempotent-step runner (the check / confirm / apply primitive behind idempotent steps). Finally it supplies zx initialization (`initZx()`) and the TypeScript loader hook that transforms TypeScript on import. It is the dependency-light core that the SDK, compiler, and `kici` CLI consume directly so they stay free of heavier server-only dependencies. `@kici-dev/shared` re-exports it, so existing `@kici-dev/shared` import paths keep working.
|
|
1379
1401
|
|
|
1380
1402
|
> Source: `packages/core/src/`
|
|
1381
1403
|
|
|
@@ -1463,7 +1485,7 @@ The orchestrator connects outbound to the Platform WebSocket endpoint. After aut
|
|
|
1463
1485
|
|
|
1464
1486
|
When multiple orchestrators are deployed, they establish direct WebSocket connections to each other on the `/ws/peer` endpoint. Peers are discovered via the Platform matchmaker (Platform/hybrid modes) or static configuration (`KICI_CLUSTER_PEERS` env var, independent mode). Connections are authenticated with a mutual pre-shared key (PSK). Traffic includes agent inventory heartbeats, job rerouting, progress reporting, cancel propagation, and Raft leader election. These messages never transit the Platform tier.
|
|
1465
1487
|
|
|
1466
|
-
> See [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/) for clustering details and [Protocol Messages](https://docs.kici.dev/architecture/protocol/dashboard/#orchestrator
|
|
1488
|
+
> See [Multi-Orchestrator Architecture](https://docs.kici.dev/architecture/clustering/multi-orchestrator/) for clustering details and [Protocol Messages](https://docs.kici.dev/architecture/protocol/dashboard/#orchestrator---orchestrator-messages-peer-to-peer) for message schemas.
|
|
1467
1489
|
|
|
1468
1490
|
### Orchestrator ↔ Agent
|
|
1469
1491
|
|