@akanjs/devkit 3.1.1-rc.10 → 3.1.1-rc.12

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.
@@ -13,6 +13,18 @@ describe("readProcessRssBytes", () => {
13
13
  expect(rssBytes).toBeLessThan(64 * 1024 * 1024 * 1024);
14
14
  });
15
15
 
16
+ // The builder's own report triggers a recycle and this reading settles it, so the two must agree.
17
+ test.skipIf(process.platform !== "darwin")("reads what the process reports once it frees memory", async () => {
18
+ let held: number[][] | null = Array.from({ length: 64 }, (_, idx) => new Array<number>(500_000).fill(idx + 0.5));
19
+ expect(held.length).toBe(64);
20
+ held = null;
21
+ Bun.gc(true);
22
+ await Bun.sleep(500);
23
+ const rssBytes = await AkanAppHost.readProcessRssBytes(process.pid);
24
+ if (rssBytes === null) throw new Error("expected to read this process's own rss");
25
+ expect(Math.abs(rssBytes - process.memoryUsage.rss())).toBeLessThan(64 * 1024 * 1024);
26
+ });
27
+
16
28
  // A 0 would read as "settled below the ceiling" and cancel a recycle that should happen.
17
29
  test("returns null for a pid that does not exist", async () => {
18
30
  expect(await AkanAppHost.readProcessRssBytes(2_147_483_646)).toBeNull();
@@ -651,6 +651,13 @@ export class AkanAppHost {
651
651
  ["tasklist", "/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"],
652
652
  AkanAppHost.#tasklistRssKb,
653
653
  );
654
+ if (process.platform === "darwin") {
655
+ const footprintBytes = await AkanAppHost.#readRssKbVia(
656
+ ["footprint", "-p", String(pid), "-f", "bytes"],
657
+ AkanAppHost.#footprintKb,
658
+ );
659
+ if (footprintBytes !== null) return footprintBytes;
660
+ }
654
661
  const status = await Bun.file(`/proc/${pid}/status`)
655
662
  .text()
656
663
  .catch(() => null);
@@ -687,6 +694,11 @@ export class AkanAppHost {
687
694
  const memUsage = /"([^"]*)"\s*$/m.exec(output)?.[1];
688
695
  return memUsage ? Number(memUsage.replace(/\D/g, "")) : Number.NaN;
689
696
  }
697
+ // The footprint is what `process.memoryUsage.rss()` reports on macOS; `ps` also counts pages freed for reuse.
698
+ static #footprintKb(output: string): number {
699
+ const bytes = /Footprint: (\d+) B\b/.exec(output)?.[1];
700
+ return bytes ? Number(bytes) / 1024 : Number.NaN;
701
+ }
690
702
  #cancelRssRecycle(): void {
691
703
  this.#rssRecycleReason = null;
692
704
  this.#rssRecycleOver = null;
@@ -317,6 +317,36 @@ describe("AkanAppConfig", () => {
317
317
  expect(() => brokenConfig.getProductionPackageJson()).toThrow("Dependency missing-lib not found");
318
318
  });
319
319
 
320
+ test("carries the root's overrides and resolutions, so the image resolves what the workspace locked", () => {
321
+ const pinned = new AkanAppConfig(
322
+ app,
323
+ [],
324
+ { ...packageJson, overrides: { "js-tokens": "2.0.0" }, resolutions: { ms: "2.0.0" } },
325
+ {},
326
+ baseDevEnv,
327
+ );
328
+ expect(pinned.getProductionPackageJson()).toMatchObject({
329
+ overrides: { "js-tokens": "2.0.0" },
330
+ resolutions: { ms: "2.0.0" },
331
+ });
332
+ const local = new AkanAppConfig(
333
+ app,
334
+ [],
335
+ {
336
+ ...packageJson,
337
+ overrides: { "js-tokens": "2.0.0", patched: "file:./patches/patched", shared: "workspace:*" },
338
+ resolutions: { ms: "link:../ms" },
339
+ },
340
+ {},
341
+ baseDevEnv,
342
+ ).getProductionPackageJson();
343
+ expect(local.overrides).toEqual({ "js-tokens": "2.0.0" });
344
+ expect(local).not.toHaveProperty("resolutions");
345
+ const unpinned = new AkanAppConfig(app, [], packageJson, {}, baseDevEnv).getProductionPackageJson();
346
+ expect(unpinned).not.toHaveProperty("overrides");
347
+ expect(unpinned).not.toHaveProperty("resolutions");
348
+ });
349
+
320
350
  test("falls back to akanjs package versions for built-in runtime dependencies", () => {
321
351
  const runtimeDependencies = {
322
352
  ...akanPackageJson.dependencies,
@@ -776,7 +776,15 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
776
776
  static get akanVersion() {
777
777
  return getAkanPackageJson().version;
778
778
  }
779
+ //? The image installs from the registry alone: a pin to a path or a workspace package resolves nowhere there.
780
+ static #registryPins(pins: Record<string, string> | undefined) {
781
+ if (!pins) return undefined;
782
+ const kept = Object.entries(pins).filter(([, spec]) => !/^(?:file:|link:|workspace:|portal:|[./~])/.test(spec));
783
+ return kept.length ? Object.fromEntries(kept) : undefined;
784
+ }
779
785
  getProductionPackageJson(data: Partial<PackageJson> = {}): PackageJson {
786
+ const overrides = AkanAppConfig.#registryPins(this.rootPackageJson.overrides);
787
+ const resolutions = AkanAppConfig.#registryPins(this.rootPackageJson.resolutions);
780
788
  return {
781
789
  name: this.app.name,
782
790
  description: this.app.name,
@@ -789,6 +797,8 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
789
797
  ]),
790
798
  ),
791
799
  ...(this.trustedDependencies.length ? { trustedDependencies: this.trustedDependencies } : {}),
800
+ ...(overrides ? { overrides } : {}),
801
+ ...(resolutions ? { resolutions } : {}),
792
802
  ...data,
793
803
  };
794
804
  }
@@ -70,6 +70,37 @@ describe("ProductionLockfile", () => {
70
70
  });
71
71
  });
72
72
 
73
+ test("keeps a version an override pinned outside its parent's range", async () => {
74
+ const overriddenLock = `{
75
+ "lockfileVersion": 1,
76
+ "configVersion": 1,
77
+ "workspaces": {
78
+ "": {
79
+ "name": "repo",
80
+ "dependencies": {
81
+ "loose-envify": "^1.4.0",
82
+ },
83
+ },
84
+ },
85
+ "overrides": {
86
+ "js-tokens": "2.0.0",
87
+ },
88
+ "packages": {
89
+ "js-tokens": ["js-tokens@2.0.0", "", {}, "sha512-kk4RjoztHFeAlCNPdTr/uF04oN5z53L18LJfADBQ7YHkO3iR2rP2ZwOH0n33r2FRw0psRmjTDEkOWPE//zmujg=="],
90
+
91
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
92
+ }
93
+ }
94
+ `;
95
+ await withWorkspace(overriddenLock, async (root) => {
96
+ const lock = await ProductionLockfile.prune(root, {
97
+ ...productionPackageJson,
98
+ overrides: { "js-tokens": "2.0.0" },
99
+ });
100
+ expect(lock).toContain('"js-tokens": ["js-tokens@2.0.0"');
101
+ });
102
+ });
103
+
73
104
  test("refuses a workspace without a bun.lock, so the build can fall back to an unlocked install", async () => {
74
105
  await withWorkspace(null, async (root) => {
75
106
  await expect(ProductionLockfile.prune(root, productionPackageJson)).rejects.toThrow(
@@ -13,9 +13,11 @@ export class ProductionLockfile {
13
13
  if (!(await workspaceLock.exists())) throw new Error(`the workspace root has no ${ProductionLockfile.fileName}`);
14
14
  const dir = await mkdtemp(path.join(os.tmpdir(), "akan-production-lock-"));
15
15
  try {
16
+ const npmrc = Bun.file(path.join(workspaceRoot, ".npmrc"));
16
17
  await Promise.all([
17
18
  Bun.write(path.join(dir, "package.json"), JSON.stringify(packageJson, null, 2)),
18
19
  Bun.write(path.join(dir, ProductionLockfile.fileName), workspaceLock),
20
+ ...((await npmrc.exists()) ? [Bun.write(path.join(dir, ".npmrc"), npmrc)] : []),
19
21
  ]);
20
22
  const install = Bun.spawn([process.execPath, "install", "--lockfile-only", "--no-progress"], {
21
23
  cwd: dir,
@@ -114,6 +114,10 @@ export class CodeAgent {
114
114
  profile: options.profile,
115
115
  apps: options.apps ?? [],
116
116
  canSeeImages: akanCodeModelSupportsImages(model),
117
+ model: () => {
118
+ const current = agent.#session?.model;
119
+ return current ? { provider: current.provider, id: current.id } : undefined;
120
+ },
117
121
  deepseek: await DeepSeekWeb.fromRuntime(modelRuntime, !!proxy),
118
122
  depth: options.depth ?? 0,
119
123
  ...(options.subagentSpend ? { subagentSpend: options.subagentSpend } : {}),
@@ -2,11 +2,14 @@ import type { ExtensionAPI, InlineExtension } from "@earendil-works/pi-coding-ag
2
2
  import { type CodeAgentProfile, type CodeAgentSubagent, codeAgentReadOnlyBuiltins } from "akanjs/common";
3
3
  import { Type } from "typebox";
4
4
  import type { Workspace } from "../../commandDecorators";
5
+ import type { CodeAgentModelRef } from "./akanCodeModel";
5
6
 
6
7
  export interface SubagentPoolOptions {
7
8
  workspace: Workspace;
8
9
  cwd: string;
9
10
  parent: CodeAgentProfile;
11
+ /** The parent's model, read when a task starts: a child runs on the model the parent runs on now. */
12
+ model?: () => CodeAgentModelRef | undefined;
10
13
  depth: number;
11
14
  /** What the tree above this pool has spent and has running; a root pool starts its own. */
12
15
  spend?: SubagentSpend;
@@ -164,10 +167,12 @@ export class SubagentPool {
164
167
  const { CodeAgent } = await import("./CodeAgent");
165
168
  const budget = this.#options.parent.tools.subagent;
166
169
  const child = SubagentPool.childOf(this.#options.parent, kind);
170
+ const model = this.#options.model?.();
167
171
  const agent = await CodeAgent.create({
168
172
  workspace: this.#options.workspace,
169
173
  cwd: this.#options.cwd,
170
174
  profile: child,
175
+ ...(model ? { model } : {}),
171
176
  apps: [],
172
177
  depth: this.#options.depth + 1,
173
178
  subagentSpend: this.#spend,
@@ -6,7 +6,7 @@ import { type CodeAgentEvent, type CodeAgentProfile, codeAgentPresets } from "ak
6
6
  import { WorkspaceExecutor } from "../../executors";
7
7
  import { isolateEnv, tempDirs } from "../../testHelpers";
8
8
  import { AkanCodePlugins } from "../tools/AkanCodePlugins";
9
- import { akanCodeDefaultModel } from "./akanCodeModel";
9
+ import { akanCodeDefaultModel, type CodeAgentModelRef } from "./akanCodeModel";
10
10
  import { akanCodePaths } from "./akanCodePaths";
11
11
  import { CodeAgent, type CodeAgentOptions } from "./CodeAgent";
12
12
  import { CodeAgentSuspended } from "./CodeAgentSuspended";
@@ -37,11 +37,14 @@ const usageChunk = (promptTokens: number) =>
37
37
  let llm: ReturnType<typeof Bun.serve>;
38
38
  let llmCalls = 0;
39
39
  let reportedTokens = 0;
40
+ const requestedModels: string[] = [];
40
41
  beforeAll(() => {
41
42
  llm = Bun.serve({
42
43
  port: 0,
43
- fetch: () => {
44
+ fetch: async (request) => {
44
45
  llmCalls += 1;
46
+ const body = (await request.json().catch(() => ({}))) as { model?: string };
47
+ requestedModels.push(body.model ?? "");
45
48
  const usage = reportedTokens ? usageChunk(reportedTokens) : "";
46
49
  return new Response(
47
50
  `${chunk({ role: "assistant", content: "ok" })}${chunk({}, "stop")}${usage}data: [DONE]\n\n`,
@@ -160,6 +163,42 @@ describe("CodeAgent", () => {
160
163
  spy.mockRestore();
161
164
  }
162
165
  });
166
+
167
+ test("a task child sends its requests on the parent's model, and on the one the parent switches to", async () => {
168
+ const root = await makeTempRoot();
169
+ const build = AkanCodePlugins.build;
170
+ let parentModel: (() => CodeAgentModelRef | undefined) | undefined;
171
+ const spy = spyOn(AkanCodePlugins, "build").mockImplementation(async (options) => {
172
+ parentModel = options.model;
173
+ return await build.call(AkanCodePlugins, options);
174
+ });
175
+ const agent = await createAgent(root, { model: { provider: "deepseek", id: "deepseek-v4-pro" } });
176
+ spy.mockRestore();
177
+ const profile = quietProfile(root);
178
+ const pool = SubagentPool.extensionFor({
179
+ workspace: new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" }),
180
+ cwd: root,
181
+ parent: { ...profile, tools: { ...profile.tools, subagent: { maxDepth: 2, maxConcurrent: 3, budget: 100_000 } } },
182
+ ...(parentModel ? { model: parentModel } : {}),
183
+ depth: 0,
184
+ });
185
+ let execute: ((id: string, params: never) => Promise<unknown>) | undefined;
186
+ if (pool && "factory" in pool)
187
+ pool.factory({
188
+ registerTool: (tool: { execute: typeof execute }) => {
189
+ execute = tool.execute;
190
+ },
191
+ } as never);
192
+ try {
193
+ requestedModels.length = 0;
194
+ await execute?.("c1", { description: "d", prompt: "p" } as never);
195
+ await agent.setModel(akanCodeDefaultModel);
196
+ await execute?.("c2", { description: "d", prompt: "p" } as never);
197
+ expect(requestedModels).toEqual(["deepseek-v4-pro", akanCodeDefaultModel.id]);
198
+ } finally {
199
+ agent.dispose();
200
+ }
201
+ });
163
202
  });
164
203
 
165
204
  describe("a sub-agent its parent stops before the sub-agent's run is live", () => {
@@ -85,6 +85,7 @@ Stored under `.akan/code/sessions` — project history, so a second checkout of
85
85
  ## Model, effort, keys
86
86
 
87
87
  - `/model <provider>/<id>`, e.g. `/model deepseek/deepseek-v4-pro`. `/effort <level>` for how hard it thinks.
88
+ A `task` sub-agent starts on the model the session is on at that moment.
88
89
  - A provider is authorized by **`<PROVIDER>_API_KEY`** — `DEEPSEEK_API_KEY`, `ANTHROPIC_API_KEY`. The
89
90
  workspace's own `.env` is read as well as the environment, and a key found there is used for the run and
90
91
  never copied anywhere.
@@ -1,6 +1,7 @@
1
1
  import type { InlineExtension } from "@earendil-works/pi-coding-agent";
2
2
  import type { CodeAgentMcpStatus, CodeAgentProfile, CodeAgentQuestion, CodeAgentSubagent } from "akanjs/common";
3
3
  import type { Workspace } from "../../commandDecorators";
4
+ import type { CodeAgentModelRef } from "../agent/akanCodeModel";
4
5
  import type { CodeMailbox } from "../agent/CodeMailbox";
5
6
  import { SubagentPool, type SubagentSpend } from "../agent/SubagentPool";
6
7
  import { DevLogFeedback } from "../feedback/DevLogFeedback";
@@ -23,6 +24,8 @@ export interface AkanCodePluginsOptions {
23
24
  /** Apps whose dev server should be watched, and whose page should be probed. */
24
25
  apps: string[];
25
26
  canSeeImages: boolean;
27
+ /** The owning agent's model as it is now; a `task` child runs on it. */
28
+ model?: () => CodeAgentModelRef | undefined;
26
29
  deepseek?: DeepSeekWeb | undefined;
27
30
  /** How many `task` levels deep the owning agent is. */
28
31
  depth: number;
@@ -92,6 +95,7 @@ export class AkanCodePlugins {
92
95
  workspace: options.workspace,
93
96
  cwd: options.cwd,
94
97
  parent: options.profile,
98
+ ...(options.model ? { model: options.model } : {}),
95
99
  depth: options.depth,
96
100
  ...(options.subagentSpend ? { spend: options.subagentSpend } : {}),
97
101
  ...(options.onNotice ? { onNotice: options.onNotice } : {}),
@@ -519,6 +519,54 @@ describe("SubagentPool", () => {
519
519
  }
520
520
  });
521
521
 
522
+ test("a child runs on the model its parent runs on when the task starts", async () => {
523
+ const { opened, spy } = openFakeAgents([]);
524
+ let current = { provider: "deepseek", id: "deepseek-v4-pro" };
525
+ try {
526
+ const root = SubagentPool.extensionFor({
527
+ workspace,
528
+ cwd: "/tmp/akan",
529
+ parent: budgeted(1_000),
530
+ model: () => current,
531
+ depth: 0,
532
+ });
533
+ await runTask(root, "c1");
534
+ current = { provider: "openai", id: "gpt-5.5" };
535
+ await runTask(root, "c2");
536
+ expect(opened.map((options) => options.model)).toEqual([
537
+ { provider: "deepseek", id: "deepseek-v4-pro" },
538
+ { provider: "openai", id: "gpt-5.5" },
539
+ ]);
540
+ } finally {
541
+ spy.mockRestore();
542
+ }
543
+ });
544
+
545
+ test("the plugins hand their pool the owning agent's model", async () => {
546
+ const { opened, spy } = openFakeAgents([]);
547
+ try {
548
+ const plugins = await AkanCodePlugins.build({
549
+ workspace,
550
+ cwd: "/tmp/akan",
551
+ profile: budgeted(1_000),
552
+ apps: [],
553
+ canSeeImages: false,
554
+ model: () => ({ provider: "deepseek", id: "deepseek-v4-pro" }),
555
+ depth: 0,
556
+ currentSessionId: () => "",
557
+ mailbox: () => undefined,
558
+ });
559
+ await runTask(
560
+ plugins.extensions.find((extension) => extension.name === "akan-subagent"),
561
+ "c1",
562
+ );
563
+ expect(opened[0]?.model).toEqual({ provider: "deepseek", id: "deepseek-v4-pro" });
564
+ plugins.dispose();
565
+ } finally {
566
+ spy.mockRestore();
567
+ }
568
+ });
569
+
522
570
  test("a child keeps no session and cannot ask a human", () => {
523
571
  const child = SubagentPool.childOf(codeAgentPresets.local("/tmp/akan"), "explore");
524
572
  expect(child.session).toEqual({ store: "memory", crossSession: false });
@@ -23,14 +23,24 @@ describe("WorkspaceVersionCheck", () => {
23
23
  expect(WorkspaceVersionCheck.warning("3.1.1-rc.5", argv("sync", "demo"), await makeWorkspace({}))).toBeNull();
24
24
  });
25
25
 
26
- test("names both versions and the matching CLI with the same arguments", async () => {
26
+ test("names both versions and the matching CLI with the same arguments, quoted for the shell it runs in", async () => {
27
27
  const root = await makeWorkspace({ akanjs: "3.1.0-rc.6" });
28
-
29
- const warning = WorkspaceVersionCheck.warning("3.1.0", argv("sync", "demo", "--title", "it's"), root);
30
-
31
- expect(warning).toContain("@akanjs/cli 3.1.0, but this workspace has akanjs 3.1.0-rc.6");
32
- expect(warning).toContain(`Run the matching CLI: bunx -p @akanjs/cli@3.1.0-rc.6 akan sync demo --title 'it'\\''s'`);
33
- expect(warning).toEndWith("together: akan update");
28
+ const platform = Object.getOwnPropertyDescriptor(process, "platform");
29
+ const warningOn = (os: NodeJS.Platform) => {
30
+ Object.defineProperty(process, "platform", { value: os });
31
+ return WorkspaceVersionCheck.warning("3.1.0", argv("sync", "demo", "--title", "it's"), root);
32
+ };
33
+ try {
34
+ const posix = warningOn("linux");
35
+ expect(posix).toContain("@akanjs/cli 3.1.0, but this workspace has akanjs 3.1.0-rc.6");
36
+ expect(posix).toContain(`Run the matching CLI: bunx -p @akanjs/cli@3.1.0-rc.6 akan sync demo --title 'it'\\''s'`);
37
+ expect(posix).toEndWith("together: akan update");
38
+ expect(warningOn("win32")).toContain(
39
+ `Run the matching CLI: bunx -p @akanjs/cli@3.1.0-rc.6 akan sync demo --title "it's"`,
40
+ );
41
+ } finally {
42
+ if (platform) Object.defineProperty(process, "platform", platform);
43
+ }
34
44
  });
35
45
 
36
46
  test("offers akan update with the CLI's prerelease tag only when the CLI is the newer one", async () => {
@@ -45,6 +45,9 @@ export class WorkspaceVersionCheck {
45
45
  }
46
46
 
47
47
  static #quote(arg: string) {
48
- return /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
48
+ if (/^[\w@%+=:,./-]+$/.test(arg)) return arg;
49
+ //? cmd and PowerShell take neither single quotes nor '\'' as POSIX shells do.
50
+ if (process.platform === "win32") return `"${arg.replaceAll('"', '\\"')}"`;
51
+ return `'${arg.replaceAll("'", `'\\''`)}'`;
49
52
  }
50
53
  }
@@ -61,7 +61,12 @@ describe.skipIf(!CsrE2eHarness.enabled)("SSR dev registry (minimal)", () => {
61
61
  let ssr: CsrE2eHarness;
62
62
 
63
63
  beforeAll(async () => {
64
- ssr = await CsrE2eHarness.start({ app: "minimal", port, env: { AKAN_DEV_SSR_ARM_DELAY_MS: "3000" } });
64
+ ssr = await CsrE2eHarness.start({
65
+ app: "minimal",
66
+ port,
67
+ // The server-log assertions match verbose-level lines, which a workspace `.env` may or may not turn on.
68
+ env: { AKAN_DEV_SSR_ARM_DELAY_MS: "3000", AKAN_PUBLIC_LOG_LEVEL: "verbose" },
69
+ });
65
70
  }, 240_000);
66
71
 
67
72
  afterAll(async () => {
@@ -1,4 +1,5 @@
1
1
  import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
2
+ import { AkanAppHost } from "../akanApp";
2
3
  import { DevGeneratedIndexSync } from "../frontendBuild";
3
4
  import { DevStabilityHarness, type DevStabilityHmrProbe } from "./devStabilityHarness";
4
5
 
@@ -467,7 +468,8 @@ export class FixtureService extends serve("fixture" as const, { serverMode: "bat
467
468
  const harness = await createHarness();
468
469
  const port = await harness.resolvePort();
469
470
  // Child 0's deterministic ws port, held the way an orphaned replica from a killed run would hold it.
470
- const blocker = Bun.serve({ port: port + 10_000, fetch: () => new Response("occupied") });
471
+ //? On the child's own 127.0.0.1: macOS lets a loopback bind share a port a wildcard listener holds.
472
+ const blocker = Bun.serve({ port: port + 10_000, hostname: "127.0.0.1", fetch: () => new Response("occupied") });
471
473
  try {
472
474
  const host = await harness.startHost();
473
475
  await host.waitForLog(/falling back to an ephemeral port/);
@@ -583,9 +585,9 @@ describe("dev resource budgets", () => {
583
585
  expect(await DevStabilityHarness.buildWorkerProcess(host.proc.pid)).toBeNull();
584
586
  });
585
587
 
586
- //? Between a fresh builder and one that has built a route, or the host stops enforcing it or never recycles.
587
- //? macOS keeps the bundler arenas (~380MiB built); Linux measured 101-115MiB fresh, 157-176MiB built and settled.
588
- const CEILING_MB = process.platform === "linux" ? 135 : 200;
588
+ //? Between a fresh builder and one that has built a route, or the host stops enforcing it or never recycles. Linux
589
+ //? measured 101-115MiB fresh, 157-176MiB built and settled; macOS's footprint (Bun 1.4.4) 65MiB fresh, 109MiB built.
590
+ const CEILING_MB = process.platform === "darwin" ? 87 : 135;
589
591
 
590
592
  budgetTest("recycles the builder at an unmeetable ceiling and keeps developing through it", async () => {
591
593
  const harness = await createHarness();
@@ -635,9 +637,11 @@ describe("dev resource budgets", () => {
635
637
  await host.waitForLogSince(mark, /pages-rebundle ok/, WAIT_MS).catch(() => undefined);
636
638
  }
637
639
  const settled = await DevStabilityHarness.builderProcess(host.proc.pid);
640
+ //? Read as the host reads it: on macOS `ps` also counts the pages the builder freed.
641
+ const settledRss = settled ? await AkanAppHost.readProcessRssBytes(settled.pid) : null;
638
642
  const warned = /ceiling costs about one boot build per interval/.test(host.logs.join("").slice(start));
639
643
  //? Past the interval (a slow machine) the next report recycles instead of warning: either answer settles it.
640
- if ((settled?.rssBytes ?? 0) >= CEILING_MB * MB && !warned)
644
+ if ((settledRss ?? 0) >= CEILING_MB * MB && !warned)
641
645
  await host.waitForLogSince(
642
646
  settledFrom,
643
647
  /ceiling costs about one boot build per interval|recycling builder pid=\d+|skipped: the builder fell to/,
@@ -381,7 +381,7 @@ describe("DesktopRelease signer", () => {
381
381
 
382
382
  const windowsTool = (powershell: (out: string) => Promise<number>) =>
383
383
  recordingTool(async (cmd, env) => {
384
- if (cmd[0] === "tar.exe") {
384
+ if (cmd[0] === path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe")) {
385
385
  await Bun.write(path.join(extractedTo(cmd), cmd.at(-1) ?? ""), "MZ");
386
386
  return { code: 0, output: "" };
387
387
  }
@@ -245,7 +245,9 @@ Set-Content -LiteralPath $env:AKAN_SIGNER_OUT -Value $subject -Encoding UTF8`;
245
245
  }
246
246
 
247
247
  static async #subjectOf(run: SignatureTool, archive: string, tmp: string, exe: string) {
248
- await run(["tar.exe", "-xzf", archive, "-C", tmp, exe]);
248
+ //? Windows' own bsdtar: a GNU tar ahead of it on PATH (Git's) takes `C:` for a remote host.
249
+ const tar = path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
250
+ await run([tar, "-xzf", archive, "-C", tmp, exe]);
249
251
  const extracted = path.join(tmp, exe);
250
252
  if (!(await Bun.file(extracted).exists())) return { signer: null, unread: `the archive holds no ${exe}` };
251
253
  const out = path.join(tmp, "signer.txt");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "3.1.1-rc.10",
3
+ "version": "3.1.1-rc.12",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -48,7 +48,7 @@
48
48
  "@earendil-works/pi-tui": "1.3.0",
49
49
  "@inquirer/prompts": "^8.7.3",
50
50
  "@tailwindcss/node": "^4.3.3",
51
- "akanjs": "3.1.1-rc.10",
51
+ "akanjs": "3.1.1-rc.12",
52
52
  "chalk": "^6.0.1",
53
53
  "commander": "^15.0.0",
54
54
  "dayjs": "^1.11.23",
@@ -40,6 +40,9 @@ export class LinuxDockerTarget extends PlatformTestTarget {
40
40
  "run",
41
41
  "-d",
42
42
  "--rm",
43
+ //? An init as PID 1 reaps orphans as a real host does; under `sleep` a killed orphan stays a zombie that still
44
+ //? answers kill(pid, 0).
45
+ "--init",
43
46
  "--name",
44
47
  container,
45
48
  "--cpus",
@@ -110,6 +110,21 @@ export class ItemEndpoint extends endpoint(srv.item, ({ query, mutation }) => ({
110
110
  expect(index.isMutation("apps/third/page/x.tsx", "approveTicket")).toBe(false);
111
111
  });
112
112
 
113
+ test("reads a lib page's call by its own lib's endpoint before another lib's of that name", () => {
114
+ const shared = fileOf(
115
+ "libs/shared/lib/_shared/shared.signal.ts",
116
+ `export class SharedEndpoint extends endpoint(srv.shared, ({ mutation }) => ({ syncFeed: mutation(Boolean, {}).exec(() => true) })) {}`,
117
+ );
118
+ const social = fileOf(
119
+ "libs/social/lib/_social/social.signal.ts",
120
+ `export class SocialEndpoint extends endpoint(srv.social, ({ query }) => ({ syncFeed: query(Boolean, {}).exec(() => true) })) {}`,
121
+ );
122
+ const index = new SignalMutationIndex([shared, social]);
123
+ expect(index.isMutation("libs/social/page/x.tsx", "syncFeed")).toBe(false);
124
+ expect(index.policyIndexOf("libs/social/page/x.tsx", "syncFeed")).toBeNull();
125
+ expect(index.isMutation("libs/shared/page/x.tsx", "syncFeed")).toBe(true);
126
+ });
127
+
113
128
  test("ignores signal tests and specs", () => {
114
129
  const spec = fileOf(
115
130
  "apps/demo/lib/ticket/ticket.signal.spec.ts",
@@ -34,7 +34,7 @@ export class SignalMutationIndex {
34
34
 
35
35
  #shapeOf(consumerFile: string, name: string) {
36
36
  const [root, owner] = consumerFile.split("/");
37
- const own = root === "apps" ? this.#shapesByOwner.get(`apps/${owner}`)?.get(name) : undefined;
37
+ const own = this.#shapesByOwner.get(`${root}/${owner}`)?.get(name);
38
38
  if (own) return own;
39
39
  for (const [key, shapes] of this.#shapesByOwner) {
40
40
  const shape = shapes.get(name);
package/types.ts CHANGED
@@ -12,6 +12,7 @@ export interface PackageJson {
12
12
  optionalDependencies?: Record<string, string>;
13
13
  trustedDependencies?: string[];
14
14
  overrides?: Record<string, string>;
15
+ resolutions?: Record<string, string>;
15
16
  engines?: Record<string, string>;
16
17
  exports?: Record<string, string | Record<string, string>>;
17
18
  bun?: {