@akanjs/cli 3.0.0-alpha.9 → 3.0.0-alpha.90
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/.build-stamp +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/{abstractCompactor-e5yq60qz.js → abstractCompactor-cefxzggj.js} +3 -5
- package/{agent.command-f0gqkfgz.js → agent.command-r93wmq7m.js} +11 -13
- package/{application.command-x903m2qq.js → application.command-5nqfqgdn.js} +62 -38
- package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-b21m07fs.js} +23 -20
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-3wh58n2m.js} +8 -9
- package/buildBatch.proc.js +12 -14
- package/capacitorApp-gzcx6txb.js +12 -0
- package/{cloud.command-zs6kyf83.js → cloud.command-g0pk0k60.js} +15 -18
- package/commandManifest.json +1 -1
- package/{context.command-fmepgjzg.js → context.command-qd8kqrnh.js} +19 -23
- package/dependencyScanner-shs82ynp.js +8 -0
- package/devTui-2xf0meaq.js +552 -0
- package/devTuiView-7jy8f30g.js +9 -0
- package/getDirname-dr3jz1xg.js +7 -0
- package/{guideline.command-n6sw0a0c.js → guideline.command-f38x8jhw.js} +16 -18
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +533 -0
- package/guidelines/conventions/conventions.instruction.md +406 -197
- package/guidelines/cssRule/cssRule.instruction.md +7 -0
- package/guidelines/framework/framework.generate.json +3 -2
- package/guidelines/framework/framework.instruction.md +1 -9
- package/guidelines/mcpRule/mcpRule.generate.json +25 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
- package/guidelines/modelStore/modelStore.instruction.md +12 -0
- package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
- package/guidelines/queryRule/queryRule.generate.json +31 -0
- package/guidelines/queryRule/queryRule.instruction.md +143 -0
- package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +213 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
- package/guidelines/transportRule/transportRule.generate.json +31 -0
- package/guidelines/transportRule/transportRule.instruction.md +60 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +51 -760
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
- package/incrementalBuilder.proc.js +26 -25
- package/{index-774gcx29.js → index-0emb9eat.js} +41 -34
- package/index-3d9y066c.js +76 -0
- package/{index-3vta1tqa.js → index-3vvbv974.js} +32 -32
- package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
- package/{index-y4vkyhvv.js → index-5xdqxbn9.js} +14 -14
- package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
- package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
- package/{index-jhpa13zj.js → index-9bwhxrqf.js} +57 -57
- package/{index-bjpxzr6s.js → index-9wtbw98m.js} +6 -3
- package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
- package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
- package/{index-kdh76j26.js → index-cezy7yb3.js} +100 -32
- package/{index-62955kn5.js → index-d61rcfa2.js} +758 -127
- package/{index-82jrh4hj.js → index-deekbtkj.js} +13 -13
- package/{index-rxkpk7n4.js → index-e82dyeqy.js} +4 -4
- package/{index-qzdjd4ct.js → index-h759vjfa.js} +18 -18
- package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
- package/{index-qb61h0k7.js → index-jydtyzva.js} +1098 -1360
- package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
- package/{index-4m0bphac.js → index-kfnr6c19.js} +31 -30
- package/{index-xmmzf150.js → index-mpmyqm1y.js} +1 -1
- package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
- package/{index-0cj2zxbm.js → index-n85fer99.js} +12 -11
- package/{index-779ske1y.js → index-pgsswppj.js} +27 -27
- package/index-pvb4vdwr.js +159 -0
- package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
- package/{index-z8ptfkzv.js → index-q8sn827n.js} +969 -240
- package/{index-5sn5w2mt.js → index-r50qen0z.js} +94 -61
- package/{index-eyyf2149.js → index-rntcdss1.js} +36 -37
- package/{index-gcv8ncfh.js → index-sjak5v5b.js} +16 -12
- package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
- package/{index-jf2ya7r2.js → index-y74k5anj.js} +8 -8
- package/{index-d43t8af2.js → index-zkvpaggp.js} +58 -25
- package/index.js +22 -25
- package/{library.command-cv9gdbmg.js → library.command-em103wtn.js} +11 -10
- package/{localRegistry.command-d8fg36zc.js → localRegistry.command-gz8apn4y.js} +20 -23
- package/{module.command-qyr1s9cn.js → module.command-451pztkc.js} +13 -16
- package/{package.command-wsjx0k47.js → package.command-rfhyf38t.js} +8 -10
- package/package.json +5 -7
- package/{page.command-rdzg65rm.js → page.command-kt19da5g.js} +7 -9
- package/{primitive.command-89g38b53.js → primitive.command-pgy69ere.js} +13 -16
- package/{quality.command-0cs2z92d.js → quality.command-gpe2vqb5.js} +226 -105
- package/recipeScanner-fpagtgc1.js +11 -0
- package/{repair.command-6fphxfwj.js → repair.command-2sbrz187.js} +12 -14
- package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
- package/{scalar.command-xfer69s6.js → scalar.command-jcf4e5sv.js} +11 -14
- package/subspace.command-kqzqtk0p.js +755 -0
- package/templates/app/env/env.client.debug.ts.template +1 -5
- package/templates/app/env/env.client.develop.ts.template +1 -5
- package/templates/app/env/env.client.local.ts.template +1 -5
- package/templates/app/env/env.client.main.ts.template +1 -5
- package/templates/app/env/env.client.testing.ts.template +1 -5
- package/templates/app/env/env.client.type.ts +2 -6
- package/templates/app/env/env.server.debug.ts.template +1 -5
- package/templates/app/env/env.server.develop.ts.template +1 -5
- package/templates/app/env/env.server.local.ts.template +1 -5
- package/templates/app/env/env.server.main.ts.template +1 -5
- package/templates/app/main.ts +3 -1
- package/templates/appSample/lib/task/task.signal.ts +11 -6
- package/templates/appSample/srvkit/AuthGuard.ts +9 -5
- package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
- package/templates/crudPages/[__model__Id]/edit/page.tsx +5 -5
- package/templates/crudPages/[__model__Id]/page.tsx +10 -13
- package/templates/crudPages/new/page.tsx +5 -5
- package/templates/crudPages/page.tsx +9 -9
- package/templates/crudSinglePage/page.tsx +11 -7
- package/templates/env/_env.server.type.ts +7 -5
- package/templates/lib/db.ts +1 -1
- package/templates/lib/dict.ts +1 -1
- package/templates/lib/sig.ts +1 -1
- package/templates/lib/srv.ts +6 -2
- package/templates/module/__model__.dictionary.ts +4 -1
- package/templates/module/__model__.signal.ts +2 -3
- package/templates/module/__model__.store.ts +1 -2
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
- package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
- package/templates/workspaceRoot/AGENTS.md.template +4 -0
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +2 -1
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
- package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
- package/templates/workspaceRoot/package.json.template +2 -4
- package/typeChecker-3xgrzc2v.js +7 -0
- package/typecheck.proc.js +4 -5
- package/{workflow.command-2ygfqt4f.js → workflow.command-m4z25a2p.js} +22 -25
- package/{workspace.command-88m0zs64.js → workspace.command-dqgj9zbk.js} +96 -81
- package/capacitorApp-9xswcgx7.js +0 -61
- package/dependencyScanner-f6grrep3.js +0 -9
- package/getDirname-m9s0cmhq.js +0 -8
- package/index-mxvakhsm.js +0 -66
- package/index-r24hmh0q.js +0 -4
- package/recipeScanner-tghkzrrt.js +0 -12
- package/typeChecker-kravn7ns.js +0 -8
- /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
- /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
|
@@ -5,12 +5,8 @@ interface Dict {
|
|
|
5
5
|
}
|
|
6
6
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict = {}) {
|
|
7
7
|
return `
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export type AppClientEnv = ClientEnv & {
|
|
11
|
-
cloudflare?: {
|
|
12
|
-
siteKey: string;
|
|
13
|
-
};
|
|
8
|
+
export type AppClientEnv = {
|
|
9
|
+
// apply public envs here
|
|
14
10
|
};
|
|
15
11
|
`;
|
|
16
12
|
}
|
package/templates/app/main.ts
CHANGED
|
@@ -5,7 +5,9 @@ interface Dict {
|
|
|
5
5
|
}
|
|
6
6
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
7
7
|
return `
|
|
8
|
-
|
|
8
|
+
// Not the \`akanjs/server\` barrel: it re-exports AkanServer, whose graph the gateway never runs. Through the
|
|
9
|
+
// barrel this process evaluates 35MB of SSR renderer and DB driver to spawn children and relay bytes.
|
|
10
|
+
import { AkanApp } from "akanjs/server/akanApp";
|
|
9
11
|
|
|
10
12
|
const run = async () => {
|
|
11
13
|
await new AkanApp().start();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
2
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
-
return `import {
|
|
4
|
+
return `import { SignedIn } from "@apps/${dict.appName}/srvkit";
|
|
5
|
+
import { endpoint, internal, None, Public, slice } from "akanjs/signal";
|
|
5
6
|
|
|
6
7
|
import * as cnst from "../cnst";
|
|
7
8
|
import * as srv from "../srv";
|
|
@@ -21,20 +22,24 @@ import * as srv from "../srv";
|
|
|
21
22
|
|
|
22
23
|
export class TaskInternal extends internal(srv.task, ({ interval }) => ({})) {}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
// Guards, per key: root = the generated admin API (runs any filter with caller-supplied args), get = reads,
|
|
26
|
+
// cru = create/update/remove. root and cru are never Public; None here means "nobody until you name a guard".
|
|
27
|
+
export class TaskSlice extends slice(srv.task, { guards: { root: None, get: Public, cru: SignedIn } }, (init) => ({
|
|
25
28
|
inPublic: init().exec(function () {
|
|
26
29
|
return this.taskService.queryAny();
|
|
27
30
|
}),
|
|
28
31
|
})) {}
|
|
29
32
|
|
|
33
|
+
// Every custom endpoint names its own guards array — the slice's map above only covers generated CRUD.
|
|
34
|
+
// It is also the MCP decision: an endpoint declaring no guard is not published to agents at all.
|
|
30
35
|
export class TaskEndpoint extends endpoint(srv.task, ({ mutation }) => ({
|
|
31
|
-
startTask: mutation(cnst.Task)
|
|
36
|
+
startTask: mutation(cnst.Task, { guards: [SignedIn] })
|
|
32
37
|
.param("taskId", String)
|
|
33
38
|
.exec(async function (taskId) {
|
|
34
39
|
return await this.taskService.startTask(taskId);
|
|
35
40
|
}),
|
|
36
41
|
|
|
37
|
-
completeTask: mutation(cnst.Task)
|
|
42
|
+
completeTask: mutation(cnst.Task, { guards: [SignedIn] })
|
|
38
43
|
.param("taskId", String)
|
|
39
44
|
.exec(async function (taskId) {
|
|
40
45
|
return await this.taskService.completeTask(taskId);
|
|
@@ -48,7 +53,7 @@ export class TaskEndpoint extends endpoint(srv.task, ({ mutation }) => ({
|
|
|
48
53
|
// taskEvent: pubsub(cnst.Task).exec(...),
|
|
49
54
|
// // search: search parameters (optional, URL query string)
|
|
50
55
|
// searchTask: query(cnst.LightTask).search("keyword", String).exec(...),
|
|
51
|
-
// //
|
|
52
|
-
//
|
|
56
|
+
// // mcp: false keeps a guarded endpoint off the agent shelf without changing its authorization
|
|
57
|
+
// startTask: mutation(cnst.Task, { guards: [SignedIn], mcp: false })...
|
|
53
58
|
`;
|
|
54
59
|
}
|
|
@@ -3,7 +3,7 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "AuthGuard.ts",
|
|
6
|
-
content: `import type { Guard, SignalContext } from "akanjs/signal";
|
|
6
|
+
content: `import type { Guard, GuardScope, SignalContext } from "akanjs/signal";
|
|
7
7
|
|
|
8
8
|
// ===== AuthGuard.ts =====
|
|
9
9
|
// Convention: srvkit/ folder — server-only helpers, cannot import from client code.
|
|
@@ -14,10 +14,14 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
14
14
|
|
|
15
15
|
export class SignedIn implements Guard {
|
|
16
16
|
static name = "SignedIn";
|
|
17
|
+
// "account" — the verdict reads the caller and nothing about the call, so an MCP catalogue can evaluate it with
|
|
18
|
+
// no arguments and hide what this caller certainly cannot use. Required: "resource" is never evaluated for a
|
|
19
|
+
// listing, so a guard like this one marked that way would filter nothing.
|
|
20
|
+
static scope: GuardScope = "account";
|
|
17
21
|
|
|
18
22
|
canPass(context: SignalContext): boolean {
|
|
19
|
-
|
|
20
|
-
return !!
|
|
23
|
+
// Reads the account the middleware resolved, so the same guard answers over HTTP, a websocket and MCP alike.
|
|
24
|
+
return !!context.get<{ id?: string }>("account");
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -25,9 +29,9 @@ export class SignedIn implements Guard {
|
|
|
25
29
|
// Role-based guard: only allow users with a specific role
|
|
26
30
|
// export class IsAdmin implements Guard {
|
|
27
31
|
// static name = "IsAdmin";
|
|
32
|
+
// static scope: GuardScope = "account";
|
|
28
33
|
// canPass(context: SignalContext): boolean {
|
|
29
|
-
//
|
|
30
|
-
// return user?.role === "admin";
|
|
34
|
+
// return context.get<{ role?: string }>("account")?.role === "admin";
|
|
31
35
|
// }
|
|
32
36
|
// }
|
|
33
37
|
`,
|
|
@@ -15,8 +15,9 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
15
15
|
|
|
16
16
|
export class CurrentUserId implements InternalArg<string | null> {
|
|
17
17
|
getArg(context: SignalContext): string | null {
|
|
18
|
-
|
|
19
|
-
return
|
|
18
|
+
// \`context.get\` reads what the account middleware resolved, on every transport. Branching on
|
|
19
|
+
// \`getHttpContext()\` would return null for the same caller arriving over a websocket or MCP.
|
|
20
|
+
return context.get<{ id?: string }>("account")?.id ?? null;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -24,7 +25,7 @@ export class CurrentUserId implements InternalArg<string | null> {
|
|
|
24
25
|
// Inject current session language/locale
|
|
25
26
|
// export class CurrentLocale implements InternalArg<string> {
|
|
26
27
|
// getArg(context: SignalContext): string {
|
|
27
|
-
// return context.
|
|
28
|
+
// return context.get<string>("locale") ?? "en";
|
|
28
29
|
// }
|
|
29
30
|
// }
|
|
30
31
|
`,
|
|
@@ -9,9 +9,9 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
9
9
|
return {
|
|
10
10
|
filename: "_index.tsx",
|
|
11
11
|
content: `
|
|
12
|
-
import {
|
|
13
|
-
import { fetch, usePage, ${dict.Model} } from "@apps/${dict.appName}/client";
|
|
12
|
+
import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
|
|
14
13
|
import type { PageConfig } from "akanjs/client";
|
|
14
|
+
import { Load } from "akanjs/ui";
|
|
15
15
|
|
|
16
16
|
interface PageProps {
|
|
17
17
|
params: { ${dict.model}Id: string };
|
|
@@ -20,11 +20,11 @@ interface PageProps {
|
|
|
20
20
|
export default async function Page({ params }: PageProps) {
|
|
21
21
|
const { l } = usePage();
|
|
22
22
|
const { ${dict.model}Id } = params;
|
|
23
|
-
const { ${dict.model}
|
|
23
|
+
const [{ ${dict.model}Edit }] = await Promise.all([fetch.edit${dict.Model}(${dict.model}Id)]);
|
|
24
24
|
return (
|
|
25
25
|
<div className="container">
|
|
26
|
-
<div className="
|
|
27
|
-
<div className="
|
|
26
|
+
<div className="m-4 mt-8 flex justify-between">
|
|
27
|
+
<div className="flex items-center gap-2 text-primary text-xl">
|
|
28
28
|
{l("base.updateModel", { model: l("${dict.model}.modelName") })}
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
@@ -10,37 +10,34 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
10
10
|
filename: "_index.tsx",
|
|
11
11
|
content: `
|
|
12
12
|
import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
|
|
13
|
-
import { Link } from "akanjs/ui";
|
|
14
13
|
import type { PageConfig } from "akanjs/client";
|
|
14
|
+
import { buttonRecipe, Link } from "akanjs/ui";
|
|
15
15
|
|
|
16
16
|
interface PageProps {
|
|
17
17
|
params: { ${dict.model}Id: string };
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export
|
|
20
|
+
export function generateHead({ params }: PageProps) {
|
|
21
21
|
const { ${dict.model}Id } = params;
|
|
22
|
-
const { ${dict.model} } = await fetch.view${dict.Model}(${dict.model}Id);
|
|
23
22
|
return (
|
|
24
23
|
<>
|
|
25
|
-
<title>{${dict.model}
|
|
26
|
-
<meta name="description" content={${dict.model}
|
|
27
|
-
<meta property="og:title" content={${dict.model}
|
|
28
|
-
<meta property="og:description" content={${dict.model}
|
|
24
|
+
<title>{${dict.model}Id}</title>
|
|
25
|
+
<meta name="description" content={${dict.model}Id} />
|
|
26
|
+
<meta property="og:title" content={${dict.model}Id} />
|
|
27
|
+
<meta property="og:description" content={${dict.model}Id} />
|
|
29
28
|
</>
|
|
30
29
|
);
|
|
31
30
|
}
|
|
32
31
|
export default async function Page({ params }: PageProps) {
|
|
33
32
|
const { l } = usePage();
|
|
34
33
|
const { ${dict.model}Id } = params;
|
|
35
|
-
const { ${dict.model}, ${dict.model}View } = await fetch.view${dict.Model}(${dict.model}Id);
|
|
34
|
+
const [{ ${dict.model}, ${dict.model}View }] = await Promise.all([fetch.view${dict.Model}(${dict.model}Id)]);
|
|
36
35
|
return (
|
|
37
36
|
<div className="container flex flex-col gap-4">
|
|
38
|
-
<div className="flex gap-4 font-bold text-lg
|
|
37
|
+
<div className="flex items-center gap-4 font-bold text-lg">
|
|
39
38
|
<${dict.Model}.Zone.View view={${dict.model}View} />
|
|
40
|
-
<Link href={\`/${dict.model}/\${${dict.model}.id}/edit\`}>
|
|
41
|
-
|
|
42
|
-
{l("base.updateModel", { model: l("${dict.model}.modelName") })}
|
|
43
|
-
</button>
|
|
39
|
+
<Link className={buttonRecipe()} href={\`/${dict.model}/\${${dict.model}.id}/edit\`}>
|
|
40
|
+
{l("base.updateModel", { model: l("${dict.model}.modelName") })}
|
|
44
41
|
</Link>
|
|
45
42
|
</div>
|
|
46
43
|
</div>
|
|
@@ -9,17 +9,17 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
9
9
|
return {
|
|
10
10
|
filename: "_index.tsx",
|
|
11
11
|
content: `
|
|
12
|
-
import {
|
|
13
|
-
import { cnst, fetch, usePage, ${dict.Model} } from "@apps/${dict.appName}/client";
|
|
12
|
+
import { type cnst, fetch, usePage, ${dict.Model} } from "@apps/${dict.appName}/client";
|
|
14
13
|
import type { PageConfig } from "akanjs/client";
|
|
14
|
+
import { Load } from "akanjs/ui";
|
|
15
15
|
|
|
16
16
|
export default function Page() {
|
|
17
17
|
const { l } = usePage();
|
|
18
18
|
const ${dict.model}Form: Partial<cnst.${dict.Model}> = {};
|
|
19
19
|
return (
|
|
20
20
|
<div className="container">
|
|
21
|
-
<div className="
|
|
22
|
-
<div className="
|
|
21
|
+
<div className="m-4 mt-8 flex justify-between">
|
|
22
|
+
<div className="flex items-center gap-2 text-primary text-xl">
|
|
23
23
|
+ {l("base.createModel", { model: l("${dict.model}.modelName") })}
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
@@ -37,6 +37,6 @@ export default function Page() {
|
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
export const pageConfig = { transition: "none" } satisfies PageConfig;
|
|
40
|
-
|
|
40
|
+
`,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
@@ -10,26 +10,26 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
10
10
|
filename: "_index.tsx",
|
|
11
11
|
content: `
|
|
12
12
|
import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
|
|
13
|
-
import { Link } from "akanjs/ui";
|
|
14
13
|
import type { PageConfig } from "akanjs/client";
|
|
14
|
+
import { buttonRecipe, Link } from "akanjs/ui";
|
|
15
15
|
|
|
16
16
|
export default async function Page() {
|
|
17
17
|
const { l } = usePage();
|
|
18
|
-
const { ${dict.model}InitInPublic } = await fetch.init${dict.Model}InPublic();
|
|
18
|
+
const [{ ${dict.model}InitInPublic }] = await Promise.all([fetch.init${dict.Model}InPublic()]);
|
|
19
19
|
return (
|
|
20
20
|
<div className="container flex flex-col gap-4">
|
|
21
|
-
<div className="
|
|
21
|
+
<div className="mt-5 h-full w-full px-5">
|
|
22
22
|
<div>{l("${dict.model}.modelName")}</div>
|
|
23
|
-
<div className="animate-fadeIn
|
|
23
|
+
<div className="flex animate-fadeIn items-center gap-4 px-4 pt-4">
|
|
24
24
|
<div className="font-bold text-lg md:text-4xl">${dict.Model}s</div>
|
|
25
|
-
<Link href={
|
|
26
|
-
|
|
25
|
+
<Link className={buttonRecipe()} href="/${dict.model}/new">
|
|
26
|
+
+ {l("base.createModel", { model: l("${dict.model}.modelName") })}
|
|
27
27
|
</Link>
|
|
28
28
|
</div>
|
|
29
29
|
<div>{l("${dict.model}.modelDesc")}</div>
|
|
30
|
-
<div className="
|
|
30
|
+
<div className="mt-3 flex gap-4 px-6">
|
|
31
31
|
<${dict.Model}.Zone.Card
|
|
32
|
-
className="animate-fadeIn grid
|
|
32
|
+
className="grid w-full animate-fadeIn grid-cols-1 justify-center gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
|
33
33
|
init={${dict.model}InitInPublic}
|
|
34
34
|
/>
|
|
35
35
|
</div>
|
|
@@ -38,6 +38,6 @@ export default async function Page() {
|
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
export const pageConfig = { transition: "none" } satisfies PageConfig;
|
|
41
|
-
|
|
41
|
+
`,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
@@ -9,23 +9,27 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
9
9
|
return {
|
|
10
10
|
filename: "_index.tsx",
|
|
11
11
|
content: `
|
|
12
|
-
import type { PageConfig } from "akanjs/client";
|
|
13
|
-
import { Model } from "akanjs/ui";
|
|
14
12
|
import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
|
|
13
|
+
import type { PageConfig } from "akanjs/client";
|
|
14
|
+
import { buttonRecipe, Model } from "akanjs/ui";
|
|
15
15
|
|
|
16
16
|
export default async function Page() {
|
|
17
17
|
const { l } = usePage();
|
|
18
|
-
const { ${dict.model}InitInPublic } = await fetch.init${dict.Model}InPublic();
|
|
18
|
+
const [{ ${dict.model}InitInPublic }] = await Promise.all([fetch.init${dict.Model}InPublic()]);
|
|
19
19
|
return (
|
|
20
20
|
<>
|
|
21
|
-
<div className="animate-fadeIn
|
|
22
|
-
<div className="text-lg
|
|
23
|
-
<Model.New
|
|
21
|
+
<div className="flex animate-fadeIn items-center gap-4 px-4 pt-4">
|
|
22
|
+
<div className="font-bold text-lg md:text-4xl">{l("${dict.model}.modelName")}</div>
|
|
23
|
+
<Model.New
|
|
24
|
+
className={buttonRecipe({ size: "sm", variant: "ghost" })}
|
|
25
|
+
slice={fetch.slice.${dict.model}InPublic}
|
|
26
|
+
renderTitle="id"
|
|
27
|
+
>
|
|
24
28
|
<${dict.Model}.Template.General />
|
|
25
29
|
</Model.New>
|
|
26
30
|
</div>
|
|
27
31
|
<${dict.Model}.Zone.Card
|
|
28
|
-
className="
|
|
32
|
+
className="mt-2 grid w-full animate-fadeIn grid-cols-1 justify-center gap-4 md:grid-cols-2 xl:grid-cols-3"
|
|
29
33
|
init={${dict.model}InitInPublic}
|
|
30
34
|
/>
|
|
31
35
|
</>
|
|
@@ -5,12 +5,14 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [
|
|
|
5
5
|
const libs = scanInfo.getLibs();
|
|
6
6
|
return {
|
|
7
7
|
filename: "env.server.type.ts",
|
|
8
|
-
content:
|
|
9
|
-
|
|
8
|
+
content: libs.length
|
|
9
|
+
? `
|
|
10
|
+
${libs.map((lib) => `import { env as ${lib}Option } from "@libs/${lib}/server";`).join("\n")}
|
|
10
11
|
|
|
11
12
|
export const libEnv = {
|
|
12
|
-
${libs.
|
|
13
|
-
}
|
|
14
|
-
|
|
13
|
+
${libs.map((lib) => ` ...${lib}Option,`).join("\n")}
|
|
14
|
+
};`
|
|
15
|
+
: `
|
|
16
|
+
export const libEnv = {};`,
|
|
15
17
|
};
|
|
16
18
|
}
|
package/templates/lib/db.ts
CHANGED
|
@@ -13,7 +13,7 @@ ${scalarModules.map((module) => `import { ${capitalize(module)} } from "./__scal
|
|
|
13
13
|
${databaseModules.length ? `import * as cnst from "./cnst";` : ""}
|
|
14
14
|
${databaseModules.map((module) => `import * as ${module}Db from "./${module}/${module}.document";`).join("\n")}
|
|
15
15
|
|
|
16
|
-
${libs.map((lib) => `export
|
|
16
|
+
${libs.map((lib) => `export * as ${lib} from "@libs/${lib}/lib/db";`).join("\n")}
|
|
17
17
|
|
|
18
18
|
${databaseModules.map((module) => `class ${capitalize(module)}Input extends by(cnst.${capitalize(module)}Input) {}`).join("\n")}
|
|
19
19
|
${databaseModules.map((module) => `class ${capitalize(module)}Insight extends by(cnst.${capitalize(module)}Insight) {}`).join("\n")}
|
package/templates/lib/dict.ts
CHANGED
|
@@ -9,7 +9,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [
|
|
|
9
9
|
const libs = scanInfo.getLibs();
|
|
10
10
|
return `
|
|
11
11
|
import { makeDictionary, makeTrans, registerScalarTrans, registerServiceTrans, registerModelTrans${libs.length === 0 ? `, dictionary as base` : ""} } from "akanjs/dictionary";
|
|
12
|
-
${libs.length ? libs.map((lib) => `import { dictionary as ${lib} } from "@libs/${lib}/
|
|
12
|
+
${libs.length ? libs.map((lib) => `import { dictionary as ${lib} } from "@libs/${lib}/lib/dict";`).join("\n") : ""}
|
|
13
13
|
|
|
14
14
|
${databaseModules.map((module) => `import * as ${module} from "./${module}/${module}.dictionary";`).join("\n")}
|
|
15
15
|
${serviceModules.map((module) => `import * as ${module} from "./_${module}/${module}.dictionary";`).join("\n")}
|
package/templates/lib/sig.ts
CHANGED
|
@@ -17,7 +17,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [
|
|
|
17
17
|
return `
|
|
18
18
|
import { FetchClient, type FetchClientType } from "akanjs/fetch";
|
|
19
19
|
import { SignalRegistry, serverSignal${libs.length === 0 ? ", fetch as base" : ""} } from "akanjs/signal";
|
|
20
|
-
${libs.map((lib) => `import { fetch as ${lib} } from "@libs/${lib}/
|
|
20
|
+
${libs.map((lib) => `import { fetch as ${lib} } from "@libs/${lib}/lib/sig";`).join("\n")}
|
|
21
21
|
|
|
22
22
|
${[...scanInfo.database.entries()]
|
|
23
23
|
.filter(([_, files]) => files.has("signal"))
|
package/templates/lib/srv.ts
CHANGED
|
@@ -13,7 +13,11 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [
|
|
|
13
13
|
const libs = scanInfo.getLibs();
|
|
14
14
|
return `
|
|
15
15
|
import { ServiceModel } from "akanjs/service";
|
|
16
|
-
${
|
|
16
|
+
${
|
|
17
|
+
libs.length
|
|
18
|
+
? libs.map((lib) => `import * as ${lib} from "@libs/${lib}/lib/option";`).join("\n")
|
|
19
|
+
: 'import type { BackendEnv } from "akanjs/base";'
|
|
20
|
+
}
|
|
17
21
|
|
|
18
22
|
import * as cnst from "./cnst";
|
|
19
23
|
import * as db from "./db";
|
|
@@ -21,7 +25,7 @@ import * as db from "./db";
|
|
|
21
25
|
${databaseModules.map((module) => `import { ${capitalize(module)}Service } from "./${module}/${module}.service";`).join("\n")}
|
|
22
26
|
${serviceModules.map((module) => `import { ${capitalize(module)}Service } from "./_${module}/${module}.service";`).join("\n")}
|
|
23
27
|
|
|
24
|
-
${libs.map((lib) => `export
|
|
28
|
+
${libs.map((lib) => `export * as ${lib} from "@libs/${lib}/lib/srv";`).join("\n")}
|
|
25
29
|
|
|
26
30
|
${databaseModules.map((module) => `export { ${capitalize(module)}Service } from "./${module}/${module}.service";`).join("\n")}
|
|
27
31
|
${serviceModules.map((module) => `export { ${capitalize(module)}Service } from "./_${module}/${module}.service";`).join("\n")}
|
|
@@ -18,6 +18,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
18
18
|
import { modelDictionary } from "akanjs/dictionary";
|
|
19
19
|
|
|
20
20
|
import type { ${dict.Model}, ${dict.Model}Insight } from "./${dict.model}.constant";
|
|
21
|
+
import type { ${dict.Model}Filter } from "./${dict.model}.document";
|
|
21
22
|
import type { ${dict.Model}Endpoint, ${dict.Model}Slice } from "./${dict.model}.signal";
|
|
22
23
|
|
|
23
24
|
export const dictionary = modelDictionary(["en", "ko"])
|
|
@@ -25,9 +26,11 @@ export const dictionary = modelDictionary(["en", "ko"])
|
|
|
25
26
|
t(["${modelLabelEn}", "${modelLabelKo}"]).desc(["${modelDescEn}", "${modelDescKo}"])
|
|
26
27
|
)
|
|
27
28
|
.model<${dict.Model}>((t) => ({
|
|
28
|
-
name: t(["Name", "이름"]),
|
|
29
|
+
name: t(["Name", "이름"]).desc(["Name", "이름"]),
|
|
29
30
|
}))
|
|
30
31
|
.insight<${dict.Model}Insight>((t) => ({}))
|
|
32
|
+
.query<${dict.Model}Filter>((fn) => ({}))
|
|
33
|
+
.sort<${dict.Model}Filter>((t) => ({}))
|
|
31
34
|
.slice<${dict.Model}Slice>((fn) => ({
|
|
32
35
|
inPublic: fn(["${dict.Model} In Public", "${dict.Model} 공개"]).arg((t) => ({})),
|
|
33
36
|
}))
|
|
@@ -8,14 +8,13 @@ interface Dict {
|
|
|
8
8
|
}
|
|
9
9
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
10
10
|
return `
|
|
11
|
-
import { endpoint, internal, Public, slice } from "akanjs/signal";
|
|
11
|
+
import { endpoint, internal, None, Public, slice } from "akanjs/signal";
|
|
12
12
|
|
|
13
|
-
import * as cnst from "../cnst";
|
|
14
13
|
import * as srv from "../srv";
|
|
15
14
|
|
|
16
15
|
export class ${dict.Model}Internal extends internal(srv.${dict.model}, ({ interval }) => ({})) {}
|
|
17
16
|
|
|
18
|
-
export class ${dict.Model}Slice extends slice(srv.${dict.model}, { guards: { root:
|
|
17
|
+
export class ${dict.Model}Slice extends slice(srv.${dict.model}, { guards: { root: None, get: Public, cru: Public } }, (init) => ({
|
|
19
18
|
inPublic: init()
|
|
20
19
|
.exec(function () {
|
|
21
20
|
return this.${dict.model}Service.queryAny();
|
|
@@ -9,8 +9,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
|
|
|
9
9
|
return `
|
|
10
10
|
import { store } from "akanjs/store";
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
import { fetch, sig } from "../useClient";
|
|
12
|
+
import { sig } from "../useClient";
|
|
14
13
|
|
|
15
14
|
export class ${dict.Model}Store extends store(sig.${dict.model}, () => ({
|
|
16
15
|
// state
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
set -e
|
|
2
|
+
|
|
3
|
+
# Find commit files
|
|
4
|
+
echo "Finding commit files..."
|
|
5
|
+
FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g')
|
|
6
|
+
[ -z "$FILES" ] && exit 0
|
|
7
|
+
|
|
8
|
+
# Filter out ts, tsx, json files, should not exit when there are no lint files
|
|
9
|
+
LINT_FILES=$(echo "$FILES" | grep -E '(\.ts|\.tsx)$' || true)
|
|
10
|
+
|
|
11
|
+
echo "Checking commit files with Biome..."
|
|
12
|
+
# Run Biome autofix if there are lint files. Any remaining errors fail the commit.
|
|
13
|
+
if [ -n "$LINT_FILES" ]; then
|
|
14
|
+
echo "$LINT_FILES" | xargs ./node_modules/.bin/biome check --write --no-errors-on-unmatched --diagnostic-level=error
|
|
15
|
+
echo "$LINT_FILES" | xargs git add --
|
|
16
|
+
fi
|
|
@@ -12,6 +12,10 @@ The section below is regenerated by `akan agent install` (run `bun run setup:age
|
|
|
12
12
|
workspace's apps, generated files, validation commands, and framework guide. Edit anything outside the
|
|
13
13
|
markers freely; content between them is overwritten on the next install.
|
|
14
14
|
|
|
15
|
+
It carries the rules only. The reasoning behind each area lives in deep-dive guidelines fetched on demand —
|
|
16
|
+
`akan guideline list` names them, `akan guideline show <name>` prints one, and an agent wired to `akan mcp`
|
|
17
|
+
calls `get_guideline` instead.
|
|
18
|
+
|
|
15
19
|
<!-- akan:agent:start -->
|
|
16
20
|
<!-- Populated by `akan agent install`. Run `bun run setup:agent` to refresh. -->
|
|
17
21
|
<!-- akan:agent:end -->
|
|
@@ -1,3 +1,22 @@
|
|
|
1
1
|
# <%= repoName %> — Claude Code Guide
|
|
2
2
|
|
|
3
3
|
@AGENTS.md
|
|
4
|
+
|
|
5
|
+
## Comments — Overrides Your Default
|
|
6
|
+
|
|
7
|
+
Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
|
|
8
|
+
is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
|
|
9
|
+
|
|
10
|
+
Before typing `//`, `/*`, or a doc block, ask — **does this sentence carry a fact that is nowhere in the code?**
|
|
11
|
+
|
|
12
|
+
- Restates the identifier, the signature, or the line under it → delete it.
|
|
13
|
+
- Labels a section (`// helpers`, `// state`) or narrates a step (`// fetch the user`, `// then save`) → delete it.
|
|
14
|
+
- JSDoc on an ordinary function, or a why/how preamble on ordinary logic → delete it.
|
|
15
|
+
- Explains the edit you just made, for whoever reads the diff → say it in your reply, not in the file.
|
|
16
|
+
- Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
|
|
17
|
+
derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected →
|
|
18
|
+
keep it, one line.
|
|
19
|
+
|
|
20
|
+
That keep-list is exact — `Comments` in the guide is the full version. "It aids readability" and "this logic is
|
|
21
|
+
subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
|
|
22
|
+
surrounding code carries none, your diff carries none.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.12/schema.json",
|
|
3
3
|
"extends": ["@akanjs/devkit/biome.base.json"],
|
|
4
4
|
"files": {
|
|
5
5
|
"includes": [
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"!!**/node_modules",
|
|
13
13
|
"!!**/dist",
|
|
14
14
|
"!!**/.akan",
|
|
15
|
+
"!!**/.claude",
|
|
15
16
|
"!!**/dump",
|
|
16
17
|
"!!**/local",
|
|
17
18
|
"!!**/ios/DerivedData"
|