@kybernesis/create 0.14.3 → 0.14.6
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/README.md +10 -1
- package/dist/cli.js +7 -2
- package/dist/init.js +13 -1
- package/dist/templates.js +1 -1
- package/dist/upgrade-sessions.d.ts +43 -0
- package/dist/upgrade-sessions.js +123 -0
- package/dist/upgrade.d.ts +5 -1
- package/dist/upgrade.js +294 -95
- package/dist/util.d.ts +4 -0
- package/dist/util.js +5 -1
- package/package.json +1 -1
- package/skills/fde-engagement/references/playbook.md +1 -1
package/README.md
CHANGED
|
@@ -47,13 +47,22 @@ Run inside an agent project. Checks, with pass/warn/fail per line:
|
|
|
47
47
|
|
|
48
48
|
Exit code 1 on any failure — usable as a CI preflight.
|
|
49
49
|
|
|
50
|
-
## `kyb upgrade [--skip-eval]`
|
|
50
|
+
## `kyb upgrade [--skip-eval] [--yes]`
|
|
51
51
|
|
|
52
52
|
Compares installed `@kybernesis/*` versions against npm, installs what's
|
|
53
53
|
behind, typechecks, then **runs the eval suite as the gate** — prints
|
|
54
54
|
"deploy" only on green. This is the maintenance-retainer loop as a command:
|
|
55
55
|
package improvement → `kyb upgrade` per client → green → `npx eve deploy`.
|
|
56
56
|
|
|
57
|
+
Before an eve version change, upgrade reads `.eve/.workflow-data/runs`, prints
|
|
58
|
+
`This will reset N open conversations`, lists matching persisted Buzz channel
|
|
59
|
+
sessions when available, and requires explicit confirmation. Noninteractive
|
|
60
|
+
runs fail closed unless `--yes` (or `-y`) is supplied. The warning is still printed with
|
|
61
|
+
`--yes` and is not bypassed by `--skip-eval`. Existing direct `eve eval` scripts
|
|
62
|
+
are migrated to `kyb-eval`; custom scripts are left untouched with a manual fix.
|
|
63
|
+
The wrapper counts corruption diagnostics beside the eval result and turns a
|
|
64
|
+
nominally green eve exit into a failure when condemned durable state was seen.
|
|
65
|
+
|
|
57
66
|
On self-hosted agents, upgrade also reconciles package-owned host artifacts. It
|
|
58
67
|
refreshes a stale `/etc/cron.daily/kyb-docker-prune` and an existing generated
|
|
59
68
|
systemd unit, warning before replacement and printing an exact manual repair
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* kyb deploy put this repo on its host and restart it, with proof
|
|
12
12
|
* kyb upgrade bump @kybernesis/* to latest, gated on the eval suite
|
|
13
13
|
* --skip-eval skip the eval gate (not for production changes)
|
|
14
|
+
* --yes, -y confirm an eve upgrade noninteractively
|
|
14
15
|
*/
|
|
15
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
16
17
|
import { basename, dirname, join } from "node:path";
|
|
@@ -73,7 +74,7 @@ const COMMANDS = {
|
|
|
73
74
|
credential: "Write the agent credential onto a host (--local to stay here).",
|
|
74
75
|
register: "Register this agent with the control plane (--name, --url).",
|
|
75
76
|
deploy: "Deploy this agent to its host (--no-env to leave the env file alone).",
|
|
76
|
-
upgrade: "Bring @kybernesis packages and eve to
|
|
77
|
+
upgrade: "Bring @kybernesis packages and eve to certified versions (--skip-eval, --yes).",
|
|
77
78
|
tui: "Talk to your agents in the terminal.",
|
|
78
79
|
version: "Print the version of this tool.",
|
|
79
80
|
};
|
|
@@ -144,7 +145,10 @@ switch (command) {
|
|
|
144
145
|
tui(rest);
|
|
145
146
|
break;
|
|
146
147
|
case "upgrade":
|
|
147
|
-
await upgrade(
|
|
148
|
+
await upgrade({
|
|
149
|
+
skipEval: rest.includes("--skip-eval"),
|
|
150
|
+
yes: rest.includes("--yes") || rest.includes("-y"),
|
|
151
|
+
});
|
|
148
152
|
break;
|
|
149
153
|
case "--version":
|
|
150
154
|
case "-v":
|
|
@@ -209,6 +213,7 @@ ${dim(" npm i -g @kybernesis/create@latest")}
|
|
|
209
213
|
--no-env ${dim("do not send .env.local (host manages its own secrets)")}
|
|
210
214
|
${bold("kyb upgrade")} bump @kybernesis/* packages, gated on evals
|
|
211
215
|
--skip-eval ${dim("skip the eval gate")}
|
|
216
|
+
--yes, -y ${dim("confirm an eve version change noninteractively")}
|
|
212
217
|
|
|
213
218
|
Registry: https://registry.kybernesis.ai · Docs: the FDE playbook
|
|
214
219
|
`);
|
package/dist/init.js
CHANGED
|
@@ -74,7 +74,19 @@ export async function init(rawName, options = {}) {
|
|
|
74
74
|
// refuses an id it cannot find in the AI Gateway catalog and then creates
|
|
75
75
|
// nothing at all, and an exe-hosted agent takes its model from EXE_MODEL,
|
|
76
76
|
// not from the gateway.
|
|
77
|
-
|
|
77
|
+
// Two guards, both learned on a real Factory build (KYB-521):
|
|
78
|
+
// - AI_AGENT: eve's init hands off to `eve dev --onboard` when it sees no
|
|
79
|
+
// interactive terminal, and that is a server that waits for a person, so a
|
|
80
|
+
// headless `kyb init` never returned. eve skips the handoff and prints its
|
|
81
|
+
// summary when it believes a coding agent launched it; the marker is
|
|
82
|
+
// stripped from eve's own child spawns, so nothing else sees it.
|
|
83
|
+
// - a timeout, because `--yes` cannot reach a choice eve makes internally,
|
|
84
|
+
// and a scaffold that cannot finish must fail, not wait forever.
|
|
85
|
+
run("npx", [`eve@${EVE_VERSION}`, "init", name], {
|
|
86
|
+
allowFail: true,
|
|
87
|
+
env: { AI_AGENT: process.env.AI_AGENT?.trim() || "kybernesis-create" },
|
|
88
|
+
timeoutMs: 20 * 60_000,
|
|
89
|
+
});
|
|
78
90
|
// The real test of that step, since its exit code cannot be trusted.
|
|
79
91
|
if (!existsSync(join(dir, "package.json"))) {
|
|
80
92
|
console.error(red(`\n eve did not create a project in ${dir}. Nothing else can run.`));
|
package/dist/templates.js
CHANGED
|
@@ -207,7 +207,7 @@ export function evalScript(name, depts) {
|
|
|
207
207
|
`ARCANA_DM_WORKSPACE=${name}-eval`,
|
|
208
208
|
...depts.map((d) => `ARCANA_${d.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_WORKSPACE=${name}-eval`),
|
|
209
209
|
];
|
|
210
|
-
return `${overrides.join(" ")}
|
|
210
|
+
return `${overrides.join(" ")} kyb-eval`;
|
|
211
211
|
}
|
|
212
212
|
export const CHANNEL_KINDS = [
|
|
213
213
|
"none",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export interface DurableRunInspection {
|
|
3
|
+
runningRunIds: string[];
|
|
4
|
+
inspectedRecords: number;
|
|
5
|
+
issues: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface BuzzSessionMatch {
|
|
8
|
+
runId: string;
|
|
9
|
+
community: string;
|
|
10
|
+
channel: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BuzzSessionInspection {
|
|
13
|
+
path?: string;
|
|
14
|
+
matches: BuzzSessionMatch[];
|
|
15
|
+
issue?: string;
|
|
16
|
+
}
|
|
17
|
+
export type EvalScriptReconciliation = {
|
|
18
|
+
kind: "updated";
|
|
19
|
+
script: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "current";
|
|
22
|
+
script: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "custom";
|
|
25
|
+
script: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "missing";
|
|
28
|
+
};
|
|
29
|
+
/** Read the local durable records without loading eve or mutating its store. */
|
|
30
|
+
export declare function inspectDurableRuns(cwd: string): DurableRunInspection;
|
|
31
|
+
/** Follow the same precedence as buzzBridge when enough environment is available. */
|
|
32
|
+
export declare function buzzSessionsPath(cwd: string, env: Record<string, string | undefined>): string | undefined;
|
|
33
|
+
/** Best-effort join from durable run ids to Buzz's persisted community|channel map. */
|
|
34
|
+
export declare function inspectBuzzSessions(cwd: string, env: Record<string, string | undefined>, runningRunIds: readonly string[]): BuzzSessionInspection;
|
|
35
|
+
/** Replace only a simple direct eve eval invocation, preserving assignments and arguments. */
|
|
36
|
+
export declare function reconcileEvalScript(script: unknown): EvalScriptReconciliation;
|
|
37
|
+
export declare function confirmEveUpgrade(options: {
|
|
38
|
+
yes: boolean;
|
|
39
|
+
input?: Readable & {
|
|
40
|
+
isTTY?: boolean;
|
|
41
|
+
};
|
|
42
|
+
output?: Writable;
|
|
43
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
/** Read the local durable records without loading eve or mutating its store. */
|
|
5
|
+
export function inspectDurableRuns(cwd) {
|
|
6
|
+
const runsDir = join(cwd, ".eve", ".workflow-data", "runs");
|
|
7
|
+
if (!existsSync(runsDir))
|
|
8
|
+
return { runningRunIds: [], inspectedRecords: 0, issues: [] };
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = readdirSync(runsDir, { withFileTypes: true });
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
runningRunIds: [],
|
|
16
|
+
inspectedRecords: 0,
|
|
17
|
+
issues: [`could not read ${runsDir}: ${error.message}`],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const runningRunIds = [];
|
|
21
|
+
const issues = [];
|
|
22
|
+
let inspectedRecords = 0;
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
25
|
+
continue;
|
|
26
|
+
const path = join(runsDir, entry.name);
|
|
27
|
+
try {
|
|
28
|
+
const record = JSON.parse(readFileSync(path, "utf8"));
|
|
29
|
+
inspectedRecords += 1;
|
|
30
|
+
if (record.status !== "running")
|
|
31
|
+
continue;
|
|
32
|
+
if (typeof record.runId !== "string" || record.runId.length === 0) {
|
|
33
|
+
issues.push(`${path}: running record has no string runId`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
runningRunIds.push(record.runId);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
issues.push(`${path}: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { runningRunIds, inspectedRecords, issues };
|
|
43
|
+
}
|
|
44
|
+
function resolveEnvPath(cwd, value) {
|
|
45
|
+
return isAbsolute(value) ? value : resolve(cwd, value);
|
|
46
|
+
}
|
|
47
|
+
/** Follow the same precedence as buzzBridge when enough environment is available. */
|
|
48
|
+
export function buzzSessionsPath(cwd, env) {
|
|
49
|
+
if (env.BUZZ_SESSIONS_FILE)
|
|
50
|
+
return resolveEnvPath(cwd, env.BUZZ_SESSIONS_FILE);
|
|
51
|
+
const legacy = join(cwd, ".buzz-sessions.json");
|
|
52
|
+
if (existsSync(legacy))
|
|
53
|
+
return legacy;
|
|
54
|
+
if (env.BUZZ_KEYFILE)
|
|
55
|
+
return join(dirname(resolveEnvPath(cwd, env.BUZZ_KEYFILE)), "buzz-sessions.json");
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Best-effort join from durable run ids to Buzz's persisted community|channel map. */
|
|
59
|
+
export function inspectBuzzSessions(cwd, env, runningRunIds) {
|
|
60
|
+
const path = buzzSessionsPath(cwd, env);
|
|
61
|
+
if (!path) {
|
|
62
|
+
return { matches: [], issue: "no BUZZ_SESSIONS_FILE, legacy .buzz-sessions.json, or BUZZ_KEYFILE was found" };
|
|
63
|
+
}
|
|
64
|
+
if (!existsSync(path))
|
|
65
|
+
return { path, matches: [], issue: `${path} does not exist` };
|
|
66
|
+
try {
|
|
67
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
69
|
+
throw new Error("expected an object map");
|
|
70
|
+
const running = new Set(runningRunIds);
|
|
71
|
+
const matches = [];
|
|
72
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
73
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
74
|
+
continue;
|
|
75
|
+
const runId = value.id;
|
|
76
|
+
if (typeof runId !== "string" || !running.has(runId))
|
|
77
|
+
continue;
|
|
78
|
+
const separator = key.lastIndexOf("|");
|
|
79
|
+
matches.push({
|
|
80
|
+
runId,
|
|
81
|
+
community: separator < 0 ? "unknown community" : key.slice(0, separator),
|
|
82
|
+
channel: separator < 0 ? key : key.slice(separator + 1),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return { path, matches };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return { path, matches: [], issue: `could not read Buzz session metadata at ${path}: ${error.message}` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Replace only a simple direct eve eval invocation, preserving assignments and arguments. */
|
|
92
|
+
export function reconcileEvalScript(script) {
|
|
93
|
+
if (typeof script !== "string" || script.trim().length === 0)
|
|
94
|
+
return { kind: "missing" };
|
|
95
|
+
if (/^\s*(?:(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+))\s+)*kyb-eval(?:\s|$)/.test(script)) {
|
|
96
|
+
return { kind: "current", script };
|
|
97
|
+
}
|
|
98
|
+
if (/[;&|<>`]/.test(script))
|
|
99
|
+
return { kind: "custom", script };
|
|
100
|
+
const direct = /^(\s*(?:(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+))\s+)*)eve\s+eval(?=\s|$)(.*)$/.exec(script);
|
|
101
|
+
if (!direct)
|
|
102
|
+
return { kind: "custom", script };
|
|
103
|
+
return { kind: "updated", script: `${direct[1]}kyb-eval${direct[2]}` };
|
|
104
|
+
}
|
|
105
|
+
export async function confirmEveUpgrade(options) {
|
|
106
|
+
if (options.yes)
|
|
107
|
+
return true;
|
|
108
|
+
const input = options.input ?? process.stdin;
|
|
109
|
+
const output = options.output ?? process.stdout;
|
|
110
|
+
if (input.isTTY !== true)
|
|
111
|
+
return false;
|
|
112
|
+
const rl = createInterface({ input, output });
|
|
113
|
+
try {
|
|
114
|
+
const answer = (await rl.question("Continue with the eve upgrade? Type yes to continue: ")).trim().toLowerCase();
|
|
115
|
+
return answer === "yes" || answer === "y";
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
rl.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
package/dist/upgrade.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { type AgentInputLimit } from "./agent-limits.js";
|
|
2
2
|
export declare function agentInputLimitUpgradeMessage(limit: AgentInputLimit): string;
|
|
3
|
-
export
|
|
3
|
+
export interface UpgradeOptions {
|
|
4
|
+
skipEval?: boolean;
|
|
5
|
+
yes?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function upgrade(options?: UpgradeOptions): Promise<void>;
|
package/dist/upgrade.js
CHANGED
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { upsertEnv } from "./envfile.js";
|
|
4
5
|
import { reconcileHostArtifact } from "./host-artifacts.js";
|
|
5
|
-
import { repairManageRestart } from "./systemd.js";
|
|
6
|
+
import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
|
|
6
7
|
import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
|
|
7
8
|
import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
|
|
8
9
|
import { EVE_VERSION, bold, capture, dim, green, parseEnv, red, run, yellow } from "./util.js";
|
|
9
10
|
import { inspectEveAgent } from "./agent-limits.js";
|
|
10
|
-
|
|
11
|
-
* Which packages to upgrade: every `@kybernesis/*` this agent depends on.
|
|
12
|
-
*
|
|
13
|
-
* @remarks
|
|
14
|
-
* This was a fixed list of six, written when there were six. Four more shipped
|
|
15
|
-
* afterwards — connectors, local, manage and exe — and an agent using them was
|
|
16
|
-
* told "everything is at latest certified versions" while holding versions from
|
|
17
|
-
* months earlier. A hardcoded list does not fail loudly when it falls behind;
|
|
18
|
-
* it just quietly stops covering things, and the command that reports it is the
|
|
19
|
-
* same one that is wrong.
|
|
20
|
-
*
|
|
21
|
-
* Reading the manifest cannot fall behind. A package added tomorrow is covered
|
|
22
|
-
* by an upgrade run today.
|
|
23
|
-
*/
|
|
24
|
-
function kybernesisPackages(deps) {
|
|
25
|
-
return Object.keys(deps)
|
|
26
|
-
.filter((name) => name.startsWith("@kybernesis/"))
|
|
27
|
-
.sort();
|
|
28
|
-
}
|
|
11
|
+
import { confirmEveUpgrade, inspectBuzzSessions, inspectDurableRuns, reconcileEvalScript, } from "./upgrade-sessions.js";
|
|
29
12
|
function versionLt(a, b) {
|
|
30
13
|
const pa = a.split(".").map(Number);
|
|
31
14
|
const pb = b.split(".").map(Number);
|
|
@@ -330,113 +313,329 @@ function repairHostArtifacts(cwd, deps) {
|
|
|
330
313
|
manualCommand: `bash ${shellQuote(serviceScript)} --refresh-unit`,
|
|
331
314
|
});
|
|
332
315
|
}
|
|
333
|
-
|
|
316
|
+
function repairEvalCommand(cwd) {
|
|
317
|
+
const packagePath = join(cwd, "package.json");
|
|
318
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
319
|
+
const result = reconcileEvalScript(pkg.scripts?.eval);
|
|
320
|
+
if (result.kind === "updated") {
|
|
321
|
+
pkg.scripts = { ...pkg.scripts, eval: result.script };
|
|
322
|
+
writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
323
|
+
console.log(` ${green("+")} updated scripts.eval to run through kyb-eval`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (result.kind === "current")
|
|
327
|
+
return;
|
|
328
|
+
const current = result.kind === "custom" ? result.script : "(missing)";
|
|
329
|
+
console.log(` ${yellow("!")} scripts.eval is not a recognized direct eve eval command, so it was left unchanged.\n` +
|
|
330
|
+
` Manual remediation: make this command invoke kyb-eval instead of eve eval, preserving its setup and arguments.\n` +
|
|
331
|
+
` Current scripts.eval: ${dim(JSON.stringify(current))}`);
|
|
332
|
+
}
|
|
333
|
+
function upgradeEnvironment(cwd) {
|
|
334
|
+
const envPath = join(cwd, ".env.local");
|
|
335
|
+
const env = {
|
|
336
|
+
...(existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {}),
|
|
337
|
+
...process.env,
|
|
338
|
+
};
|
|
339
|
+
if (!env.BUZZ_SESSIONS_FILE && !env.BUZZ_KEYFILE) {
|
|
340
|
+
const unit = capture("sh", [
|
|
341
|
+
"-c",
|
|
342
|
+
"grep -ohE 'BUZZ_(SESSIONS_FILE|KEYFILE)=[^ \" ]+' /etc/systemd/system/*buzz-bridge.service 2>/dev/null | head -1",
|
|
343
|
+
]);
|
|
344
|
+
const [name, ...value] = (unit ?? "").trim().split("=");
|
|
345
|
+
if (name && value.length > 0)
|
|
346
|
+
env[name] = value.join("=").replace(/^\"|\"$/g, "");
|
|
347
|
+
}
|
|
348
|
+
return env;
|
|
349
|
+
}
|
|
350
|
+
async function approveEveChange(cwd, yes, hasBuzz) {
|
|
351
|
+
const durable = inspectDurableRuns(cwd);
|
|
352
|
+
console.log(bold(`\nThis will reset ${durable.runningRunIds.length} open conversations`));
|
|
353
|
+
if (durable.issues.length > 0) {
|
|
354
|
+
console.log(` ${yellow("!")} Durable-store inspection was incomplete; the count includes only readable records ` +
|
|
355
|
+
`whose status is exactly "running".`);
|
|
356
|
+
for (const issue of durable.issues)
|
|
357
|
+
console.log(` ${dim(issue)}`);
|
|
358
|
+
}
|
|
359
|
+
if (hasBuzz) {
|
|
360
|
+
const buzz = inspectBuzzSessions(cwd, upgradeEnvironment(cwd), durable.runningRunIds);
|
|
361
|
+
if (buzz.matches.length > 0) {
|
|
362
|
+
console.log(" Buzz-bound open conversations:");
|
|
363
|
+
for (const match of buzz.matches) {
|
|
364
|
+
console.log(` ${match.runId} ${dim(`(${match.community} | ${match.channel})`)}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (buzz.issue) {
|
|
368
|
+
console.log(` ${yellow("!")} Buzz session metadata unavailable: ${buzz.issue}`);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.log(` ${dim("No open durable conversations were matched to Buzz session metadata.")}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const approved = await confirmEveUpgrade({ yes });
|
|
375
|
+
if (!approved) {
|
|
376
|
+
console.log(`\n${red("Eve upgrade cancelled before installation.")} ` +
|
|
377
|
+
`${dim("Pass --yes for deliberate noninteractive use.")}\n`);
|
|
378
|
+
}
|
|
379
|
+
return approved;
|
|
380
|
+
}
|
|
381
|
+
function commandResult(command, args, cwd, print = true) {
|
|
382
|
+
console.log(dim(` $ ${command} ${args.join(" ")}`));
|
|
383
|
+
const result = spawnSync(command, args, { cwd, encoding: "utf8", env: process.env });
|
|
384
|
+
const stdout = result.stdout ?? "";
|
|
385
|
+
const stderr = result.stderr ?? "";
|
|
386
|
+
if (print) {
|
|
387
|
+
if (stdout)
|
|
388
|
+
process.stdout.write(stdout);
|
|
389
|
+
if (stderr)
|
|
390
|
+
process.stderr.write(stderr);
|
|
391
|
+
}
|
|
392
|
+
return { status: result.status ?? 1, stdout, stderr };
|
|
393
|
+
}
|
|
394
|
+
function fail(message) {
|
|
395
|
+
console.error(red(message));
|
|
396
|
+
process.exitCode = 1;
|
|
397
|
+
}
|
|
398
|
+
function majorMinor(version) {
|
|
399
|
+
const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(version);
|
|
400
|
+
if (!match)
|
|
401
|
+
return null;
|
|
402
|
+
const major = Number(match[1]);
|
|
403
|
+
const minor = Number(match[2]);
|
|
404
|
+
return Number.isSafeInteger(major) && Number.isSafeInteger(minor) ? [major, minor] : null;
|
|
405
|
+
}
|
|
406
|
+
function installedPackageValue(cwd, name, expression) {
|
|
407
|
+
const packageName = JSON.stringify(`${name}/package.json`);
|
|
408
|
+
return capture("node", ["-p", `require(${packageName})${expression}`], cwd)?.trim() || null;
|
|
409
|
+
}
|
|
410
|
+
function targetEvePeer(name, version) {
|
|
411
|
+
return capture("npm", ["view", `${name}@${version}`, "peerDependencies.eve"])?.trim() || undefined;
|
|
412
|
+
}
|
|
413
|
+
function dependencyEntries(pkg) {
|
|
414
|
+
const entries = [];
|
|
415
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
416
|
+
const values = pkg[section];
|
|
417
|
+
for (const [name, range] of Object.entries(values ?? {})) {
|
|
418
|
+
if (name === "eve" || name.startsWith("@kybernesis/"))
|
|
419
|
+
entries.push({ name, section, range });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name) || a.section.localeCompare(b.section));
|
|
423
|
+
}
|
|
424
|
+
function rewriteManifest(cwd, pkg, targets) {
|
|
425
|
+
for (const target of targets) {
|
|
426
|
+
const section = pkg[target.section];
|
|
427
|
+
// eve is pinned exactly: the pin IS the certification. A caret let a
|
|
428
|
+
// clean install resolve eve@0.49.1 against a certified 0.49.0 (blind
|
|
429
|
+
// latest, the one thing kyb upgrade exists to prevent).
|
|
430
|
+
section[target.name] = target.name === "eve" ? target.targetVersion : `^${target.targetVersion}`;
|
|
431
|
+
}
|
|
432
|
+
writeFileSync(join(cwd, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
433
|
+
}
|
|
434
|
+
function recoveryGuidance(targets, bridgeUnit) {
|
|
435
|
+
console.log(`\n${yellow("The dependency tree was not verified. Complete the clean install before using it:")}\n` +
|
|
436
|
+
` 1. Ensure every root @kybernesis/* range in package.json is the resolved ^version, and eve is pinned exactly to ${EVE_VERSION}.\n` +
|
|
437
|
+
` 2. ${dim("rm -rf node_modules")}\n` +
|
|
438
|
+
` 3. ${dim("rm -f package-lock.json")}\n` +
|
|
439
|
+
` 4. ${dim("npm install")}\n` +
|
|
440
|
+
` 5. ${dim("npm ls eve")}`);
|
|
441
|
+
const peerChanges = targets.filter((target) => target.name.startsWith("@kybernesis/") &&
|
|
442
|
+
target.installedEvePeer !== target.targetEvePeer &&
|
|
443
|
+
(target.installedEvePeer !== undefined || target.targetEvePeer !== undefined));
|
|
444
|
+
if (peerChanges.length > 0) {
|
|
445
|
+
console.log("\n Published Eve peer metadata changed for these root packages:");
|
|
446
|
+
for (const target of peerChanges) {
|
|
447
|
+
console.log(` ${target.name}: ${target.installedVersion} peers on ${target.installedEvePeer ?? "(none)"}; ` +
|
|
448
|
+
`${target.targetVersion} peers on ${target.targetEvePeer ?? "(none)"}`);
|
|
449
|
+
}
|
|
450
|
+
console.log(dim(" These are metadata facts, not an attribution of npm's reported conflict."));
|
|
451
|
+
}
|
|
452
|
+
if (bridgeUnit) {
|
|
453
|
+
console.log(`\n The Buzz bridge remains stopped until the tree is verified. Then run:\n ${dim(`sudo -n systemctl start ${bridgeUnit}`)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function buzzBridgeUnit(cwd) {
|
|
457
|
+
const systemdDir = process.env.KYB_SYSTEMD_DIR;
|
|
458
|
+
const match = findMatchingAgentServiceUnit(cwd, systemdDir || undefined);
|
|
459
|
+
return match ? `${match.values.name}-buzz-bridge.service` : null;
|
|
460
|
+
}
|
|
461
|
+
function stopActiveBridge(cwd, unit) {
|
|
462
|
+
if (!unit)
|
|
463
|
+
return null;
|
|
464
|
+
const active = commandResult("systemctl", ["is-active", "--quiet", unit], cwd, false);
|
|
465
|
+
if (active.status !== 0)
|
|
466
|
+
return null;
|
|
467
|
+
const stopped = commandResult("sudo", ["-n", "systemctl", "stop", unit], cwd);
|
|
468
|
+
if (stopped.status === 0)
|
|
469
|
+
return unit;
|
|
470
|
+
fail(`Could not stop the active Buzz bridge. Run exactly:\n sudo -n systemctl stop ${unit}`);
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
function startStoppedBridge(cwd, unit) {
|
|
474
|
+
const started = commandResult("sudo", ["-n", "systemctl", "start", unit], cwd);
|
|
475
|
+
if (started.status === 0)
|
|
476
|
+
return true;
|
|
477
|
+
fail(`Could not restart the Buzz bridge. Run exactly:\n` +
|
|
478
|
+
` sudo -n systemctl start ${unit}\n` +
|
|
479
|
+
` systemctl status ${unit}`);
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
export async function upgrade(options = {}) {
|
|
334
483
|
const cwd = process.cwd();
|
|
335
|
-
const
|
|
336
|
-
const
|
|
484
|
+
const packagePath = join(cwd, "package.json");
|
|
485
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
486
|
+
const deps = {
|
|
487
|
+
...(pkg.dependencies ?? {}),
|
|
488
|
+
...(pkg.devDependencies ?? {}),
|
|
489
|
+
};
|
|
337
490
|
console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
|
|
338
491
|
warnIfStale();
|
|
339
|
-
// Read-only: report the compiled effective policy, never rewrite agent/agent.ts.
|
|
340
492
|
reportAgentInputLimit(cwd);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
493
|
+
const entries = dependencyEntries(pkg);
|
|
494
|
+
const installedVersions = new Map();
|
|
495
|
+
const targetVersions = new Map();
|
|
496
|
+
const installedPeers = new Map();
|
|
497
|
+
const targetPeers = new Map();
|
|
498
|
+
const unresolved = new Set();
|
|
499
|
+
for (const name of [...new Set(entries.map((entry) => entry.name))]) {
|
|
500
|
+
const installed = installedPackageValue(cwd, name, ".version");
|
|
501
|
+
if (!installed) {
|
|
502
|
+
console.log(` ${yellow("!")} ${name}: could not resolve installed version`);
|
|
503
|
+
unresolved.add(name);
|
|
347
504
|
continue;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
505
|
+
}
|
|
506
|
+
installedVersions.set(name, installed);
|
|
507
|
+
const target = name === "eve" ? EVE_VERSION : capture("npm", ["view", name, "version"])?.trim();
|
|
508
|
+
if (!target) {
|
|
509
|
+
console.log(` ${yellow("!")} ${name}: could not resolve target version`);
|
|
510
|
+
unresolved.add(name);
|
|
353
511
|
continue;
|
|
354
512
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
toUpgrade.push(`${name}@${latest}`);
|
|
513
|
+
targetVersions.set(name, target);
|
|
514
|
+
if (name.startsWith("@kybernesis/")) {
|
|
515
|
+
installedPeers.set(name, installedPackageValue(cwd, name, ".peerDependencies?.eve || ''") ?? undefined);
|
|
516
|
+
targetPeers.set(name, targetEvePeer(name, target));
|
|
360
517
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// npm latest. Certification happens in the platform repo — packages are
|
|
364
|
-
// tested against a new eve, then the pin in @kybernesis/create advances,
|
|
365
|
-
// then this command carries client agents there behind their eval gate.
|
|
366
|
-
let eveChanged = false;
|
|
367
|
-
const eveInstalled = capture("node", ["-p", "require('eve/package.json').version"], cwd)?.trim();
|
|
368
|
-
const eveLatest = capture("npm", ["view", "eve", "version"])?.trim();
|
|
369
|
-
if (eveInstalled) {
|
|
370
|
-
if (versionLt(eveInstalled, EVE_VERSION)) {
|
|
371
|
-
console.log(` ${yellow("↑")} eve: ${eveInstalled} → ${EVE_VERSION} ${dim("(Kybernesis-certified)")}`);
|
|
372
|
-
toUpgrade.push(`eve@${EVE_VERSION}`);
|
|
373
|
-
eveChanged = true;
|
|
518
|
+
if (installed === target) {
|
|
519
|
+
console.log(` ${green("✓")} ${name}@${installed} ${dim(name === "eve" ? "(certified)" : "(latest)")}`);
|
|
374
520
|
}
|
|
375
|
-
else if (versionLt(EVE_VERSION,
|
|
376
|
-
console.log(` ${yellow("!")} eve@${
|
|
521
|
+
else if (name === "eve" && versionLt(EVE_VERSION, installed)) {
|
|
522
|
+
console.log(` ${yellow("!")} eve@${installed} is AHEAD of the certified ${EVE_VERSION} ${dim("— unsupported territory")}`);
|
|
377
523
|
}
|
|
378
524
|
else {
|
|
379
|
-
console.log(` ${
|
|
380
|
-
|
|
381
|
-
if (eveLatest && versionLt(EVE_VERSION, eveLatest)) {
|
|
382
|
-
console.log(dim(` note: eve@${eveLatest} exists upstream; ${EVE_VERSION} is the newest Kybernesis-certified version.`));
|
|
525
|
+
console.log(` ${yellow("↑")} ${name}: ${installed} → ${target}` +
|
|
526
|
+
(name === "eve" ? ` ${dim("(Kybernesis-certified)")}` : ""));
|
|
383
527
|
}
|
|
384
528
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
`
|
|
393
|
-
repairManageRestart(cwd, deps);
|
|
529
|
+
const eveInstalled = installedVersions.get("eve");
|
|
530
|
+
const eveLatest = capture("npm", ["view", "eve", "version"])?.trim();
|
|
531
|
+
if (eveLatest && versionLt(EVE_VERSION, eveLatest)) {
|
|
532
|
+
console.log(dim(` note: eve@${eveLatest} exists upstream; ${EVE_VERSION} is the newest Kybernesis-certified version.`));
|
|
533
|
+
}
|
|
534
|
+
if (unresolved.size > 0) {
|
|
535
|
+
fail(`\nUpgrade planning failed for ${[...unresolved].sort().join(", ")}. ` +
|
|
536
|
+
`No manifest, dependency tree, or service state was changed.`);
|
|
394
537
|
return;
|
|
395
538
|
}
|
|
396
|
-
|
|
539
|
+
const targets = entries.map((entry) => ({
|
|
540
|
+
name: entry.name,
|
|
541
|
+
section: entry.section,
|
|
542
|
+
currentRange: entry.range,
|
|
543
|
+
installedVersion: installedVersions.get(entry.name),
|
|
544
|
+
targetVersion: targetVersions.get(entry.name),
|
|
545
|
+
installedEvePeer: installedPeers.get(entry.name),
|
|
546
|
+
targetEvePeer: targetPeers.get(entry.name),
|
|
547
|
+
}));
|
|
548
|
+
const eveTarget = targetVersions.get("eve");
|
|
549
|
+
const installedMajorMinor = eveInstalled ? majorMinor(eveInstalled) : null;
|
|
550
|
+
const targetMajorMinor = eveTarget ? majorMinor(eveTarget) : null;
|
|
551
|
+
if ((eveInstalled && !installedMajorMinor) || (eveTarget && !targetMajorMinor)) {
|
|
552
|
+
fail(`Cannot safely compare Eve versions ${JSON.stringify(eveInstalled)} and ${JSON.stringify(eveTarget)}.`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const eveChanged = Boolean(eveInstalled && eveTarget && versionLt(eveInstalled, eveTarget));
|
|
556
|
+
const cleanInstall = Boolean(eveChanged &&
|
|
557
|
+
installedMajorMinor &&
|
|
558
|
+
targetMajorMinor &&
|
|
559
|
+
(installedMajorMinor[0] !== targetMajorMinor[0] || installedMajorMinor[1] !== targetMajorMinor[1]));
|
|
560
|
+
const changed = [...new Set(targets
|
|
561
|
+
.filter((target) => target.installedVersion !== target.targetVersion && !(target.name === "eve" && versionLt(target.targetVersion, target.installedVersion)))
|
|
562
|
+
.map((target) => `${target.name}@${target.targetVersion}`))];
|
|
563
|
+
if (eveChanged && !(await approveEveChange(cwd, options.yes === true, Boolean(deps["@kybernesis/buzz"])))) {
|
|
564
|
+
process.exitCode = 1;
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
repairLocalQueueTimeouts(cwd, deps);
|
|
568
|
+
if (changed.length === 0) {
|
|
397
569
|
console.log(`\n${green("Everything is at latest certified versions.")}\n`);
|
|
398
|
-
// Still repair: being on the right versions is not the same as being set
|
|
399
|
-
// up. An agent can sit at latest for weeks with a capability switched off.
|
|
400
570
|
repairBuzzSetup(cwd, deps);
|
|
401
571
|
repairRemovedDefaultTools(cwd);
|
|
402
572
|
repairSandboxCleanupHooks(cwd, deps);
|
|
403
573
|
repairHostArtifacts(cwd, deps);
|
|
574
|
+
repairEvalCommand(cwd);
|
|
404
575
|
repairManageRestart(cwd, deps);
|
|
405
576
|
return;
|
|
406
577
|
}
|
|
407
|
-
console.log(bold(`\nInstalling: ${
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
578
|
+
console.log(bold(`\nInstalling: ${changed.join(", ")}\n`));
|
|
579
|
+
let stoppedBridge = null;
|
|
580
|
+
if (cleanInstall && deps["@kybernesis/buzz"]) {
|
|
581
|
+
const stopped = stopActiveBridge(cwd, buzzBridgeUnit(cwd));
|
|
582
|
+
if (stopped === false)
|
|
583
|
+
return;
|
|
584
|
+
stoppedBridge = stopped;
|
|
585
|
+
}
|
|
586
|
+
if (cleanInstall) {
|
|
587
|
+
rewriteManifest(cwd, pkg, targets);
|
|
588
|
+
try {
|
|
589
|
+
rmSync(join(cwd, "node_modules"), { recursive: true, force: true });
|
|
590
|
+
rmSync(join(cwd, "package-lock.json"), { force: true });
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
fail(`Could not remove npm resolver state: ${error.message}`);
|
|
594
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// Outside a clean install, eve goes in with --save-exact for the same
|
|
599
|
+
// reason rewriteManifest pins it: npm would save a caret by default.
|
|
600
|
+
const eveChange = changed.find((spec) => spec.startsWith("eve@"));
|
|
601
|
+
const others = changed.filter((spec) => spec !== eveChange);
|
|
602
|
+
const installArgs = cleanInstall ? ["install"] : ["install", ...others];
|
|
603
|
+
let installed = cleanInstall || others.length > 0 ? commandResult("npm", installArgs, cwd) : { status: 0, stdout: "", stderr: "" };
|
|
604
|
+
if (installed.status === 0 && !cleanInstall && eveChange) {
|
|
605
|
+
installed = commandResult("npm", ["install", "--save-exact", eveChange], cwd);
|
|
606
|
+
}
|
|
607
|
+
if (installed.status !== 0) {
|
|
608
|
+
const output = `${installed.stdout}\n${installed.stderr}`;
|
|
609
|
+
if (/ERESOLVE/i.test(output)) {
|
|
610
|
+
console.log(red("\nnpm reported ERESOLVE while installing the planned dependency set."));
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
console.log(red("\nnpm install failed before the dependency tree could be verified."));
|
|
614
|
+
}
|
|
615
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
616
|
+
process.exitCode = 1;
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const validation = commandResult("npm", ["ls", "eve"], cwd);
|
|
620
|
+
if (validation.status !== 0) {
|
|
621
|
+
fail("npm ls eve failed. The installed peer tree is not valid.");
|
|
622
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (stoppedBridge && !startStoppedBridge(cwd, stoppedBridge))
|
|
626
|
+
return;
|
|
428
627
|
repairBuzzSetup(cwd, deps);
|
|
429
628
|
repairRemovedDefaultTools(cwd);
|
|
430
629
|
repairSandboxCleanupHooks(cwd, deps);
|
|
431
630
|
repairHostArtifacts(cwd, deps);
|
|
631
|
+
repairEvalCommand(cwd);
|
|
432
632
|
repairManageRestart(cwd, deps);
|
|
433
633
|
run("npm", ["run", "typecheck"], { cwd });
|
|
434
634
|
if (eveChanged) {
|
|
435
|
-
// A framework bump must also pass discovery/compile, not just types.
|
|
436
635
|
const infoOk = run("npx", ["eve", "info"], { cwd, allowFail: true, quiet: true });
|
|
437
636
|
console.log(infoOk ? green(" ✓ eve discovery clean after framework upgrade") : red(" ✗ eve info failed after framework upgrade — inspect before going further"));
|
|
438
637
|
}
|
|
439
|
-
if (skipEval) {
|
|
638
|
+
if (options.skipEval) {
|
|
440
639
|
console.log(yellow("\nEval gate SKIPPED (--skip-eval). Run `npm run eval` before deploying.\n"));
|
|
441
640
|
return;
|
|
442
641
|
}
|
package/dist/util.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare function run(command: string, args: string[], options?: {
|
|
|
10
10
|
cwd?: string;
|
|
11
11
|
allowFail?: boolean;
|
|
12
12
|
quiet?: boolean;
|
|
13
|
+
/** Extra environment for this one child; the parent's is inherited underneath. */
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
/** Kill the child after this many milliseconds. A command that cannot finish is a failure, not a wait. */
|
|
16
|
+
timeoutMs?: number;
|
|
13
17
|
}): boolean;
|
|
14
18
|
export declare function capture(command: string, args: string[], cwd?: string, extraEnv?: Record<string, string>): string | null;
|
|
15
19
|
export declare function ask(question: string, fallback: string): Promise<string>;
|
package/dist/util.js
CHANGED
|
@@ -15,8 +15,12 @@ export function run(command, args, options) {
|
|
|
15
15
|
const result = spawnSync(command, args, {
|
|
16
16
|
cwd: options?.cwd,
|
|
17
17
|
stdio: options?.quiet ? "pipe" : "inherit",
|
|
18
|
-
env: process.env,
|
|
18
|
+
env: { ...process.env, ...options?.env },
|
|
19
|
+
timeout: options?.timeoutMs,
|
|
19
20
|
});
|
|
21
|
+
if (result.error && result.error.code === "ETIMEDOUT") {
|
|
22
|
+
console.error(red(`Command did not finish within ${Math.round((options?.timeoutMs ?? 0) / 60_000)} min and was stopped: ${command} ${args.join(" ")}`));
|
|
23
|
+
}
|
|
20
24
|
const ok = result.status === 0;
|
|
21
25
|
if (!ok && !options?.allowFail) {
|
|
22
26
|
console.error(red(`Command failed: ${command} ${args.join(" ")}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.6",
|
|
4
4
|
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -1229,7 +1229,7 @@ to `acme-eval` and evals never write into a real brain:
|
|
|
1229
1229
|
|
|
1230
1230
|
```jsonc
|
|
1231
1231
|
"scripts": {
|
|
1232
|
-
"eval": "ARCANA_COMPANY_WORKSPACE=acme-eval ARCANA_DM_WORKSPACE=acme-eval ARCANA_FINANCE_WORKSPACE=acme-eval ARCANA_ENGINEERING_WORKSPACE=acme-eval
|
|
1232
|
+
"eval": "ARCANA_COMPANY_WORKSPACE=acme-eval ARCANA_DM_WORKSPACE=acme-eval ARCANA_FINANCE_WORKSPACE=acme-eval ARCANA_ENGINEERING_WORKSPACE=acme-eval kyb-eval",
|
|
1233
1233
|
"typecheck": "tsc"
|
|
1234
1234
|
}
|
|
1235
1235
|
```
|