@akanjs/devkit 3.0.0-alpha.13 → 3.0.0-alpha.15

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.
@@ -10,6 +10,7 @@ import {
10
10
  renderRecipeEntries,
11
11
  renderScopeAgentBlock,
12
12
  renderScopeAgentsMd,
13
+ renderScopeClaudeMd,
13
14
  upsertAgentBlock,
14
15
  } from "./agentsIndex";
15
16
  import type { RecipeInfo } from "./recipeScanner";
@@ -48,6 +49,15 @@ describe("renderScopeAgentBlock", () => {
48
49
  });
49
50
  });
50
51
 
52
+ describe("renderScopeClaudeMd", () => {
53
+ test("points at AGENTS.md and restates only the comment rule", () => {
54
+ const claude = renderScopeClaudeMd({ type: "app", name: "minimal" });
55
+ expect(claude).toContain("@AGENTS.md");
56
+ expect(claude).toContain("## Comments — Overrides Your Default");
57
+ expect(claude).not.toContain("## Recipes In Scope");
58
+ });
59
+ });
60
+
51
61
  describe("upsertAgentBlock / extractAgentBlock", () => {
52
62
  test("round-trips: fresh file → replace block → extract equals block", () => {
53
63
  const fresh = renderScopeAgentsMd({ type: "app", name: "minimal" }, "OLD");
package/agentsIndex.ts CHANGED
@@ -165,8 +165,28 @@ ${block}
165
165
  ${AGENT_BLOCK_END}
166
166
  `;
167
167
 
168
- /** Claude Code reads nested CLAUDE.md files as it works under a directory; keep it a thin pointer. */
168
+ export const CLAUDE_COMMENT_RULE = `## Comments Overrides Your Default
169
+
170
+ Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
171
+ is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
172
+
173
+ Before typing \`//\`, \`/*\`, or a doc block, ask — **does this sentence carry a fact that is nowhere in the code?**
174
+
175
+ - Restates the identifier, the signature, or the line under it → delete it.
176
+ - Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) → delete it.
177
+ - JSDoc on an ordinary function, or a why/how preamble on ordinary logic → delete it.
178
+ - Explains the edit you just made, for whoever reads the diff → say it in your reply, not in the file.
179
+ - Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
180
+ derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected →
181
+ keep it, one line.
182
+
183
+ That keep-list is exact — \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
184
+ subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
185
+ surrounding code carries none, your diff carries none.`;
186
+
169
187
  export const renderScopeClaudeMd = (scope: AgentsIndexScope): string => `# ${scope.name} — Claude Code Guide
170
188
 
171
189
  @AGENTS.md
190
+
191
+ ${CLAUDE_COMMENT_RULE}
172
192
  `;
package/executors.test.ts CHANGED
@@ -760,6 +760,29 @@ describe("Workspace and app executor environment contracts", () => {
760
760
  expect(offsetMinimalStart.env.AKAN_PUBLIC_CLIENT_PORT).toBe("8286");
761
761
  expect(offsetMinimalStart.env.AKAN_PUBLIC_SERVER_PORT).toBe("8286");
762
762
  });
763
+
764
+ test("pins the start command to development so an ambient NODE_ENV cannot pick the production router", async () => {
765
+ const root = await makeTempRoot();
766
+ process.env.AKAN_PUBLIC_REPO_NAME = "repo";
767
+ process.env.AKAN_PUBLIC_SERVE_DOMAIN = "example.com";
768
+ process.env.AKAN_PUBLIC_ENV = "local";
769
+ await writeJson(path.join(root, "package.json"), rootPackageJson());
770
+ await mkdir(path.join(root, "apps/ambient"), { recursive: true });
771
+ await writeFile(path.join(root, "apps/ambient/akan.config.ts"), "export default {};\n");
772
+
773
+ const originalNodeEnv = process.env.NODE_ENV;
774
+ process.env.NODE_ENV = "production";
775
+ try {
776
+ const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
777
+ const started = await AppExecutor.from(workspace, "ambient").prepareCommand("start");
778
+ expect(started.env.NODE_ENV).toBe("development");
779
+ // The builder runs in this process and bakes NODE_ENV into the dev bundles it emits.
780
+ expect(process.env.NODE_ENV).toBe("development");
781
+ } finally {
782
+ if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
783
+ else process.env.NODE_ENV = originalNodeEnv;
784
+ }
785
+ });
763
786
  });
764
787
 
765
788
  describe("PkgExecutor package generation", () => {
package/executors.ts CHANGED
@@ -233,7 +233,7 @@ export class Executor {
233
233
  );
234
234
  });
235
235
  proc.on("exit", (code, signal) => {
236
- if (!!code || signal)
236
+ if (code || signal)
237
237
  reject(
238
238
  new CommandExecutionError({
239
239
  command,
@@ -342,7 +342,7 @@ export class Executor {
342
342
  );
343
343
  });
344
344
  proc.on("exit", (code, signal) => {
345
- if (!!code || signal)
345
+ if (code || signal)
346
346
  reject(
347
347
  new CommandExecutionError({
348
348
  command: modulePath,
@@ -1364,6 +1364,12 @@ export class AppExecutor extends SysExecutor {
1364
1364
  this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true }),
1365
1365
  ]);
1366
1366
  } else await this.removeDir(".akan");
1367
+ //? `akan start` is the dev server, and it must say so rather than inherit an answer. Bun auto-loads the
1368
+ //? workspace `.env`, so NODE_ENV=production can reach this process without ever being exported in a shell —
1369
+ //? and a dev server that believes it is production serves from the production route cache, whose every
1370
+ //? route throws because a dev artifact carries no routes manifest. Written into `process.env` and not only
1371
+ //? into the child env because the builder runs here and bakes NODE_ENV into the bundles it emits.
1372
+ if (type === "start") process.env.NODE_ENV = "development";
1367
1373
  const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
1368
1374
  const env = this.getCommandEnv({
1369
1375
  AKAN_COMMAND_TYPE: type,
@@ -22,27 +22,25 @@ describe("prepareDevWatchBatch", () => {
22
22
  expect(prepared.event.devPlan?.files).toEqual(prepared.files);
23
23
  });
24
24
 
25
- test.each([
26
- "common",
27
- "srvkit",
28
- "ui",
29
- "webkit",
30
- ])("keeps %s facet add/delete generated index in the same generation", (facet) => {
31
- const root = "/repo";
32
- const changedFile = `${root}/libs/shared/${facet}/tmpExample.ts`;
33
- const generatedIndex = `${root}/libs/shared/${facet}/index.ts`;
34
- const prepared = prepareDevWatchBatch({
35
- generation: 20,
36
- batch: { files: [changedFile], kinds: new Set(["code"]) },
37
- indexSync: { changedFiles: [generatedIndex], errors: [] },
38
- changePlanner: new DevChangePlanner({ workspaceRoot: root }),
39
- });
25
+ test.each(["common", "srvkit", "ui", "webkit"])(
26
+ "keeps %s facet add/delete generated index in the same generation",
27
+ (facet) => {
28
+ const root = "/repo";
29
+ const changedFile = `${root}/libs/shared/${facet}/tmpExample.ts`;
30
+ const generatedIndex = `${root}/libs/shared/${facet}/index.ts`;
31
+ const prepared = prepareDevWatchBatch({
32
+ generation: 20,
33
+ batch: { files: [changedFile], kinds: new Set(["code"]) },
34
+ indexSync: { changedFiles: [generatedIndex], errors: [] },
35
+ changePlanner: new DevChangePlanner({ workspaceRoot: root }),
36
+ });
40
37
 
41
- expect(new Set(prepared.files)).toEqual(new Set([changedFile, generatedIndex]));
42
- expect(prepared.event.devPlan?.generatedFiles).toEqual([generatedIndex]);
43
- expect(prepared.event.devPlan?.roles).toContain("barrel");
44
- expect(prepared.event.devPlan?.actions).toContain("sync-generated");
45
- });
38
+ expect(new Set(prepared.files)).toEqual(new Set([changedFile, generatedIndex]));
39
+ expect(prepared.event.devPlan?.generatedFiles).toEqual([generatedIndex]);
40
+ expect(prepared.event.devPlan?.roles).toContain("barrel");
41
+ expect(prepared.event.devPlan?.actions).toContain("sync-generated");
42
+ },
43
+ );
46
44
 
47
45
  test("marks failed generated index sync as an error generation", () => {
48
46
  const root = "/repo";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "3.0.0-alpha.13",
3
+ "version": "3.0.0-alpha.15",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -45,7 +45,7 @@
45
45
  "@langchain/openai": "^1.4.6",
46
46
  "@tailwindcss/node": "^4.3.0",
47
47
  "@trapezedev/project": "^7.1.4",
48
- "akanjs": "3.0.0-alpha.13",
48
+ "akanjs": "3.0.0-alpha.15",
49
49
  "chalk": "^5.6.2",
50
50
  "commander": "^14.0.3",
51
51
  "dayjs": "^1.11.20",