@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.
- package/agentsIndex.test.ts +10 -0
- package/agentsIndex.ts +21 -1
- package/executors.test.ts +23 -0
- package/executors.ts +8 -2
- package/incrementalBuilder/devWatchBatch.test.ts +18 -20
- package/package.json +2 -2
package/agentsIndex.test.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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.
|
|
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.
|
|
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",
|