@akanjs/cli 3.0.0-alpha.10 → 3.0.0-alpha.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.
- package/.build-stamp +1 -1
- package/{agent.command-f0gqkfgz.js → agent.command-a3n15c6t.js} +5 -5
- package/{application.command-x903m2qq.js → application.command-rp3y4ejb.js} +4 -4
- package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-73kbbb7e.js} +1 -1
- package/buildBatch.proc.js +2 -2
- package/{capacitorApp-9xswcgx7.js → capacitorApp-mr81qdxq.js} +2 -2
- package/{cloud.command-zs6kyf83.js → cloud.command-7mcmm52j.js} +7 -7
- package/{context.command-fmepgjzg.js → context.command-ysxawe7h.js} +12 -12
- package/{guideline.command-n6sw0a0c.js → guideline.command-yqf9a372.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +158 -1
- package/guidelines/cssRule/cssRule.instruction.md +7 -0
- package/guidelines/framework/framework.instruction.md +1 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +3 -1
- package/incrementalBuilder.proc.js +1 -1
- package/{index-5sn5w2mt.js → index-3789fdpg.js} +10 -10
- package/{index-y4vkyhvv.js → index-4mgkgba3.js} +2 -2
- package/{index-62955kn5.js → index-5yjc80ha.js} +5 -5
- package/{index-xmmzf150.js → index-65aeh789.js} +1 -1
- package/{index-d43t8af2.js → index-8pd02prk.js} +2 -2
- package/{index-779ske1y.js → index-9va8zayb.js} +1 -1
- package/{index-qzdjd4ct.js → index-cv9cxs5k.js} +3 -3
- package/{index-774gcx29.js → index-e4b2ae6b.js} +2 -2
- package/{index-kdh76j26.js → index-etwh7sd3.js} +1 -1
- package/{index-gcv8ncfh.js → index-h5whaj26.js} +2 -2
- package/{index-82jrh4hj.js → index-k8gpsrm1.js} +5 -5
- package/{index-rxkpk7n4.js → index-kcsa1zff.js} +1 -1
- package/{index-3vta1tqa.js → index-m90fn8xe.js} +4 -4
- package/{index-jhpa13zj.js → index-mewacxye.js} +4 -4
- package/{index-4m0bphac.js → index-rdv33eye.js} +8 -10
- package/{index-qb61h0k7.js → index-wzdeact3.js} +72 -8
- package/{index-jf2ya7r2.js → index-yzmqvg23.js} +3 -3
- package/{index-z8ptfkzv.js → index-yzsxc30b.js} +15 -12
- package/{index-eyyf2149.js → index-z1s5f4e0.js} +3 -3
- package/index.js +18 -18
- package/{library.command-cv9gdbmg.js → library.command-x6t29a30.js} +3 -3
- package/{localRegistry.command-d8fg36zc.js → localRegistry.command-f75tqg9w.js} +6 -6
- package/{module.command-qyr1s9cn.js → module.command-8w8ys5t3.js} +5 -5
- package/{package.command-wsjx0k47.js → package.command-zgy7f3fz.js} +3 -3
- package/package.json +2 -2
- package/{page.command-rdzg65rm.js → page.command-yjmnkyk2.js} +3 -3
- package/{primitive.command-89g38b53.js → primitive.command-gfqaagsg.js} +6 -6
- package/{quality.command-0cs2z92d.js → quality.command-ec8r1dn3.js} +361 -90
- package/{repair.command-6fphxfwj.js → repair.command-p9ey6mkm.js} +5 -5
- package/{scalar.command-xfer69s6.js → scalar.command-9dhmqhtm.js} +4 -4
- package/templates/appSample/srvkit/AuthGuard.ts +9 -5
- package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
- package/templates/workspaceRoot/biome.json.template +1 -0
- package/{workflow.command-2ygfqt4f.js → workflow.command-ycwc6zr6.js} +9 -9
- package/{workspace.command-vbgpjkf9.js → workspace.command-x49ekpkt.js} +50 -33
package/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
84acd2086ed367dc670ca155f5d5f884d8af105b3527914ac5ba312e3e1985e6
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
AgentScript
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
4
|
+
} from "./index-z1s5f4e0.js";
|
|
5
|
+
import"./index-e4b2ae6b.js";
|
|
6
|
+
import"./index-kcsa1zff.js";
|
|
7
7
|
import"./index-j37qq1f2.js";
|
|
8
8
|
import {
|
|
9
9
|
Workspace,
|
|
10
10
|
command
|
|
11
|
-
} from "./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
} from "./index-etwh7sd3.js";
|
|
12
|
+
import"./index-yzsxc30b.js";
|
|
13
13
|
import"./index-mxvakhsm.js";
|
|
14
14
|
import"./index-xys926f2.js";
|
|
15
15
|
import"./index-1577bej2.js";
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
} from "./index-0wae5ebk.js";
|
|
5
5
|
import {
|
|
6
6
|
ApplicationScript
|
|
7
|
-
} from "./index-
|
|
8
|
-
import"./index-
|
|
7
|
+
} from "./index-5yjc80ha.js";
|
|
8
|
+
import"./index-8pd02prk.js";
|
|
9
9
|
import {
|
|
10
10
|
getMobileTargetChoices
|
|
11
11
|
} from "./index-76rn3g2c.js";
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
Sys,
|
|
16
16
|
Workspace,
|
|
17
17
|
command
|
|
18
|
-
} from "./index-
|
|
18
|
+
} from "./index-etwh7sd3.js";
|
|
19
19
|
import"./index-fgc8r6dj.js";
|
|
20
20
|
import"./index-bjpxzr6s.js";
|
|
21
|
-
import"./index-
|
|
21
|
+
import"./index-yzsxc30b.js";
|
|
22
22
|
import"./index-mxvakhsm.js";
|
|
23
23
|
import"./index-1577bej2.js";
|
|
24
24
|
import"./index-67546d0j.js";
|
package/buildBatch.proc.js
CHANGED
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
FontOptimizer,
|
|
6
6
|
PagesBundleBuilder,
|
|
7
7
|
SsrBaseArtifactBuilder
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-wzdeact3.js";
|
|
9
9
|
import"./index-xjd3rqzj.js";
|
|
10
10
|
import"./index-j9cxndc0.js";
|
|
11
11
|
import"./index-bjpxzr6s.js";
|
|
12
12
|
import {
|
|
13
13
|
AppExecutor,
|
|
14
14
|
WorkspaceExecutor
|
|
15
|
-
} from "./index-
|
|
15
|
+
} from "./index-yzsxc30b.js";
|
|
16
16
|
import"./index-mxvakhsm.js";
|
|
17
17
|
import"./index-1577bej2.js";
|
|
18
18
|
import"./index-67546d0j.js";
|
|
@@ -25,9 +25,9 @@ import {
|
|
|
25
25
|
selectLocalDevHost,
|
|
26
26
|
sortIosRunTargets,
|
|
27
27
|
writeRootCapacitorConfig
|
|
28
|
-
} from "./index-
|
|
28
|
+
} from "./index-9va8zayb.js";
|
|
29
29
|
import"./index-76rn3g2c.js";
|
|
30
|
-
import"./index-
|
|
30
|
+
import"./index-yzsxc30b.js";
|
|
31
31
|
import"./index-mxvakhsm.js";
|
|
32
32
|
import"./index-1577bej2.js";
|
|
33
33
|
import"./index-67546d0j.js";
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
CloudScript
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
4
|
+
} from "./index-k8gpsrm1.js";
|
|
5
|
+
import"./index-rdv33eye.js";
|
|
6
|
+
import"./index-h5whaj26.js";
|
|
7
7
|
import {
|
|
8
8
|
GlobalConfig
|
|
9
9
|
} from "./index-0cj2zxbm.js";
|
|
10
|
-
import"./index-
|
|
11
|
-
import"./index-
|
|
10
|
+
import"./index-5yjc80ha.js";
|
|
11
|
+
import"./index-8pd02prk.js";
|
|
12
12
|
import"./index-76rn3g2c.js";
|
|
13
13
|
import {
|
|
14
14
|
Workspace,
|
|
15
15
|
command
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-etwh7sd3.js";
|
|
17
17
|
import"./index-fgc8r6dj.js";
|
|
18
18
|
import"./index-bjpxzr6s.js";
|
|
19
|
-
import"./index-
|
|
19
|
+
import"./index-yzsxc30b.js";
|
|
20
20
|
import"./index-mxvakhsm.js";
|
|
21
21
|
import"./index-1577bej2.js";
|
|
22
22
|
import"./index-46tjzh6s.js";
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
ContextScript
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
7
|
-
import"./index-
|
|
8
|
-
import"./index-
|
|
9
|
-
import"./index-
|
|
4
|
+
} from "./index-3789fdpg.js";
|
|
5
|
+
import"./index-yzmqvg23.js";
|
|
6
|
+
import"./index-cv9cxs5k.js";
|
|
7
|
+
import"./index-4mgkgba3.js";
|
|
8
|
+
import"./index-m90fn8xe.js";
|
|
9
|
+
import"./index-mewacxye.js";
|
|
10
10
|
import"./index-ss469dec.js";
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
import"./index-65aeh789.js";
|
|
12
|
+
import"./index-9va8zayb.js";
|
|
13
13
|
import"./index-0cj2zxbm.js";
|
|
14
|
-
import"./index-
|
|
15
|
-
import"./index-
|
|
14
|
+
import"./index-e4b2ae6b.js";
|
|
15
|
+
import"./index-kcsa1zff.js";
|
|
16
16
|
import"./index-j37qq1f2.js";
|
|
17
17
|
import"./index-76rn3g2c.js";
|
|
18
18
|
import {
|
|
19
19
|
Workspace,
|
|
20
20
|
command
|
|
21
|
-
} from "./index-
|
|
22
|
-
import"./index-
|
|
21
|
+
} from "./index-etwh7sd3.js";
|
|
22
|
+
import"./index-yzsxc30b.js";
|
|
23
23
|
import"./index-mxvakhsm.js";
|
|
24
24
|
import"./index-xys926f2.js";
|
|
25
25
|
import"./index-1577bej2.js";
|
|
@@ -72,6 +72,17 @@ that looks wrong; do not "fix" it back.
|
|
|
72
72
|
(`no-import-client-functions.grit`, `no-use-client-in-server.grit`, `non-scalar-props-restricted.grit`).
|
|
73
73
|
- `noArrayIndexKey` and `useExhaustiveDependencies` are **off** on purpose: `key={idx}` for embedded scalars and
|
|
74
74
|
short dependency arrays are intentional, not oversights.
|
|
75
|
+
- **A grit plugin diagnostic is suppressed as `lint/plugin`, not `plugin`** — `// biome-ignore lint/plugin: <reason>`
|
|
76
|
+
for one line, `// biome-ignore-all lint/plugin: <reason>` for a file. The bare `// biome-ignore plugin:` form Biome's
|
|
77
|
+
own category name suggests does nothing. Suppress a plugin only where the rule is genuinely wrong for the file, and
|
|
78
|
+
say why: the module-convention plugins (`no-import-external-library`, `no-deep-internal-import`, the store/signal
|
|
79
|
+
ones) apply to `apps/**` and `libs/**` only, so a plain package under `pkgs/` never needs the escape hatch.
|
|
80
|
+
- **`biome.json` is strict JSON — a comment in it breaks config resolution.** Biome 2.5.8 does not report the parse
|
|
81
|
+
error; it falls back to discovery and aborts on whatever nested config the walk finds, typically inside a directory
|
|
82
|
+
`files.includes` excludes. Rename the file to `biome.jsonc` to document a disabled rule; `akan lint` pins the config
|
|
83
|
+
path either way, so it reports the parse error on the offending line.
|
|
84
|
+
- **`akan lint` prints up to 200 diagnostics** (`--maxDiagnostics <n>`, `0` for no limit). Biome's own default is 20
|
|
85
|
+
with no count, which reads as progress when the mix of findings merely changed.
|
|
75
86
|
|
|
76
87
|
## Coding Style (`**/*.{ts,tsx}`)
|
|
77
88
|
|
|
@@ -261,7 +272,7 @@ Full version with code, the `Tab` composition example, and a review checklist: `
|
|
|
261
272
|
- Conditional render is `cond ? <X/> : null`. Never `{cond && <X/>}` — in a `className` context it renders the literal string `"false"`. Early `return null` is for guard clauses only.
|
|
262
273
|
- Never hand-roll loading, empty, or list states. Use `Load.Units` / `Load.View` / `Load.Edit` with `renderItem`, `renderList`, `renderView`, and `renderEmpty`; `<Empty />` for a bare placeholder; and `Model.New` / `Model.Edit` / `Model.SureToRemove` for CRUD modals.
|
|
263
274
|
- Avoid hooks. `useState` is for modal-open, tab, draft-input, and drag state only — never for server data. `useEffect` must be a genuine effect such as subscribe-with-cleanup or one-shot init. Prefer `Tab` over a `useState` mode switch. `.Template.tsx` files contain zero `useState`.
|
|
264
|
-
- Forms are entirely store-driven: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`, the setter passed by reference. Always use `Field.*`, never a bare `<input>` for a model field. Nested rows use `st.do.writeOnX("payments.3.name", v)` plus the generated `add<Field>OnX` / `sub<Field>OnX`.
|
|
275
|
+
- Forms are entirely store-driven: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`, the setter passed by reference. Always use `Field.*`, never a bare `<input>` for a model field. Nested rows use `st.do.writeOnX("payments.3.name", v)` plus the generated `add<Field>OnX` / `sub<Field>OnX`. **Passing the setter by reference is also what makes the framework emit `data-akan-action` / `data-akan-state`** on the control — the annotation an in-page agent, an E2E selector, and an external browser agent all read. Wrapping it in an inline arrow (`onChange={(v) => st.do.setFieldOnX(v)}`) silently drops that: a closure the caller wrote says nothing about what it does. Never hand-write a `data-akan-*` attribute.
|
|
265
276
|
- Read with `st.use.*` and write with `st.do.*`. Client components do not call `fetch.*`.
|
|
266
277
|
- Static class strings stay plain strings. Reach for `cn` only for a conditional or to merge an incoming `className`, and merge the caller last: `cn("base classes", cond && "extra", className)`. `cn` comes from `akanjs/client` (token-aware tailwind-merge) and is the only class-combining function — no `clsx` (removed), no raw `twMerge` imports, no object syntax (`{ x: cond }` → `cond && "x"`).
|
|
267
278
|
- Multi-slot components take extra named props (`wrapperClassName`, `bodyClassName`), never a `classNames` object.
|
|
@@ -333,6 +344,10 @@ than returning it (`no-return-in-store-action.grit`); a bare `return;` guard sta
|
|
|
333
344
|
`.of() → .model() → .insight() → .query() → .sort() → .enum() → .slice() → .endpoint() → .error() → .translate()`.
|
|
334
345
|
Name every argument in `.arg()`, including framework-supplied `skip` / `limit` / `sort`. Use `modelDictionary`,
|
|
335
346
|
`scalarDictionary`, or `serviceDictionary` to match the module kind.
|
|
347
|
+
**`.store()` sits between `.endpoint()` and `.error()` and is the one optional stage** — omit it entirely rather
|
|
348
|
+
than writing it empty. It names custom store actions (labels and `.desc()` only, no `.arg()`), and it is only
|
|
349
|
+
needed where inheriting would be wrong: an action named after the endpoint it calls already reads as that
|
|
350
|
+
endpoint's `.desc()`, which is most of them. `akan.agent.missing-store-description` names the rest.
|
|
336
351
|
|
|
337
352
|
**`<module>.abstract.md`** — a title line, one declarative sentence naming what the module owns, a `## Rules` list of
|
|
338
353
|
two to five invariants the code cannot show, and an optional workflow arrow chain
|
|
@@ -460,6 +475,147 @@ Conventions that hold for both shapes:
|
|
|
460
475
|
- `try/catch` is rare and always converts an exception into a decision, never swallows one. Guards catch → `logger.warn` → `return false`; adapters catch → `logger.error` → `return null`; UI uses `try/finally` to reset a spinner. A bodyless `catch {}` is acceptable only with a one-line reason.
|
|
461
476
|
- Store actions do not `try/catch` — let the framework toast the `Err`. Client-side validation failure is `msg.error("<key>")` plus an early return, never a throw.
|
|
462
477
|
|
|
478
|
+
### MCP Exposure
|
|
479
|
+
|
|
480
|
+
Any signal can be served to AI agents as an MCP server on `POST /mcp`, and **nothing is exposed until it says so**.
|
|
481
|
+
Turn it on in the app's `main.ts` — `new AkanApp("./server", { mcp: { … } })` — which takes `enabled`, `readOnly`,
|
|
482
|
+
`path`, `version`, `instructions`, `allowedOrigins`, `pageSize`, `language`, and `auth`. That is the only
|
|
483
|
+
app-authored place for it: `server.ts` is generated and takes no options, and the gateway configures a child
|
|
484
|
+
through its environment — so each field also has an env spelling (`AKAN_MCP`, `AKAN_MCP_READONLY`,
|
|
485
|
+
`AKAN_MCP_PATH`, `AKAN_MCP_VERSION`, `AKAN_MCP_INSTRUCTIONS`, `AKAN_MCP_ALLOWED_ORIGINS`, `AKAN_MCP_PAGE_SIZE`,
|
|
486
|
+
`AKAN_MCP_LANGUAGE`, `AKAN_MCP_AUTH_SERVERS`, `AKAN_MCP_SCOPES`, `AKAN_MCP_RESOURCE`), which code overrides.
|
|
487
|
+
The two booleans answer to `AKAN_PUBLIC_MCP` / `AKAN_PUBLIC_MCP_READONLY` too, the same pairing `AKAN_OPENAPI`
|
|
488
|
+
has, and a value written in code wins over the env of the same name — an explicit `undefined` is not a value.
|
|
489
|
+
`AKAN_MCP_PATH` is normalized to a leading `/`, because the route key and the OAuth metadata path are both built by
|
|
490
|
+
concatenation.
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// <model>.signal.ts — a tool, a resource-backed slice, and a slash-command prompt
|
|
494
|
+
export class TaskSlice extends slice(
|
|
495
|
+
srv.task,
|
|
496
|
+
// generated CRUD per verb; `list` is the model's own unfiltered list, which `slice()` generates itself
|
|
497
|
+
{ guards: { root: Admin, get: SignedIn, cru: SignedIn }, mcp: { get: true, list: true } },
|
|
498
|
+
(init) => ({
|
|
499
|
+
// its own guards: the map above reaches base CRUD and the root slice, never a named slice
|
|
500
|
+
inTodo: init({ guards: [SignedIn], mcp: { expose: true } }).exec(function () {
|
|
501
|
+
return this.taskService.queryByStatuses(["todo"]);
|
|
502
|
+
}),
|
|
503
|
+
}),
|
|
504
|
+
) {}
|
|
505
|
+
|
|
506
|
+
export class TaskEndpoint extends endpoint(srv.task, ({ mutation, prompt }) => ({
|
|
507
|
+
startTask: mutation(cnst.Task, { guards: [SignedIn], mcp: { expose: true } })
|
|
508
|
+
.param("taskId", ID)
|
|
509
|
+
.exec(async function (taskId) {
|
|
510
|
+
return await this.taskService.startTask(taskId);
|
|
511
|
+
}),
|
|
512
|
+
reviewTask: prompt({ guards: [SignedIn], mcp: { expose: true } })
|
|
513
|
+
.param("taskId", ID)
|
|
514
|
+
.exec(async function (taskId) {
|
|
515
|
+
const task = await this.taskService.getTask(taskId);
|
|
516
|
+
return [Msg.user(`Review this task and suggest next steps.`), Msg.resource(`akan://task/${taskId}`, task)];
|
|
517
|
+
}),
|
|
518
|
+
})) {}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
- **The refusals are fail-closed and survive opting in**: `pubsub` and `message` (their internal args read a socket
|
|
522
|
+
an MCP request does not have), an `Any` or `Upload` return, a file upload, **a mutation with no real `guards`**
|
|
523
|
+
(`[Public]` is having none, spelled out — it answers true unconditionally), and **an argument typed `Any` that
|
|
524
|
+
must be filled**.
|
|
525
|
+
A `prompt` refuses two more, because its `arguments` is one string per name with no schema beside it: a **list
|
|
526
|
+
argument**, which could never carry a second value, and **any `Any` argument** — a tool leaves that out of its
|
|
527
|
+
schema, and a prompt has no schema to leave it out of. `resource: true` is refused there too: only a read
|
|
528
|
+
publishes a resource template.
|
|
529
|
+
- **Every refusal is named in the boot log**, and so is every published entry that declares no `guards` at all —
|
|
530
|
+
the access is what `[Public]` grants, but only one of the two is a decision you made. One `warn` per endpoint
|
|
531
|
+
plus a `MCP catalogue: tools=… prompts=…` count. Read that line first when a tool you exposed is missing —
|
|
532
|
+
fail-closed is right, and a silent fail-closed leaves you nothing to read. `akan quality scan` covers the two
|
|
533
|
+
shapes visible in source, `akan.mcp.missing-description` and `akan.mcp.unguarded-exposure`; the API explorer
|
|
534
|
+
badges the per-endpoint rules (`MCP` / `MCP refused`) from the same rule the catalogue runs.
|
|
535
|
+
- **An `Any` argument is left out of the published schema** rather than described as `{}` — it tells a model
|
|
536
|
+
nothing — and a value sent for one is refused by name, so the endpoint reads it as omitted. That is what happens
|
|
537
|
+
to the root list's raw `query` descriptor: read as sent, it would be an arbitrary filter over every model you
|
|
538
|
+
exposed through `mcp: { list: true }`. Expose a named filter slice when an agent should narrow a list.
|
|
539
|
+
- **A nullable model return publishes no `outputSchema`**, and its empty answer ships as the text `null` with no
|
|
540
|
+
`structuredContent`. That field is an object by definition, so `null` cannot ride in it any more than an array
|
|
541
|
+
can — a list is wrapped as `{ items: … }` for the same reason — and a declared schema obliges every result to
|
|
542
|
+
match it, so a client SDK throws on the first call that finds nothing. A nullable *list* keeps its schema, and a
|
|
543
|
+
scalar return has no structured half at all: it ships as the value itself, not as JSON.
|
|
544
|
+
- **An `outputSchema` names no `hidden` or `secret` field.** Every response has both stripped, so publishing them
|
|
545
|
+
promises a property no answer can carry — and on a model like `user` the names are the leak. Your *input* schema
|
|
546
|
+
keeps them: they are legal to send, and the same model describes a request body.
|
|
547
|
+
- An endpoint that did not opt in answers the *same* "unknown tool" as one that does not exist. Never make that
|
|
548
|
+
message more helpful — the difference is what enumerates your private surface. A guard's refusal is generalized
|
|
549
|
+
the same way: the caller reads `You are not permitted to perform this action.`, never `Access denied by guard:
|
|
550
|
+
Admin`, which names your authorization structure to the one caller barred from it. A domain `Err` resolves
|
|
551
|
+
through the dictionary first and keeps its own words.
|
|
552
|
+
- `mcp: { readOnly, destructive, idempotent }` only override the hints a client renders. Clients are told to
|
|
553
|
+
distrust hints; they are never a gate.
|
|
554
|
+
- **`AKAN_MCP_READONLY=true` is the read-only-deployment valve, not the exposure switch.** It drops every mutation
|
|
555
|
+
whatever it declared, and reports each one in the boot log like any other refusal.
|
|
556
|
+
- OAuth resource metadata is published at `/.well-known/oauth-protected-resource` (and at that path plus the mount
|
|
557
|
+
path, the spelling most clients try first). `AKAN_MCP_AUTH_SERVERS`, `AKAN_MCP_SCOPES`, and `AKAN_MCP_RESOURCE`
|
|
558
|
+
configure it; `insufficient_scope` is enforced only once `AKAN_MCP_SCOPES` is set. A token carrying no `aud` at
|
|
559
|
+
all is refused once `AKAN_MCP_AUTH_SERVERS` names an issuer — that issuer mints tokens for its other resources
|
|
560
|
+
too — and accepted while none is named, because a first-party Akan token is bound by app and environment.
|
|
561
|
+
- `akan quality scan` warns **`akan.mcp.missing-description`** for anything exposed without a dictionary `.desc()`.
|
|
562
|
+
An agent picks a tool by its description, so a missing one is a broken tool. What the framework generates is
|
|
563
|
+
exempt and borrows the model's own text, having none of its own: `mcp: { list: true }` reads the `.of()` label,
|
|
564
|
+
and the base CRUD tools append the model's `.desc()` to their generated `Get X`. Write that model `.desc()` — it
|
|
565
|
+
is the only text those entries can carry. The scan is not the whole answer either, because it reads source: **the
|
|
566
|
+
boot log names every published entry with no description**, generated ones included.
|
|
567
|
+
- A browser-hosted client needs `allowedOrigins` **and** the CORS answer the server sends back for those origins.
|
|
568
|
+
Every other MCP client sends no `Origin` at all, and the one that does is matched against the forwarded host so
|
|
569
|
+
a proxy does not turn each call into a 403 — which is only as trustworthy as an edge that *overwrites* that
|
|
570
|
+
header. `AKAN_MCP_RESOURCE` pins the resource identifier where you cannot guarantee it.
|
|
571
|
+
- **A `resources/read` uri that does not decode** — a stray `%` — is `Unknown resource`, not a server failure.
|
|
572
|
+
- **A caller's own mistake is reported as one** and never as a server failure: an argument that is missing,
|
|
573
|
+
unparseable or **undeclared** comes back as `isError` naming it — `additionalProperties: false` travels in the
|
|
574
|
+
published schema and nothing on the wire enforces it — and so does a document that is not there, as
|
|
575
|
+
`No <model> found for the arguments given.` A `prompt`, having no `isError` to carry a refusal, answers `-32602`.
|
|
576
|
+
Only a real failure logs a stack; an agent can drive the rest at will.
|
|
577
|
+
- **Three revisions are spoken**: the modern `2026-07-28` and the legacy `2025-11-25` / `2025-06-18`, which are
|
|
578
|
+
wire-identical over the POST-only surface this implements — a client whose proposal is not listed is told to
|
|
579
|
+
disconnect. An unknown proposal is answered at whichever end of that list it is closer to, and an unimplemented
|
|
580
|
+
method answers `404` to a modern client but `200` to a legacy one, whose era spends `404` on "your session is
|
|
581
|
+
gone".
|
|
582
|
+
- **A modern-era request mirrors `MCP-Protocol-Version` and `Mcp-Method` into headers** (plus `Mcp-Name` when the
|
|
583
|
+
body names one), and one that leaves a mirror out is refused just like one that contradicts the body: a gateway
|
|
584
|
+
rule keyed on a header never fires for the request that omitted it. Legacy requests are not checked. Capabilities
|
|
585
|
+
are derived from the catalogue, so a server with no prompts does not advertise `prompts`.
|
|
586
|
+
- **An expired or wrongly-audienced bearer token is refused up front**, so an agent is told to authenticate rather
|
|
587
|
+
than that the tool does not exist. Its **signature is not checked** — that needs your app's own secret — so a
|
|
588
|
+
token signed wrong, like an opaque one, still degrades to an anonymous caller.
|
|
589
|
+
- **Resource URIs**: `akan://<model>/{id}`, `akan://<model>/light/{id}`, `akan://<model>/list` for the model's own
|
|
590
|
+
list, and `akan://<model>/list/<sliceKey>` for a slice's. The root list takes no third segment on purpose — any
|
|
591
|
+
token there is one a slice could also be named. **Those four are the whole set**, so `mcp: { resource: true }`
|
|
592
|
+
is honoured only on the generated reads: a custom endpoint keeps its tool, gets no resource template, and is
|
|
593
|
+
named in the boot log saying so.
|
|
594
|
+
- **The catalogue is one language**, `en` unless `language` says otherwise: it is built once at boot and cached by
|
|
595
|
+
clients, so there is no `Accept-Language` negotiation.
|
|
596
|
+
|
|
597
|
+
**`prompt()`** is invoked by the *user* — a client renders it as a slash command — not chosen by the model. `exec`
|
|
598
|
+
returns `PromptMessage[]`, or a bare string that is wrapped into one user message; build them with `Msg.user` /
|
|
599
|
+
`Msg.assistant` / `Msg.link` / `Msg.resource` / `Msg.image` / `Msg.imageOf`. It takes `.param()` and `.search()`
|
|
600
|
+
only, because `prompts/get` sends a flat string map. **An embedded payload is masked by the model you name** —
|
|
601
|
+
`Msg.resource(uri, task, { model: cnst.LightTask })`, or `Msg.mask(cnst.LightTask, task)` for one piece of an
|
|
602
|
+
assembly. Taking the model as an argument is what makes a `{ ...doc }` spread maskable, since that and `toJSON()`
|
|
603
|
+
arrive with the class already gone; a value with no model named whose `hidden`/`secret` fields are populated is
|
|
604
|
+
**refused**, one level into a plain object too. **A `prompt` is also mounted as a
|
|
605
|
+
plain HTTP `GET` whether or not you enabled MCP**, because that route is what lets a web UI preview one — and it
|
|
606
|
+
is in your OpenAPI document like any other `GET`, answering the one fixed `PromptMessage[]` shape. MCP exposure
|
|
607
|
+
gates the catalogue, not the surface, so guard it
|
|
608
|
+
like any other read — and a prompt declaring no
|
|
609
|
+
`guards` at all is named in the boot log, while an explicit `[Public]` is a decision and stays quiet. Every `Msg` builder takes
|
|
610
|
+
optional `annotations` last (`audience`, `priority` 0..1, `lastModified`) — give the instruction a high `priority`
|
|
611
|
+
and its attachments a low one, or a client with a full window drops blocks by position and keeps the attachment
|
|
612
|
+
over the ask.
|
|
613
|
+
|
|
614
|
+
**`McpProgress.report(n, { total, message })`** reports progress from anywhere inside a call, a service or adapter
|
|
615
|
+
frames down included, and is a no-op when nobody is streaming — so the same code runs unchanged over HTTP, a
|
|
616
|
+
websocket, and in tests. `McpProgress.streaming` says whether anyone is reading, for a report whose message
|
|
617
|
+
costs something to assemble.
|
|
618
|
+
|
|
463
619
|
## Scalar Modeling (`**/*.constant.ts`)
|
|
464
620
|
|
|
465
621
|
- Define Akan models in `.constant.ts` files with `via` from `akanjs/constant`.
|
|
@@ -630,6 +786,7 @@ export const pageConfig = { transition: "stack" } satisfies PageConfig;
|
|
|
630
786
|
| `plugin/` | build- or CLI-time `AkanPlugin` | `<name>.plugin.ts`, registered in `akan.config.ts` |
|
|
631
787
|
|
|
632
788
|
- Hooks return a named object of async closures, never a tuple.
|
|
789
|
+
- `libs/<lib>/ui/tokens.css` is the one CSS file a lib owns: plain `:root` custom properties for colors that must **not** follow the theme (a vendor brand color, a fixed surface). Every app whose pages reach that lib compiles it automatically, ahead of the app's own stylesheets, so nothing is imported by hand and no app can forget it. Reference them as `bg-[var(--kakao)]`; `@theme` extensions stay in the app stylesheet, because the color vocabulary is closed per stylesheet. Theme-following colors are the app's, not the lib's.
|
|
633
790
|
- A layer-root `index.ts` is generated, but a `ui/<Folder>/index.tsx` that builds a namespace is hand-written source. The distinguishing test is that a generated barrel contains nothing but `export * from "./X";` lines.
|
|
634
791
|
- `ui/<Folder>/index_.tsx` (trailing underscore) is the `"use client"` + `lazy()` boundary, with a server-safe `index.tsx` beside it. Collapsing the pair into one file breaks RSC.
|
|
635
792
|
|
|
@@ -55,6 +55,13 @@ Screen code is invariant — a plain `<Button variant="primary">` never changes.
|
|
|
55
55
|
- Switching themes is toggling the `data-theme` attribute. Keep every token defined in both the dark and light blocks.
|
|
56
56
|
- Radius uses `rounded-box` (cards/modals), `rounded-field` (buttons/inputs), `rounded-selector` (toggles/checkboxes).
|
|
57
57
|
|
|
58
|
+
## Lib-Owned Tokens (`libs/<lib>/ui/tokens.css`)
|
|
59
|
+
- A lib that needs colors of its own — vendor brand colors pinned by someone else's guide (Kakao `#fee500`, Naver `#1ec800`), fixed in-game surfaces — declares them **once** in `libs/<lib>/ui/tokens.css`. Never copy the block into each app's stylesheet.
|
|
60
|
+
- The file is collected automatically: every app whose page graph reaches that lib compiles it, ordered **ahead of** the app's own stylesheets, so the app wins on any variable both declare. There is nothing to import and no sync step to remember; two libs both declaring one variable resolve in lib-name order, so do not rely on that.
|
|
61
|
+
- Declare plain custom properties under `:root`, not a Tailwind `@theme` extension: the color vocabulary is closed per stylesheet, so `bg-kakao` would generate no CSS. Reference them as `bg-[var(--kakao)]` / `text-[var(--kakao-foreground)]`, which `no-arbitrary-color` allows by design (a `var()` reference is not an arbitrary color literal).
|
|
62
|
+
- Theme-following colors still belong to the app: a lib token is for a color that must **not** change with `data-theme`.
|
|
63
|
+
- An `@import` the CSS pipeline cannot resolve fails the build; it is never dropped. A stylesheet under `page/` that no route imports is compiled by nothing and warns at build time.
|
|
64
|
+
|
|
58
65
|
## Codegen Rules
|
|
59
66
|
- Do not hardcode hex or raw-palette colors (`bg-red-500`, `#ff0000`); use semantic tokens.
|
|
60
67
|
- Do not put inline color literals in `style={{ }}`; use token classes.
|
|
@@ -26,6 +26,7 @@ Use this as the compact framework context for AI codegen. It should explain how
|
|
|
26
26
|
When a request implies a distinct look and feel, do not stop at colors — customize both the theme and, when needed, the components.
|
|
27
27
|
|
|
28
28
|
- **Theme (`apps/<app>/page/styles.css`).** The app imports Tailwind and `akanjs/ui/styles.css`, then overrides semantic token *values* per theme under `:root, [data-theme="dark"]` and `[data-theme="light"]` (`--background`, `--foreground`, `--primary`, `--muted`, `--border`, … each with a `-foreground` pair for text). The framework maps them to Tailwind color names, so `bg-primary` / `text-foreground` follow the `data-theme` attribute; corner rounding uses `--radius-box` / `--radius-field`. Fetch `get_guideline` with `cssRule` for the full token set before a deep theme pass.
|
|
29
|
+
- **Lib tokens (`libs/<lib>/ui/tokens.css`).** Colors a lib's own components pin — a vendor brand color, a fixed surface — are declared once there as plain `:root` custom properties and compiled into every app that reaches the lib, ahead of the app's stylesheets. Reference them as `bg-[var(--kakao)]`; never copy the block into each app.
|
|
29
30
|
- **Components (`page/**/_overrides.tsx`).** When a default `akanjs/ui` component (Button, Modal, Table, Input, Select, …) is too restrictive for the design, re-skin it per route instead of forking, wrapping, or fighting it with utility classes. Write a drop-in replacement in `apps/<app>/ui/` typed against the slot contract (`AkanModalComponent`, or `AkanUiOverrides["<Slot>"]`), composing the framework's headless parts, then bind it in a `page/**/_overrides.tsx` manifest with a single `export default override({ Slot: BrandComponent })`. Overrides cascade down the route tree like layouts (closest ancestor wins). Fetch `get_guideline` with `componentRule` and read the `references/ui/customize` docs page for the slot list and patterns.
|
|
30
31
|
|
|
31
32
|
## Review Checklist
|
|
@@ -459,7 +459,9 @@ is convention that keeps hand-written code reading like generated code.
|
|
|
459
459
|
generated. Never `import type { RootStore } from "../st"` — it crashes `akan build` with a Bun SSR segfault.
|
|
460
460
|
- **`dictionary.ts`** — fixed chain with empty stages still written:
|
|
461
461
|
`.of() → .model() → .insight() → .query() → .sort() → .enum() → .slice() → .endpoint() → .error() → .translate()`.
|
|
462
|
-
Every label is `t(["English", "한국어"])`, and nearly every one also carries `.desc([en, ko])`.
|
|
462
|
+
Every label is `t(["English", "한국어"])`, and nearly every one also carries `.desc([en, ko])`. One optional stage,
|
|
463
|
+
`.store()`, sits between `.endpoint()` and `.error()` for custom store actions whose name differs from the
|
|
464
|
+
endpoint they call — omit it rather than writing it empty.
|
|
463
465
|
- **`srvkit/` adapters** — an injected singleton is an `adapt("name" as const, ({ use, env, plug, memory }) => ({…}))`
|
|
464
466
|
class, injected with `plug(TheClass)`. It self-registers, so do not add it to `lib/option.ts`. `this.logger` is
|
|
465
467
|
provided; lifecycle work goes in `override async onInit()`. A per-use value object stays a plain class you `new` at
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
RepairRunner
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-yzmqvg23.js";
|
|
5
5
|
import {
|
|
6
6
|
WorkflowRunner
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-cv9cxs5k.js";
|
|
8
8
|
import {
|
|
9
9
|
ScalarScript
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-4mgkgba3.js";
|
|
11
11
|
import {
|
|
12
12
|
PrimitiveScript
|
|
13
|
-
} from "./index-
|
|
13
|
+
} from "./index-m90fn8xe.js";
|
|
14
14
|
import {
|
|
15
15
|
ModuleScript
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-mewacxye.js";
|
|
17
17
|
import {
|
|
18
18
|
isPlaceholderAppId
|
|
19
|
-
} from "./index-
|
|
19
|
+
} from "./index-9va8zayb.js";
|
|
20
20
|
import {
|
|
21
21
|
AkanContextAnalyzer,
|
|
22
22
|
akanMcpInstallConfigPaths,
|
|
@@ -27,14 +27,14 @@ import {
|
|
|
27
27
|
renderDoctorText,
|
|
28
28
|
resourceList,
|
|
29
29
|
upsertCodexMcpServerBlock
|
|
30
|
-
} from "./index-
|
|
30
|
+
} from "./index-e4b2ae6b.js";
|
|
31
31
|
import {
|
|
32
32
|
buildAkanModuleContextIndex,
|
|
33
33
|
createWorkflowBaselineSummary,
|
|
34
34
|
createWorkflowStepRegistry,
|
|
35
35
|
jsonText,
|
|
36
36
|
toolingRolloutGate
|
|
37
|
-
} from "./index-
|
|
37
|
+
} from "./index-kcsa1zff.js";
|
|
38
38
|
import {
|
|
39
39
|
Prompter
|
|
40
40
|
} from "./index-j37qq1f2.js";
|
|
@@ -45,10 +45,10 @@ import {
|
|
|
45
45
|
CommandContainer,
|
|
46
46
|
runner,
|
|
47
47
|
script
|
|
48
|
-
} from "./index-
|
|
48
|
+
} from "./index-etwh7sd3.js";
|
|
49
49
|
import {
|
|
50
50
|
AppExecutor
|
|
51
|
-
} from "./index-
|
|
51
|
+
} from "./index-yzsxc30b.js";
|
|
52
52
|
|
|
53
53
|
// pkgs/@akanjs/cli/context/context.script.ts
|
|
54
54
|
import { Logger } from "akanjs/common";
|
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
createPassedPrimitiveReport,
|
|
10
10
|
generatedFilesForSync,
|
|
11
11
|
scalarChangedFiles
|
|
12
|
-
} from "./index-
|
|
12
|
+
} from "./index-kcsa1zff.js";
|
|
13
13
|
import {
|
|
14
14
|
Prompter
|
|
15
15
|
} from "./index-j37qq1f2.js";
|
|
16
16
|
import {
|
|
17
17
|
runner,
|
|
18
18
|
script
|
|
19
|
-
} from "./index-
|
|
19
|
+
} from "./index-etwh7sd3.js";
|
|
20
20
|
|
|
21
21
|
// pkgs/@akanjs/cli/scalar/scalar.prompt.ts
|
|
22
22
|
import { input } from "@inquirer/prompts";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
LibraryScript
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-8pd02prk.js";
|
|
5
5
|
import {
|
|
6
6
|
resolveMobileTargets
|
|
7
7
|
} from "./index-76rn3g2c.js";
|
|
8
8
|
import {
|
|
9
9
|
runner,
|
|
10
10
|
script
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-etwh7sd3.js";
|
|
12
12
|
import {
|
|
13
13
|
HmrWatcher,
|
|
14
14
|
WatchRootResolver
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
LibExecutor,
|
|
19
19
|
PkgExecutor,
|
|
20
20
|
WorkspaceExecutor
|
|
21
|
-
} from "./index-
|
|
21
|
+
} from "./index-yzsxc30b.js";
|
|
22
22
|
import {
|
|
23
23
|
__require
|
|
24
24
|
} from "./index-r24hmh0q.js";
|
|
@@ -1898,9 +1898,9 @@ function openBrowser(url) {
|
|
|
1898
1898
|
}
|
|
1899
1899
|
|
|
1900
1900
|
// pkgs/@akanjs/cli/application/application.runner.ts
|
|
1901
|
-
var loadBuildRunner = async () => (await import("./applicationBuildRunner-
|
|
1901
|
+
var loadBuildRunner = async () => (await import("./applicationBuildRunner-73kbbb7e.js")).ApplicationBuildRunner;
|
|
1902
1902
|
var loadReleasePackager = async () => (await import("./applicationReleasePackager-gg8ccbeh.js")).ApplicationReleasePackager;
|
|
1903
|
-
var loadCapacitorApp = async () => (await import("./capacitorApp-
|
|
1903
|
+
var loadCapacitorApp = async () => (await import("./capacitorApp-mr81qdxq.js")).CapacitorApp;
|
|
1904
1904
|
var loadAbstractCompactor = async () => (await import("./abstractCompactor-e5yq60qz.js")).AbstractCompactor;
|
|
1905
1905
|
var loadPrompts = async () => await import("@inquirer/prompts");
|
|
1906
1906
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
AkanContextAnalyzer
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-e4b2ae6b.js";
|
|
5
5
|
import {
|
|
6
6
|
WorkflowExecutor,
|
|
7
7
|
compactWorkflowInputs,
|
|
@@ -23,10 +23,10 @@ import {
|
|
|
23
23
|
workflowCommandsForPlan,
|
|
24
24
|
workflowPlanApproval,
|
|
25
25
|
writeWorkflowRunArtifact
|
|
26
|
-
} from "./index-
|
|
26
|
+
} from "./index-kcsa1zff.js";
|
|
27
27
|
import {
|
|
28
28
|
runner
|
|
29
|
-
} from "./index-
|
|
29
|
+
} from "./index-etwh7sd3.js";
|
|
30
30
|
|
|
31
31
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
32
32
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
workflowRunArtifactPath,
|
|
4
4
|
workflowSyncDir
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-kcsa1zff.js";
|
|
6
6
|
import {
|
|
7
7
|
AppExecutor,
|
|
8
8
|
LibExecutor,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
extractBlockVersion,
|
|
12
12
|
isScannedAppRootEntry,
|
|
13
13
|
readDevkitVersion
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-yzsxc30b.js";
|
|
15
15
|
import {
|
|
16
16
|
collectRecipeSources,
|
|
17
17
|
findInlineRecipeDuplicates,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
runner,
|
|
4
4
|
script
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-etwh7sd3.js";
|
|
6
6
|
import {
|
|
7
7
|
PkgExecutor
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-yzsxc30b.js";
|
|
9
9
|
import {
|
|
10
10
|
TypeScriptDependencyScanner
|
|
11
11
|
} from "./index-46tjzh6s.js";
|