@akanjs/cli 3.0.0-alpha.6 → 3.0.0-alpha.61

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.
Files changed (111) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-20e0cwbs.js} +2 -2
  5. package/{agent.command-b0fcx3r2.js → agent.command-tsx1nsdj.js} +9 -7
  6. package/{application.command-cezdnvtq.js → application.command-n8ex8g9h.js} +8 -6
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-x8h12rp0.js} +7 -7
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
  9. package/buildBatch.proc.js +7 -5
  10. package/{capacitorApp-jaksw4cy.js → capacitorApp-8mv9e537.js} +29 -27
  11. package/{cloud.command-6xvsd487.js → cloud.command-f0rhw014.js} +12 -10
  12. package/{context.command-ajn1b683.js → context.command-s7a41qjs.js} +17 -15
  13. package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
  14. package/getDirname-ka11hm7e.js +8 -0
  15. package/{guideline.command-9ew6aqhm.js → guideline.command-qrw84ttb.js} +8 -6
  16. package/guidelines/agentRule/agentRule.generate.json +31 -0
  17. package/guidelines/agentRule/agentRule.instruction.md +525 -0
  18. package/guidelines/conventions/conventions.generate.json +25 -0
  19. package/guidelines/conventions/conventions.instruction.md +821 -0
  20. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  21. package/guidelines/framework/framework.instruction.md +1 -0
  22. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  23. package/guidelines/mcpRule/mcpRule.instruction.md +176 -0
  24. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  25. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +638 -0
  26. package/incrementalBuilder.proc.js +8 -6
  27. package/{index-zr2wvx69.js → index-0g9ajdz8.js} +6 -6
  28. package/index-1577bej2.js +7 -0
  29. package/{index-wrrb8rxk.js → index-4mtyaqj2.js} +4 -4
  30. package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
  31. package/{index-47qxw1tz.js → index-681sx6nt.js} +176 -66
  32. package/{index-zbx0jheb.js → index-8md8zszv.js} +14 -13
  33. package/{index-pfrt7qwh.js → index-cc1mgv1f.js} +4 -4
  34. package/{index-hk9rbcsz.js → index-ce9755av.js} +1 -1
  35. package/{index-jkynkyfk.js → index-ckpwq052.js} +5 -3
  36. package/{index-c94nq3c1.js → index-dvg362vq.js} +3 -3
  37. package/{index-vkqxw0ek.js → index-ek4mkwxp.js} +13 -13
  38. package/{index-exhpp31y.js → index-f0t3wjyr.js} +15 -7
  39. package/index-f39512t6.js +255 -0
  40. package/{index-m4kpmmtj.js → index-g44fykfw.js} +1 -1
  41. package/{index-nfxq39kj.js → index-h0mnt0b0.js} +37 -6
  42. package/{index-qp089jp0.js → index-j37qq1f2.js} +1 -1
  43. package/{index-hrwzz2wf.js → index-kkya2ped.js} +1 -1
  44. package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
  45. package/{index-r7nqv8qj.js → index-r8z20vnj.js} +14 -14
  46. package/{index-6npsfpxr.js → index-ryns915y.js} +3 -3
  47. package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
  48. package/{index-60a3jfcz.js → index-tn46rr6w.js} +8 -8
  49. package/{index-jn9vfpwz.js → index-wc19q8mb.js} +54 -15
  50. package/{index-meh7mmz9.js → index-x1dbhd80.js} +8 -4
  51. package/{index-qb61h0k7.js → index-xj5b8c0w.js} +193 -565
  52. package/index.js +21 -19
  53. package/{library.command-aa1g8nsa.js → library.command-p3jgf5h2.js} +6 -4
  54. package/{localRegistry.command-jha3sn50.js → localRegistry.command-bms0qn6t.js} +11 -9
  55. package/{module.command-b1zzgqde.js → module.command-hb3xm07r.js} +10 -8
  56. package/{package.command-9qv6vr9s.js → package.command-fbpa6r4w.js} +6 -4
  57. package/package.json +3 -4
  58. package/{page.command-k16pw244.js → page.command-92vrmw96.js} +6 -4
  59. package/{primitive.command-s60dn1ae.js → primitive.command-k6b9jfxr.js} +11 -9
  60. package/{quality.command-p3vaze52.js → quality.command-89vwmjvf.js} +194 -112
  61. package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
  62. package/{repair.command-mfv749ha.js → repair.command-nh9jn8rh.js} +8 -6
  63. package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-7fycdqyv.js} +3 -39
  64. package/{scalar.command-hs3nwzv1.js → scalar.command-zcsjgvax.js} +9 -7
  65. package/templates/app/env/env.server.debug.ts.template +1 -5
  66. package/templates/app/env/env.server.develop.ts.template +1 -5
  67. package/templates/app/env/env.server.local.ts.template +1 -5
  68. package/templates/app/env/env.server.main.ts.template +1 -5
  69. package/templates/app/page/styles.css.template +3 -3
  70. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  71. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  72. package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
  73. package/templates/crudPages/new/page.tsx +1 -1
  74. package/templates/crudSinglePage/page.tsx +1 -1
  75. package/templates/env/_env.server.type.ts +7 -5
  76. package/templates/lib/db.ts +1 -1
  77. package/templates/lib/dict.ts +1 -1
  78. package/templates/lib/sig.ts +1 -1
  79. package/templates/lib/srv.ts +6 -2
  80. package/templates/module/__Model__.Util.tsx +1 -1
  81. package/templates/module/__Model__.Zone.tsx +3 -5
  82. package/templates/workspaceRoot/AGENTS.md.template +0 -971
  83. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  84. package/templates/workspaceRoot/biome.json.template +4 -239
  85. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  86. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  87. package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
  88. package/typecheck.proc.js +1 -1
  89. package/{workflow.command-z0e0fk0z.js → workflow.command-jj0cphfm.js} +16 -14
  90. package/{workspace.command-3ydyy991.js → workspace.command-ma3g994j.js} +71 -52
  91. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  92. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  93. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  94. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  95. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  96. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  97. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  98. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  99. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  100. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
  101. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
  102. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  103. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  104. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  105. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
  106. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  107. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  108. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  109. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  110. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  111. /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
@@ -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
@@ -0,0 +1,25 @@
1
+ {
2
+ "title": "MCP Exposure Rule",
3
+ "description": "How Akan signals reach AI agents over MCP — guard-derived exposure, refusals, prompts, and resources",
4
+ "group": "core",
5
+ "codegenPriority": 4,
6
+ "deprecated": false,
7
+ "scans": [
8
+ {
9
+ "type": "example",
10
+ "description": "Signal files whose slices and endpoints declare the guards MCP exposure is derived from",
11
+ "path": "{apps,libs}/*/lib/*/*.signal.ts",
12
+ "sample": 4
13
+ }
14
+ ],
15
+ "update": {
16
+ "filePath": "./mcpRule.instruction.md",
17
+ "contents": ["exposure rule", "configuration", "declaring endpoints", "refusals and wire behaviour", "prompts"],
18
+ "rules": [
19
+ "Use current Akan APIs only",
20
+ "State the rule before the reason, and keep the reason to one clause",
21
+ "Name the env spelling beside every option field",
22
+ "Keep it workspace-neutral — no app or repo names"
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,176 @@
1
+ # Akan MCP Exposure Guideline
2
+
3
+ ## Purpose
4
+ Use this when writing a `*.signal.ts`, configuring `option.setMcp(...)`, or debugging a tool an agent cannot see.
5
+ The always-loaded convention set carries the short version; this is the full contract.
6
+
7
+ ## Exposure Rule
8
+
9
+ Every signal is served to AI agents as an MCP server on `POST /mcp`. **`/mcp` is mounted by default and exposure
10
+ follows an endpoint's guards — there is no per-endpoint opt-in, and nothing to write in a signal file.** An endpoint
11
+ that declares a real guard is published; one that declares none is refused, and so is a mutation whose only guard is
12
+ `Public`. `AKAN_MCP=false` takes the whole surface off. The reasoning is that the guards are already the
13
+ authorization decision and `filterForAccount` re-reads them per caller on every listing, so a second per-endpoint
14
+ switch says nothing the guards do not — while guaranteeing that every endpoint added later is invisible to agents
15
+ until somebody remembers it.
16
+
17
+ ## Configuration
18
+ Settings live in the app's `lib/option.ts` — `option.setMcp({ … })`, taking `enabled`, `readOnly`, `path`,
19
+ `version`, `instructions`, `allowedOrigins`, `pageSize`, `language`, `legacyTextBlock`, and `auth`. **Not `main.ts`**: the gateway
20
+ there only spawns children, and `option.ts` is the app-authored file `server.ts` already hands to the process that
21
+ mounts `/mcp`. Every lib's option is read in mount order with the app's last, so an app tightens what a library
22
+ declared without restating it. Each field also has an env spelling (`AKAN_MCP`, `AKAN_MCP_READONLY`,
23
+ `AKAN_MCP_PATH`, `AKAN_MCP_VERSION`, `AKAN_MCP_INSTRUCTIONS`, `AKAN_MCP_ALLOWED_ORIGINS`, `AKAN_MCP_PAGE_SIZE`,
24
+ `AKAN_MCP_LANGUAGE`, `AKAN_MCP_LEGACY_TEXT`, `AKAN_MCP_AUTH_SERVERS`, `AKAN_MCP_SCOPES`, `AKAN_MCP_RESOURCE`) for a
25
+ deployment that configures what the source does not, which the option overrides.
26
+ The two booleans answer to `AKAN_PUBLIC_MCP` / `AKAN_PUBLIC_MCP_READONLY` too, the same pairing `AKAN_OPENAPI`
27
+ has, and a value written in code wins over the env of the same name — an explicit `undefined` is not a value.
28
+ `AKAN_MCP_PATH` is normalized to a leading `/`, because the route key and the OAuth metadata path are both built by
29
+ concatenation.
30
+
31
+ ## Declaring Endpoints
32
+ ```typescript
33
+ // apps/<app>/lib/option.ts
34
+ export const option = new AkanOption<ModulesOptions>().setMcp({
35
+ instructions: "Domain tools for this app. Start from taskInTodo.",
36
+ language: "en",
37
+ });
38
+ ```
39
+
40
+ ```typescript
41
+ // <model>.signal.ts — every one of these is an MCP tool or prompt, with no `mcp:` option anywhere
42
+ export class TaskSlice extends slice(
43
+ srv.task,
44
+ { guards: { root: Admin, get: SignedIn, cru: SignedIn } },
45
+ (init) => ({
46
+ // its own guards: the map above reaches base CRUD and the root slice, never a named slice — so a named slice
47
+ // that names none is refused rather than published, which is the one shape to watch for.
48
+ inTodo: init({ guards: [SignedIn] }).exec(function () {
49
+ return this.taskService.queryByStatuses(["todo"]);
50
+ }),
51
+ }),
52
+ ) {}
53
+
54
+ export class TaskEndpoint extends endpoint(srv.task, ({ mutation, prompt }) => ({
55
+ startTask: mutation(cnst.Task, { guards: [SignedIn] })
56
+ .param("taskId", ID)
57
+ .exec(async function (taskId) {
58
+ return await this.taskService.startTask(taskId);
59
+ }),
60
+ reviewTask: prompt({ guards: [SignedIn] })
61
+ .param("taskId", ID)
62
+ .exec(async function (taskId) {
63
+ const task = await this.taskService.getTask(taskId);
64
+ return [Msg.user(`Review this task and suggest next steps.`), Msg.resource(`akan://task/${taskId}`, task)];
65
+ }),
66
+ })) {}
67
+ ```
68
+
69
+ ## Refusals And Wire Behaviour
70
+ - **The refusals are fail-closed**: **an endpoint that declares no `guards` at all** (nobody decided who may reach
71
+ it), **a mutation with no real `guards`** (`[Public]` is having none, spelled out — it answers true
72
+ unconditionally), `pubsub` and `message` (their internal args read a socket an MCP request does not have), an
73
+ `Any` or `Upload` return, a file upload, and **an argument typed `Any` that must be filled**.
74
+ A `prompt` refuses two more, because its `arguments` is one string per name with no schema beside it: a **list
75
+ argument**, which could never carry a second value, and **any `Any` argument** — a tool leaves that out of its
76
+ schema, and a prompt has no schema to leave it out of.
77
+ - **Every refusal is named in the boot log**: one `warn` per endpoint plus a `MCP catalogue: tools=… prompts=…`
78
+ count. Read that line first when a tool you expected is missing — and it is the *only* place the answer exists,
79
+ because there is no absent opt-in to notice. The API explorer badges the same rule per endpoint (`MCP` /
80
+ `MCP refused`), from the same shared implementation the catalogue runs.
81
+ - **An `Any` argument is left out of the published schema** rather than described as `{}` — it tells a model
82
+ nothing — and a value sent for one is refused by name, so the endpoint reads it as omitted. That is what happens
83
+ to the root list's raw `query` descriptor: read as sent, it would be an arbitrary filter over every model you
84
+ publish. Declare a named filter slice when an agent should narrow a list.
85
+ - **A nullable model return publishes no `outputSchema`**, and its empty answer ships as the text `null` with no
86
+ `structuredContent`. That field is an object by definition, so `null` cannot ride in it any more than an array
87
+ can — a list is wrapped as `{ items: … }` for the same reason — and a declared schema obliges every result to
88
+ match it, so a client SDK throws on the first call that finds nothing. A nullable *list* keeps its schema, and a
89
+ scalar return has no structured half at all: it ships as the value itself, not as JSON.
90
+ - **An `outputSchema` names no `hidden`, `secret`, or `visual` field.** Every response has the first two stripped,
91
+ so publishing them promises a property no answer can carry — and on a model like `user` the names are the leak.
92
+ Your *input* schema keeps all three: they are legal to send, and the same model describes a request body.
93
+ - **A `field.visual` is stripped from every MCP result.** It is the marker for a field the page renders and no
94
+ question is answered from — a blur placeholder, a rendered HTML body — and it is cost rather than secrecy, so
95
+ nothing is refused over one. The strip happens in the MCP dispatcher rather than in `resolveReturn`, which every
96
+ ordinary HTTP response also passes through: the whole point is that a browser still receives it. That is also why
97
+ the readable schema drops it — a non-optional visual field would otherwise be listed `required` and a validating
98
+ client would refuse a result that correctly omits it.
99
+ - **A structured result ships twice by default**, once as `structuredContent` and once as the same JSON in the text
100
+ block, which is what the spec asks of a server for clients predating the structured field. It is also a flat
101
+ doubling of what every model-returning tool costs a model. `option.setMcp({ legacyTextBlock: false })` — or
102
+ `AKAN_MCP_LEGACY_TEXT=false` — leaves a one-line pointer in the text block instead, for a deployment whose
103
+ clients read the structured half. A scalar return is unaffected: it has no structured half to point at, so it
104
+ ships as the value itself either way.
105
+ - A refused endpoint answers the *same* "unknown tool" as one that does not exist. Never make that
106
+ message more helpful — the difference is what enumerates your private surface. A guard's refusal is generalized
107
+ the same way: the caller reads `You are not permitted to perform this action.`, never `Access denied by guard:
108
+ Admin`, which names your authorization structure to the one caller barred from it. A domain `Err` resolves
109
+ through the dictionary first and keeps its own words.
110
+ - The `readOnly` / `destructive` / `idempotent` hints a client renders are derived from the endpoint type and key
111
+ and are not configurable. Clients are told to distrust hints; they are never a gate.
112
+ - **`AKAN_MCP_READONLY=true` is the read-only-deployment valve, not the exposure switch.** It drops every mutation
113
+ whatever it declared, and reports each one in the boot log like any other refusal.
114
+ - OAuth resource metadata is published at `/.well-known/oauth-protected-resource` (and at that path plus the mount
115
+ path, the spelling most clients try first). `AKAN_MCP_AUTH_SERVERS`, `AKAN_MCP_SCOPES`, and `AKAN_MCP_RESOURCE`
116
+ configure it; `insufficient_scope` is enforced only once `AKAN_MCP_SCOPES` is set. A token carrying no `aud` at
117
+ all is refused once `AKAN_MCP_AUTH_SERVERS` names an issuer — that issuer mints tokens for its other resources
118
+ too — and accepted while none is named, because a first-party Akan token is bound by app and environment.
119
+ - **The boot log names every published entry with no dictionary `.desc()`.** An agent picks a tool by its
120
+ description, so a missing one is a broken tool. What the framework generates has no text of its own and borrows
121
+ the model's: the generated list reads the `.of()` label, and the base CRUD tools append the model's `.desc()` to
122
+ their generated `Get X`. Write that model `.desc()` — it is the only text those entries can carry. There is no
123
+ `akan quality scan` rule for this any more: a source scanner found the exposure only as an `mcp:` literal, and
124
+ with exposure derived from the guards the resolved catalogue is the only place that can answer.
125
+ - A browser-hosted client needs `allowedOrigins` **and** the CORS answer the server sends back for those origins.
126
+ Every other MCP client sends no `Origin` at all, and the one that does is matched against the forwarded host so
127
+ a proxy does not turn each call into a 403 — which is only as trustworthy as an edge that *overwrites* that
128
+ header. `AKAN_MCP_RESOURCE` pins the resource identifier where you cannot guarantee it.
129
+ - **A `resources/read` uri that does not decode** — a stray `%` — is `Unknown resource`, not a server failure.
130
+ - **A caller's own mistake is reported as one** and never as a server failure: an argument that is missing,
131
+ unparseable or **undeclared** comes back as `isError` naming it — `additionalProperties: false` travels in the
132
+ published schema and nothing on the wire enforces it — and so does a document that is not there, as
133
+ `No <model> found for the arguments given.` A `prompt`, having no `isError` to carry a refusal, answers `-32602`.
134
+ Only a real failure logs a stack; an agent can drive the rest at will.
135
+ - **Three revisions are spoken**: the modern `2026-07-28` and the legacy `2025-11-25` / `2025-06-18`, which are
136
+ wire-identical over the POST-only surface this implements — a client whose proposal is not listed is told to
137
+ disconnect. An unknown proposal is answered at whichever end of that list it is closer to, and an unimplemented
138
+ method answers `404` to a modern client but `200` to a legacy one, whose era spends `404` on "your session is
139
+ gone".
140
+ - **A modern-era request mirrors `MCP-Protocol-Version` and `Mcp-Method` into headers** (plus `Mcp-Name` when the
141
+ body names one), and one that leaves a mirror out is refused just like one that contradicts the body: a gateway
142
+ rule keyed on a header never fires for the request that omitted it. Legacy requests are not checked. Capabilities
143
+ are derived from the catalogue, so a server with no prompts does not advertise `prompts`.
144
+ - **An expired or wrongly-audienced bearer token is refused up front**, so an agent is told to authenticate rather
145
+ than that the tool does not exist. Its **signature is not checked** — that needs your app's own secret — so a
146
+ token signed wrong, like an opaque one, still degrades to an anonymous caller.
147
+ - **Resource URIs**: `akan://<model>/{id}`, `akan://<model>/light/{id}`, `akan://<model>/list` for the model's own
148
+ list, and `akan://<model>/list/<sliceKey>` for a slice's. The root list takes no third segment on purpose — any
149
+ token there is one a slice could also be named. **Those four are the whole set**, so only the generated reads are
150
+ addressable: a custom endpoint keeps its tool and gets no resource template.
151
+ - **The catalogue is one language**, `en` unless `language` says otherwise: it is built once at boot and cached by
152
+ clients, so there is no `Accept-Language` negotiation.
153
+
154
+ ## `prompt()`
155
+ **`prompt()`** is invoked by the *user* — a client renders it as a slash command — not chosen by the model. `exec`
156
+ returns `PromptMessage[]`, or a bare string that is wrapped into one user message; build them with `Msg.user` /
157
+ `Msg.assistant` / `Msg.link` / `Msg.resource` / `Msg.image` / `Msg.imageOf`. It takes `.param()` and `.search()`
158
+ only, because `prompts/get` sends a flat string map. **An embedded payload is masked by the model you name** —
159
+ `Msg.resource(uri, task, { model: cnst.LightTask })`, or `Msg.mask(cnst.LightTask, task)` for one piece of an
160
+ assembly. Taking the model as an argument is what makes a `{ ...doc }` spread maskable, since that and `toJSON()`
161
+ arrive with the class already gone; a value with no model named whose `hidden`/`secret` fields are populated is
162
+ **refused**, one level into a plain object too. **A `prompt` is also mounted as a
163
+ plain HTTP `GET` whether or not you enabled MCP**, because that route is what lets a web UI preview one — and it
164
+ is in your OpenAPI document like any other `GET`, answering the one fixed `PromptMessage[]` shape. MCP exposure
165
+ gates the catalogue, not the surface, so guard it
166
+ like any other read — and a prompt declaring no
167
+ `guards` at all is named in the boot log, while an explicit `[Public]` is a decision and stays quiet. Every `Msg` builder takes
168
+ optional `annotations` last (`audience`, `priority` 0..1, `lastModified`) — give the instruction a high `priority`
169
+ and its attachments a low one, or a client with a full window drops blocks by position and keeps the attachment
170
+ over the ask.
171
+
172
+ ## Progress Reporting
173
+ **`McpProgress.report(n, { total, message })`** reports progress from anywhere inside a call, a service or adapter
174
+ frames down included, and is a no-op when nobody is streaming — so the same code runs unchanged over HTTP, a
175
+ websocket, and in tests. `McpProgress.streaming` says whether anyone is reading, for a report whose message
176
+ costs something to assemble.
@@ -0,0 +1,24 @@
1
+ {
2
+ "title": "Workspace Onboarding",
3
+ "description": "Product-workspace orientation — layout, decision matrix, workflow recipes, and query gotchas",
4
+ "group": "core",
5
+ "codegenPriority": 2,
6
+ "deprecated": false,
7
+ "scans": [],
8
+ "update": {
9
+ "filePath": "./workspaceOnboarding.instruction.md",
10
+ "contents": [
11
+ "workspace layout and generated files",
12
+ "agent workflow and common commands",
13
+ "where-do-I-put-this decision matrix",
14
+ "workflow recipes",
15
+ "modeling and query gotchas"
16
+ ],
17
+ "rules": [
18
+ "Use current Akan APIs only",
19
+ "Write for someone building a product on Akan, not for someone changing Akan",
20
+ "Refer to the app as <%= appName %>; the renderer substitutes the workspace's first app",
21
+ "Do not repeat a rule that the conventions guideline already states"
22
+ ]
23
+ }
24
+ }