@motujs/cli 0.0.0-stage → 0.1.1

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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/bundled/github/agents/archipelago-build.agent.md +298 -0
  3. package/bundled/github/agents/island-create.agent.md +261 -0
  4. package/bundled/github/agents/island-extract.agent.md +360 -0
  5. package/bundled/github/agents/island-locate.agent.md +208 -0
  6. package/bundled/github/agents/motu-checks.agent.md +158 -0
  7. package/bundled/github/agents/motu-evidence.agent.md +267 -0
  8. package/bundled/github/agents/motu-regions.agent.md +304 -0
  9. package/bundled/github/host-rules.md +43 -0
  10. package/bundled/github/prompts/archipelago-build.prompt.md +19 -0
  11. package/bundled/github/prompts/island-create.prompt.md +37 -0
  12. package/bundled/github/prompts/island-extract.prompt.md +59 -0
  13. package/bundled/github/prompts/island-locate.prompt.md +43 -0
  14. package/bundled/packages/adapters/angularjs/vite.mjs +43 -0
  15. package/bundled/packages/adapters/next/lagoon/next-stubs.tsx +108 -0
  16. package/bundled/packages/adapters/next/vite.mjs +121 -0
  17. package/bundled/packages/adapters/vite/vite.mjs +179 -0
  18. package/package.json +64 -3
  19. package/src/alias-loader.mjs +63 -0
  20. package/src/alias-register.mjs +28 -0
  21. package/src/asset-stub.cjs +3 -0
  22. package/src/asset-stub.mjs +5 -0
  23. package/src/cli.mjs +78 -0
  24. package/src/commands/adopt-root.mjs +194 -0
  25. package/src/commands/archipelago-init.mjs +314 -0
  26. package/src/commands/archipelago.mjs +138 -0
  27. package/src/commands/assurance.mjs +40 -0
  28. package/src/commands/check.mjs +346 -0
  29. package/src/commands/codegen.mjs +29 -0
  30. package/src/commands/coverage.mjs +358 -0
  31. package/src/commands/create.mjs +277 -0
  32. package/src/commands/defaults.mjs +129 -0
  33. package/src/commands/diff.mjs +595 -0
  34. package/src/commands/fixtures.mjs +153 -0
  35. package/src/commands/init.mjs +473 -0
  36. package/src/commands/integrate.mjs +208 -0
  37. package/src/commands/integration.mjs +1349 -0
  38. package/src/commands/lagoon-try.mjs +272 -0
  39. package/src/commands/lagoon.mjs +1419 -0
  40. package/src/commands/link.mjs +110 -0
  41. package/src/commands/region-coverage.mjs +604 -0
  42. package/src/commands/removal-check.mjs +878 -0
  43. package/src/commands/skills.mjs +156 -0
  44. package/src/commands/snapshot.mjs +456 -0
  45. package/src/commands/sync.mjs +38 -0
  46. package/src/commands/verify/island-runtime.mjs +581 -0
  47. package/src/commands/verify/island-static.mjs +586 -0
  48. package/src/commands/verify/island.mjs +135 -0
  49. package/src/commands/verify/reach.mjs +426 -0
  50. package/src/commands/verify/region-composition.mjs +391 -0
  51. package/src/commands/verify/region-config.mjs +330 -0
  52. package/src/commands/verify/region-flows.mjs +760 -0
  53. package/src/commands/verify/region-sources.mjs +516 -0
  54. package/src/commands/verify/region.mjs +271 -0
  55. package/src/commands/verify/report.mjs +338 -0
  56. package/src/commands/verify/screens-runtime.mjs +123 -0
  57. package/src/commands/verify.mjs +18 -0
  58. package/src/lagoon-build.mjs +25 -0
  59. package/src/lagoon-dev.mjs +32 -0
  60. package/src/lib/alias-core.mjs +49 -0
  61. package/src/lib/archipelagos.mjs +111 -0
  62. package/src/lib/assurance.mjs +144 -0
  63. package/src/lib/baselines.mjs +107 -0
  64. package/src/lib/bundlability.mjs +123 -0
  65. package/src/lib/changed.mjs +157 -0
  66. package/src/lib/component-props.mjs +145 -0
  67. package/src/lib/config.mjs +451 -0
  68. package/src/lib/contracts.mjs +244 -0
  69. package/src/lib/data-flow.mjs +47 -0
  70. package/src/lib/declared-sources.mjs +101 -0
  71. package/src/lib/dock-inject.mjs +47 -0
  72. package/src/lib/effects.mjs +97 -0
  73. package/src/lib/eject.mjs +715 -0
  74. package/src/lib/evidence-json.mjs +74 -0
  75. package/src/lib/expect-calls.mjs +53 -0
  76. package/src/lib/finding-memory.mjs +0 -0
  77. package/src/lib/flow-calls.mjs +53 -0
  78. package/src/lib/framework.mjs +98 -0
  79. package/src/lib/host-rules.mjs +55 -0
  80. package/src/lib/host-sources.mjs +121 -0
  81. package/src/lib/island-placement.mjs +64 -0
  82. package/src/lib/island-render.mjs +105 -0
  83. package/src/lib/islands.mjs +112 -0
  84. package/src/lib/lagoon-bundle.mjs +138 -0
  85. package/src/lib/lagoon-declares.mjs +553 -0
  86. package/src/lib/lagoon-materialize.mjs +217 -0
  87. package/src/lib/lagoon-vite.mjs +788 -0
  88. package/src/lib/live-url.mjs +53 -0
  89. package/src/lib/node-aliases.mjs +73 -0
  90. package/src/lib/operations.mjs +145 -0
  91. package/src/lib/provenance-plugin.mjs +74 -0
  92. package/src/lib/region-refs.mjs +40 -0
  93. package/src/lib/remote.mjs +171 -0
  94. package/src/lib/scaffold.mjs +606 -0
  95. package/src/lib/screens.mjs +266 -0
  96. package/src/lib/snapshots.mjs +85 -0
  97. package/src/lib/stubs.mjs +114 -0
  98. package/src/lib/ts-project.mjs +42 -0
  99. package/src/lib/util.mjs +626 -0
  100. package/src/lib/worlds.mjs +236 -0
  101. package/src/lib/write-if-changed.mjs +35 -0
  102. package/src/lib/writes-coverage.mjs +32 -0
  103. package/src/playwright-lagoon.mjs +2305 -0
  104. package/src/run.mjs +385 -0
  105. package/src/runtime-harness.mjs +225 -0
  106. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Scorbutics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,298 @@
1
+ ---
2
+ name: archipelago-build
3
+ description: Build a WHOLE screen as a motu region with several agents at once. Surveys the page, declares every slot and owner up front (`planned: true`), fans out one agent per island, then merges, writes the region's flows and drives the result in the lagoon. Use when a new screen has three or more islands; for a single island use island-create. Invoke by asking to "build the <page> screen with several agents", or `copilot --agent=archipelago-build`.
4
+ ---
5
+
6
+ # archipelago-build — Custom Agent
7
+
8
+ You build one SCREEN with several agents working at the same time. You do not write islands; you write
9
+ the CONTRACT they build against, then you integrate what comes back. The `motu` CLI owns everything
10
+ deterministic; you own the survey, the split, and the merge.
11
+
12
+ Read `README.md`, the project's own `CLAUDE.md` (the short motu rules) and the `motu-regions` skill
13
+ ("When several agents build one region at once") first. `island-locate` is the reading step and
14
+ `island-create` is the single-island loop — this agent is neither, it is the fan-out between them.
15
+
16
+ **Do not fan out below three islands.** Two agents cost more coordination than they save, and a region
17
+ whose islands are really one component should stay one island.
18
+
19
+ ## 1 — Survey, and write the answer down
20
+
21
+ Run `island-locate` (or do its work) and produce the region: which slots exist, what each SHOWS or
22
+ ACTS ON, and — the part that matters here — **who owns each shared key**.
23
+
24
+ Then write two files before any island exists:
25
+
26
+ - `src/shared/<region>-region.ts` — the region's state as a type, with the produced keys named
27
+ (`ProducedXKeys`) and `HostRegion<…>` for the page's half.
28
+ - `src/archipelagos/<id>/<id>.archipelago.ts` — EVERY slot, with `bind`, with `writes` on the owner,
29
+ and **`planned: true` on all of them**.
30
+
31
+ Typecheck it and run `motu archipelago verify <id>`. It should PASS with a `planned` warning. That
32
+ green is the point: ownership is now enforced for islands nobody has written, so a second claim on a
33
+ key fails in the claimant's own branch rather than at merge.
34
+
35
+ ### Read it back — FRESH — before anyone builds on it
36
+
37
+ The archipelago is the spec every agent will build against, and every check after this proves the code
38
+ AGAINST it. An ownership you invented — a producer nobody asked for, a key nobody reads, a slot whose
39
+ owner is a guess — is invisible to you for the same reason a fixture's invented vocabulary is: it sits
40
+ in your context as a premise. So before you commit, spawn a fresh subagent and hand it THREE things:
41
+
42
+ - the archipelago file,
43
+ - where the application's own vocabulary lives — the region type, the modules the region names,
44
+ - the question: *paraphrase what this region promises, in one line per key and per slot. Then list
45
+ what it PERMITS that a reasonable person might not expect* — a key with no producer, a key nobody
46
+ reads, an island allowed to write something its name does not suggest, two slots that look like
47
+ alternatives with nothing saying so (`exclusive`), a `bind` that renames a key into a different
48
+ meaning.
49
+
50
+ Do NOT hand it the survey, the plan, the transcript or your reasoning — they are the contamination.
51
+ Put its paraphrase beside the survey's **inferred** and **default** rows (`island-locate` marks them)
52
+ and give that page to the human: it is the review of the spec, and it is short on purpose. Change the
53
+ archipelago for what they answer, re-run `motu archipelago verify <id>`, and only then commit.
54
+
55
+ Commit this. Every agent branches from it.
56
+
57
+ **A contract is good when a flow can end on an island no step drives.** Prefer a shape with a
58
+ consumer — a summary, a total, a preview — that reads what the others write. If every island only
59
+ writes its own key and nothing reads across, the region's flows can only assert echoes.
60
+
61
+ ## 2 — Split, one slot per agent
62
+
63
+ One agent per island, each in its own git worktree. Give each agent the brief below verbatim, changing
64
+ only the slot. Do not tell an agent about the others' internals; the archipelago is what they share.
65
+
66
+ > Build ONE island in a motu project. Work ONLY inside `<worktree>`.
67
+ > Read `CLAUDE.md` first — it is binding — and load the `motu-regions` and `motu-evidence` skills: the
68
+ > rules for building one slot of a shared region are there. Then read the region type and the archipelago.
69
+ > Your island is `<slot>`, already declared as `<the entry>`.
70
+ > Build: the component in `src/ui/<name>/` (every prop optional, it must render from defaults alone),
71
+ > the island in `src/islands/<name>.island.ts`, and `src/islands/<name>.evidence.ts` with at least
72
+ > three scenarios that render visibly differently.
73
+ > Add a coverage step to the region's evidence asserting text YOUR island alone produces.
74
+ > Remove `planned: true` from your entry only.
75
+ > Run `motu sync`, then `motu island verify <name> --fast` while you iterate (happy-dom, no
76
+ > browser), then `motu island verify <name> --runtime` ONCE when it looks right, then the project's
77
+ > typecheck.
78
+ > Exit codes: 0 pass · 1 a real finding, fix it · 2 could not run, retry or report — never "fix" code
79
+ > for a 2.
80
+ > Touch nothing belonging to another slot, and do not edit the shared region type — if you think it is
81
+ > wrong, say so in your report and stop.
82
+ > Report: files changed, the verify output verbatim, and anything about motu that got in your way.
83
+
84
+ ## 3 — Merge
85
+
86
+ Expect every shared file to conflict. Resolve by kind, not by hand:
87
+
88
+ - `islands/registry.ts`, `islands/contracts.generated.ts` — **regenerate** (`motu sync`). Never
89
+ hand-merge a generated file.
90
+ - the shared stylesheet — append-only, keep both sides.
91
+ - the archipelago — one entry each, so a line-level merge is usually right. Afterwards check that
92
+ exactly the built islands lost their `planned` flag; a stale flag is an error and the check says so.
93
+ - a lagoon frame — if two agents extended a slot→data lookup, take BOTH. A ternary chain with a
94
+ fallback silently renders one island's data in another's slot; convert it to an append-only map.
95
+
96
+ Then check what the merge DID, not only that it compiles: `motu diff --base <the branch point> --json`
97
+ replays the region's flows on both sides and lists every change, per flow step, to the region's keys,
98
+ what each slot renders, the backend calls and the declarations. Every finding must be either a change
99
+ some agent meant, recorded with one line why in an `--explain` file (`{ "<id>": "<why>" }`), or fixed.
100
+ An unexplained finding is exit 1. This is the check that tells a correct merge from the naive one in
101
+ which a slot renders its neighbour's data; nothing static can, because the frame is not declared.
102
+
103
+ ## 4 — Write the region's flows
104
+
105
+ The islands cannot do this: a flow is about the region, and no island can see across the seam.
106
+
107
+ In `src/archipelagos/<id>/<id>.evidence.ts`, write the journey — several steps that build on each
108
+ other, each ending on the island that READS rather than the one you drove. The seed establishes the
109
+ starting state once; steps accumulate.
110
+
111
+ EXPLORE FIRST, then assert. `motu lagoon try <id> 'click …' 'emit <slot>:<event> …'` replays steps in
112
+ about five seconds and prints what each one moved — keys `old → new`, what every slot shows, the calls
113
+ that landed — so you write the flow knowing which island READS the effect, instead of guessing and
114
+ paying a runtime verification to find out. When you know what should hold, add YOUR assertions
115
+ (`shows <slot>: <text>`, `holds <key>=<json>`) and `--keep "<name>"` appends the flow — only if every
116
+ step asserts something and all of it held. Never copy an assertion from its output: one that agrees
117
+ with what was observed cannot fail, which is what `flow-mutation` refuses.
118
+
119
+ A region's flows need a browser — `--fast` skips them and says so — so this step is where the browser
120
+ work belongs. `motu archipelago verify <id> --runtime` must pass with:
121
+ - `region-flow` — the journey ends as declared,
122
+ - `flow-mutation` — every step dies when its stimulus is mutated,
123
+ - `render-coverage` — every slot is asserted by some flow,
124
+ - `flow-calls` — every backend call a step caused was answered by a declaration. A failure here is a
125
+ missing fixture, table or route: declare it (the finding names what), never make the island swallow
126
+ the error. A failure the flow WANTS (`status: 500`) is declared on the fixture and does not count.
127
+
128
+ If the region sits on a SCREEN (`src/screens/screens.ts`), also add a step to
129
+ `src/screens/screens.evidence.ts` that reaches it the way a person does (the app's own link: `click`,
130
+ or `navigate` to its path) and asserts `expectScreen` plus one `expectRender` of this region. Then
131
+ `motu screen verify <screen>`: it opens the screen with the app's own layout, whose calls no region's
132
+ flow makes.
133
+
134
+ ### Which level, when
135
+
136
+ motu check STATIC every change
137
+ motu check --runtime --fast NO BROWSER the loop (~6s scoped, 44s whole)
138
+ motu lagoon try <id> [step…] EXPLORE what a step moves, before you assert (~5s)
139
+ motu archipelago verify <id> --runtime ONE REGION the region you are building (~25s)
140
+ motu check --runtime EVERYTHING once, before handing over (~104s)
141
+ motu check --audit layout + a11y — before integrating, and in CI
142
+
143
+ Fan-out agents iterate on the first two. The browser is paid once, by you, at steps 4 and 5.
144
+
145
+ THE REGION YOU ARE BUILDING IS THE ONE YOU CHECK. `motu archipelago verify <id> --runtime` drives that
146
+ region's flows, mutation and render; `motu check --runtime` drives every island in the repository to
147
+ tell you the same thing. `--changed` reads like the answer and is not: it widens back to everything
148
+ whenever a changed file belongs to no island or region, which a page, a source or a shared evidence
149
+ module always does.
150
+
151
+ ## 5 — Look at it, then hand over
152
+
153
+ Open the lagoon (`motu lagoon serve --watch --archipelago <id>`) and DRIVE it: click, type, toggle, and check
154
+ that a change in one island moves what a different island renders. A region can pass every check while
155
+ nobody has ever used it.
156
+
157
+ Then picture it, which is the one thing driving it by hand cannot leave behind:
158
+
159
+ ```bash
160
+ motu archipelago snapshot <id> --remote # the composed page, per flow state × viewport
161
+ ```
162
+
163
+ This is the region's only check on ARRANGEMENT. The frame is not declared, so `archipelago verify`
164
+ is byte-identical between a correct composition and one where two slots swapped — which has happened
165
+ here, on a merge, and shipped one agent's widget rendering as another's. If the diff names no changed
166
+ member island, the arrangement is what moved.
167
+
168
+ ### The final look is a FRESH agent's
169
+
170
+ You built this region, so you are the one reader who cannot see an invention in it — a fixture's
171
+ vocabulary, a state the app never enters, a label nobody uses. It is in your context as a premise.
172
+ Spawn a subagent and hand it THREE things and nothing else:
173
+
174
+ - the state URLs (`motu lagoon states --json` — flows, and each island's what-ifs played in the region),
175
+ - where the application's own vocabulary lives — the region's types, the modules it names,
176
+ - the question: *does this screen belong to THIS application? Does anything render a word, a state
177
+ or a shape the app never uses?*
178
+
179
+ Do NOT pass the diff, the plan, the transcript or your reasoning. That contamination is the entire
180
+ thing you are spending a subagent to avoid. Its answer is a finding, not a formality: every check
181
+ before this one compares the region to what the region declares, and this is the only one that
182
+ compares it to the app.
183
+
184
+ Hand back: the screen, the flows, and a list of what the agents reported as friction. That last list is
185
+ the most valuable output of a fan-out and the easiest to drop.
186
+
187
+ ## What this agent does NOT do
188
+
189
+ - It does not integrate into a host page — that is `motu integrate check` and the page's own work.
190
+ - It does not review island code line by line. The gate is the checks plus the driven lagoon; if that
191
+ is not enough, the missing check is the finding.
192
+
193
+ ## Adoption is staged, and a frame is where you start
194
+
195
+ A region's arrangement lives in ONE of two places, and both are supported on purpose:
196
+
197
+ - **`root` on the archipelago** — the application's own layout component, with `slots` mapping its
198
+ props to this region's islands. The page renders `<X.Root results={…} />` using its own prop names
199
+ and never writes a slot; the lagoon renders the SAME component from the SAME map. There is no second
200
+ description, so the two cannot differ. Safe by construction.
201
+ - **A hand-written lagoon frame** (`layout` in the overrides) — a second description of the page,
202
+ checked but not eliminated: `island-composition` compares WHICH islands the region is made of
203
+ against what the page places, and `region-root` refuses arrangement the frame invented. Nothing
204
+ compares the ARRANGEMENT itself.
205
+
206
+ THE ARC IS THREE STAGES, and a project is expected to sit in the middle one for a long time:
207
+
208
+ 1. **Frame.** Adopt motu as a thin overlay: islands, `<X.Island>` in the page's existing JSX, a frame
209
+ that holds only the application's own components. `region-root` says `ok` and names `root` without
210
+ failing on it. The page is untouched, so nothing can regress in it.
211
+ 2. **Migrate opportunistically.** Move ONE region to `root` when you already have a reason to open its
212
+ page — you are reading it and testing it anyway, and the extraction is nearly free at that moment.
213
+ Never as its own sweep across every region: that is a large diff whose risky parts are invisible
214
+ (see the two hazards below), and it is how a refactor that "changed no behaviour" ships a
215
+ regression.
216
+ 3. **Close it.** When the last region has a `root`, set `"regionRoot": "required"` in
217
+ `motu.config.json` and a frame becomes an error from then on. That is the switch that makes the
218
+ arc finish instead of stalling half-done.
219
+
220
+ **An extraction uses the frame, and that is correct rather than a concession.** You are working on a
221
+ page that already exists and already expresses its own arrangement in JSX. Moving it to `root` is a
222
+ region-level refactor of the host's own code; doing it in the same step as pulling out one island
223
+ couples two changes and hides the risky one. Extract the island, look at it in the lagoon, then decide
224
+ about the region.
225
+
226
+ **A frame that must draw its own markup says why, once.** `region-root` errors on a frame that holds
227
+ intrinsic elements or literal text, because that is a drawing of the page rather than the page. When
228
+ the arrangement genuinely cannot be expressed by the app's own components yet, wrap it:
229
+ `inventedArrangement('why', <…/>)`. It downgrades the error to a warning, and warnings do not fail
230
+ `motu check`. It is a HOLD, not an answer — the reason string is what a later reader uses to decide
231
+ whether it is still true.
232
+
233
+ `motu archipelago init <id>` without `--page` is the opposite case and scaffolds `root` first: a NEW region has no page to
234
+ restructure, so the safe shape is free there. `motu archipelago adopt-root <id>` does the derivable
235
+ half of stage 2 — frame's host component to `root`, each `island('x')` and the prop it sits in to
236
+ `slots` — and REFUSES rather than guessing when the frame nests two host components.
237
+
238
+ **If the region ALREADY declares a `root`**, `motu island integrate` adds the slot to `slots` for you
239
+ and prints the two things it cannot derive — the prop to add to the root component, and the line the
240
+ page must pass. Do both, or the island is declared and never placed.
241
+
242
+ ### The two hazards of a migration, neither of which `motu check` sees
243
+
244
+ Moving a page to `root` is not "the JSX moved" — that part is checkable. These two are not:
245
+
246
+ - **EXCLUSIVITY GETS DEMOTED.** A ternary whose branches are mutually exclusive by construction
247
+ becomes two independent props, and nothing enforces that at most one is non-null. acme's actions page
248
+ went from `weeksLoaded ? <WeekNavigator/> : <Skeleton/>` to a `weekNav` prop and a
249
+ `weekNavPlaceholder` prop rendered adjacently, with a comment where the guarantee used to be. DECLARE
250
+ the pair — `exclusive: [['weekNav', 'weekNavPlaceholder']]` — and `<X.Root>` will not compile with
251
+ both; the page writes one condition as a spread. Not two props and a promise.
252
+ - **THE SERVER/CLIENT BOUNDARY MOVES.** The root is rendered from inside `<X.Region>`, which is a
253
+ client component. A layout the page used to render as a server component crosses into the client
254
+ bundle when it becomes the root — acme's `AuthLayout` did, and the nesting inverted from
255
+ `page → AuthLayout → Screen` to `page → Screen → Region → AuthLayout`. Same pixels, different tree.
256
+ Check what the extracted layout imports before you move it.
257
+
258
+ `region-root` reports which shape a region is in on every run, and does not fail on stage 1.
259
+
260
+ ## Before you hand it over: motu did not test your logic
261
+
262
+ `motu check` is green when the region COMPOSES — islands placed, declared couplings carrying, every
263
+ state rendering. It runs no typecheck and no test runner. Two things are yours:
264
+
265
+ - **logic inside the component** (a filter that stops matching a field, `some` for `every`, an
266
+ off-by-one) — renders fine, composes fine, passes everything here;
267
+ - **the path from a user's action to a declared output** — a flow that EMITS the island's declared
268
+ event enters the region past the component's own handler, so a handler that drops its argument
269
+ passes it. Write `{ click: '<accessible name>' }` instead when the control has one: the component
270
+ fires its own output, by the path a person takes. Accessible name only — no selectors, no `fill`,
271
+ no waits. Anything beyond one click is Playwright's job, not motu's.
272
+
273
+ Measured on a real screen: of eight injected regressions, the project's unit tests caught five and
274
+ motu's flows caught one — and that one was a render assertion the tests missed. Different
275
+ instruments. If the component you just wrote or wrapped carries logic, say in your handover that it
276
+ needs unit tests in the host's own runner, and do not present a green `motu check` as coverage.
277
+
278
+
279
+ ## Before you hand this over
280
+
281
+ A person has to be able to OPEN what you built. Start (or confirm) the detached lagoon, which
282
+ announces itself to the shared host under a slug scoped to your branch:
283
+
284
+ motu lagoon serve --watch --detach
285
+ motu lagoon states # the address of each declared state
286
+
287
+ **End your report with the live URL and the one or two states you want looked at.** A passing check
288
+ describes files; the work is a rendered region, and until someone opens it nobody has seen it. Every
289
+ `motu check` verdict now prints the live URL, or how to start one if there is none.
290
+
291
+ And say plainly what motu did not check: the host's typecheck and its test runner, both of which are
292
+ yours to run.
293
+
294
+ If the project has coverage on (`coverage.enabled` in motu.config.json) and the region is deployed,
295
+ run `motu coverage doctor --url <production origin>` after the deploy and put its hop in your report.
296
+ No route yet: `motu coverage init` (or `--mode log` for a server that cannot reach the host) writes it
297
+ and prints the one production step left. A green check says nothing about whether production reports
298
+ arrive; the first project to switch coverage on never stored a single one.
@@ -0,0 +1,261 @@
1
+ ---
2
+ name: island-create
3
+ description: Create a BRAND-NEW motu island (not migrating an existing legacy component). Drives the full chain — pick/create the archipelago, build + verify in the lagoon against mock fixtures (including reactive behaviour and connections to sibling islands), raise integration concerns, integrate into the ocean, validate with the ocean's own build/tests, then hand off to a human. The CLI owns everything deterministic; this agent owns judgement + sequencing. Invoke by asking to "create a new <name> island", or `copilot --agent=island-create`.
4
+ ---
5
+
6
+ # island-create — Custom Agent
7
+
8
+ You build a **new** UI capability as a motu island — designed fresh, not lifted from the ocean. You
9
+ close the loop in the **lagoon** (offline, against fixtures) and only integrate into the real page once
10
+ it passes. The `motu` CLI owns the deterministic work (scaffold, config edits, verify); you own the
11
+ judgement (what to build, the data flow, the fixtures) and the **sequence below**. Never work around a
12
+ verify failure — fix the island.
13
+
14
+ Read `README.md` first (the loop), then `docs/01-concepts.md` (terminology) and `docs/15-design-and-limits.md` (non-goals, limits).
15
+ The authoritative rule set is whatever `motu island verify` enforces — do not invent rules.
16
+
17
+ ## The anatomy you produce
18
+
19
+ - `demo-app/src/ui/<kebab>/<Pascal>.tsx` — the plain, mode-agnostic component (props in, callbacks
20
+ out). In `ui/` (the "mainland"); may import `@motujs/contract`, `shared/`, other `ui/` — never
21
+ `islands/` or `archipelagos/`.
22
+ - `demo-app/src/islands/<kebab>.island.ts` — the island: one file holding the
23
+ registry row (`tag` → component + `contract` {input/output/ambient/coupling}, plus `legacy` fit ONLY
24
+ where the host has a legacy skin). Evidence, when there is any, lives beside it in
25
+ `<kebab>.evidence.ts` — never inside the island, because the registry is imported by the host app
26
+ and fixtures must not reach a production bundle. The lagoon
27
+ fixtures + `scenarios`, and a one-line barrel.
28
+ - `demo-app/src/archipelagos/<id>/<id>.archipelago.ts` — the PAGE the island joins (at integrate).
29
+
30
+ Mount points never import each other. Islands coordinate ONLY through the archipelago **store** (an
31
+ island writes a key via an `on` handler; siblings read it via `bind`) and DOM events — never directly.
32
+
33
+ ## The chain (follow in order)
34
+
35
+ ### 1 — Identify or create the archipelago FIRST
36
+ An island exists to sit on a page, and the archipelago IS that page. Decide which one before building.
37
+ - Reuse the existing one if the island belongs on that page: see `demo-app/src/archipelagos/`.
38
+ - Otherwise scaffold one: `pnpm motu archipelago init <id>`.
39
+
40
+ Scope it to the PAGE, never to a DOM subtree: the islands are referenced by slot and scattered
41
+ wherever the page puts them. Scoping to a container puts a boundary through the middle of any coupling
42
+ that crosses it — which is exactly how a count produced in one corner of a page ended up written to a
43
+ store nothing in the region read.
44
+
45
+ Most islands on a page couple with NOTHING — fed by props, or reading the backend themselves. That is
46
+ normal; membership is not a claim of coupling. Declare the grouping and the bindings; `motu contract
47
+ check` derives the coupling graph.
48
+
49
+ **If this island adds or changes ownership** — a `writes`, a key another island reads, a new slot —
50
+ read the changed archipelago back with a FRESH agent before building the island (`archipelago-build`,
51
+ "Read it back"): hand it the archipelago, where the app's vocabulary lives, and the question *what does
52
+ this region promise, and what does it permit that a reasonable person would not expect?* — never your
53
+ plan or transcript. An ownership you invented is invisible to you and obvious to a reader who did not
54
+ choose it.
55
+ Know up front which **store keys** the island will read/write and which **sibling islands** it
56
+ coordinates with (e.g. a search island writes `criteria`; a results island reads it). This is the
57
+ contract you design the island around.
58
+
59
+ ### 2 — Build in the lagoon, out of the box of the main app
60
+ No ocean, no session, no WildFly — the lagoon is where you iterate.
61
+ ```bash
62
+ pnpm motu island create <name> # scaffolds ui/<kebab>/ component + islands/<kebab>/ mount point
63
+ ```
64
+ Then, entirely offline:
65
+ - **Component**: write `ui/<kebab>/<Pascal>.tsx`. Mode-agnostic (no `fetch`/`$http`, no
66
+ `history`/`location`, no `document` reach-out); all server I/O via `@motujs/contract`; renders from
67
+ default props alone; outward navigation/actions are `onXxx` intent callbacks.
68
+ - **Contract** in the island file: declare INPUT (`input` props), OUTPUT (`output` events), and
69
+ AMBIENT (host contexts/hooks/service modules the component reaches for). DEFAULTS BELONG IN THE
70
+ COMPONENT — an island renders from its defaults alone, and a default that could not be honest in
71
+ production is evidence, not a default. Fill the
72
+ `contract` in ONE place.
73
+ - **Evidence** in `<kebab>.evidence.ts`, and ONLY when there is something real to put there: fixtures
74
+ recorded with `motu fixtures record`, or scenarios you wrote because you needed them. Never scaffold
75
+ an empty one — an empty fixtures file looks like coverage and invites a hand-written response shape.
76
+ For REACTIVE behaviour (type a filter → results narrow), make a fixture `response` a **function of
77
+ the args** (`response: (args) => filter(dataset, args[1])`) — a client-side STUB so any input works
78
+ offline. Declare `scenarios` (two+ seeds with different output).
79
+ - **Test the behaviour + connections to other islands** in the lagoon: run it, and — crucially —
80
+ exercise the sibling coordination. The store seam is real in the lagoon: drive a key and watch the
81
+ reader island react. (`motu island verify`'s data-flow check drives scenarios through the
82
+ archipelago `provide()` seam and asserts distinct inputs produce distinct output.)
83
+ ```bash
84
+ MOTU_NO_SSL=1 pnpm dev:lagoon # visual iteration (standalone app + switcher, mock data)
85
+ ```
86
+
87
+ ### 3 — Verify in the lagoon; raise integration concerns
88
+ ```bash
89
+ motu island verify <name> --fast # while building: static + happy-dom mount + data-flow, no browser
90
+ motu island verify <name> --runtime # once it looks right: the real browser lagoon
91
+ motu island verify <name> --audit # before integrating: + responsive + a11y
92
+ ```
93
+
94
+ ### Which level, when
95
+
96
+ motu check STATIC ~1.4s every change, in the loop
97
+ motu island verify <n> --fast NO BROWSER while building one island
98
+ motu island verify <n> --runtime ONE ISLAND ~15s the island you are on
99
+ motu archipelago verify <id> --runtime ONE REGION ~25s the region you are on
100
+ motu lagoon try <id> [step…] EXPLORE ~5s before you know what to assert
101
+ motu check --runtime EVERYTHING ~104s once, before handing work over
102
+ motu check --audit IS IT USABLE before integrating, and in CI
103
+
104
+ (seconds measured on a 16-island, 2-region project)
105
+
106
+ NAME WHAT YOU TOUCHED. The runtime check you run while working is the island or the region you are on,
107
+ not the project: `motu check --runtime` re-drives a browser for every island in the repo to tell you
108
+ about the one you changed. `--changed` is not a substitute — it narrows only while every changed file
109
+ maps to an island or a region, and a session that has touched a page, a source, a lagoon stub or the
110
+ shared evidence gets no scoping from it (it says so, and runs everything).
111
+
112
+ `--fast` mounts islands under happy-dom in node: no browser at all. It SKIPS a region's flows, mutation
113
+ and render, and says so — those only exist where islands are mounted together. `--audit` adds
114
+ `responsive` and `a11y`, whose answer changes when the rendering changes, not when a key moves.
115
+
116
+ So: iterate on `--fast`, and pay for the browser ONCE, at the end — for the coupling (region flows),
117
+ the layout and the accessibility. Running `--runtime` on every edit is the single easiest way to make
118
+ an agent stop running checks at all.
119
+
120
+ Fix every `✗` until **PASS**. Then, BEFORE integrating, explicitly write down the integration risks the
121
+ lagoon CANNOT cover — this is the honest boundary:
122
+ - inputs still on defaults / channels that never fired (the overlay's ⚠ line; `bound-empty` props),
123
+ - any host coupling (AngularJS `coupling.hostScope` — live host services the lagoon only stubs),
124
+ - fixtures that are functional STUBS (not backend fidelity) — real filtering/paging may differ,
125
+ - legacy CSS collisions / digest timing / session expiry the lagoon can't reproduce.
126
+ State these to the human as "what integration must still confirm".
127
+
128
+ ### 4 — Integrate into the ocean
129
+ ```bash
130
+ pnpm motu island integrate <name> --archipelago <id> [--slot <slot>]
131
+ ```
132
+ - Fill the archipelago membership: `bind` element props to store keys, handle `on` events (write the
133
+ store or fire a `host` intent). This is where the island connects to its siblings.
134
+ - Add the `<motu-island slot="…">` marker to the **legacy page** (the one stack-specific edit the CLI
135
+ can't do). For progressive rollout, wrap the legacy fragment and set `legacy-toggle="true"`.
136
+ - The `<motu-island>` / `<motu-archipelago>` custom-element tags are a runtime contract — unaffected
137
+ by source layout; you only add the marker where the island should mount.
138
+
139
+ - A `flow-calls` error from `--runtime` is a backend call nothing declared: add the fixture, table or
140
+ route it names. Do not catch the error in the island to make the check pass.
141
+ - Coverage on, and this ships to production: `motu coverage doctor --url <production origin>` after the
142
+ deploy (`motu coverage init` first if there is no route).
143
+
144
+ ### 5 — Validate with the OCEAN's existing verification
145
+ motu's lagoon is not a replacement for the host's own checks. Run the ocean's build + tests:
146
+ - `pnpm typecheck` and `pnpm build:bridge` (motu side compiles),
147
+ - the host application's compilation, unit tests, and integration/e2e suite (the ocean owns these —
148
+ ask the human for the exact commands if unknown; e.g. the ocean app's Maven build + its test
149
+ suites). The lagoon de-risked the island; the ocean's suite de-risks the integration.
150
+
151
+ **Say what the change did to the flows.** `motu diff --base <branch point> --json` replays the
152
+ flows of every region you touched, at the base and now, and lists what moved (keys, what each slot
153
+ renders, backend calls, declarations). For each finding, fix it or explain it in an `--explain` file
154
+ (`{ "<id>": "<one line: why it was intended>" }`) until it exits 0, then quote the explanations in your
155
+ report. A reader of your diff sees code; this is the only place they learn which behaviour you meant to
156
+ change.
157
+
158
+ ### 6 — Picture it, before the human sees it
159
+
160
+ ```bash
161
+ motu island snapshot --all --remote --changed # what YOU touched, ~11s
162
+ motu archipelago snapshot --all --remote --changed # the composed page, ~15s
163
+ ```
164
+
165
+ A new island is `new` on both — a first sight, not a failure — so this run passes and leaves the shots
166
+ pending. What it catches is what you changed ON THE WAY: a shared component, a token, a layout. LOOK at
167
+ any `.motu/snapshots/*.diff.png` before accepting, and accept with
168
+ `motu island snapshot --accept <name>` only when the change is what you intended. On a region diff with
169
+ no member island changed, the ARRANGEMENT moved — check the frame before blaming yourself.
170
+
171
+ **The final look is a FRESH agent's.** You built this island, so you are the one reader who cannot see
172
+ an invention in it — a fixture's vocabulary, a label nobody uses, a state the app never enters. It is
173
+ in your context as a premise. Spawn a subagent and hand it THREE things and nothing else: the state
174
+ URLs (`motu lagoon states --json` — the island alone, and its what-ifs in its region), where the application's own vocabulary lives (the island's types,
175
+ the modules it names), and the question — *does this belong to THIS application? Does anything render a
176
+ word, a state or a shape the app never uses?* Do NOT pass the diff, the plan, the transcript or your
177
+ reasoning; that contamination is the whole reason to spend a subagent. Its answer is a finding: every
178
+ check before it compares the island to what the island declares, and this is the only one that compares
179
+ it to the app.
180
+
181
+ ### 7 — Wait for human validation
182
+ Do NOT consider the island done until a human validates it on the real page. Present: the verify PASS,
183
+ the integration concerns from step 3, and the ocean-suite result. Stop and hand off — the human is the
184
+ final gate.
185
+
186
+ ## Two ways a region composes, and which one an extraction uses
187
+
188
+ A region's arrangement lives in ONE of two places, and both are supported:
189
+
190
+ - **`root` on the archipelago** — the application's own layout component, with `slots` mapping its
191
+ props to this region's islands. The page renders `<X.Root results={…} />` using its own prop names
192
+ and never writes a slot; the lagoon renders the SAME component from the SAME map. There is no second
193
+ description, so the two cannot differ. Safe by construction.
194
+ - **A hand-written lagoon frame** (`layout` in the overrides) — a second description of the page,
195
+ checked but not eliminated: `island-composition` compares WHICH islands the region is made of
196
+ against what the page places, and `region-root` refuses arrangement the frame invented. Nothing
197
+ compares the ARRANGEMENT itself.
198
+
199
+ **An extraction uses the frame, and that is correct rather than a concession.** You are working on a
200
+ page that already exists and already expresses its own arrangement in JSX. Moving it to `root` is a
201
+ region-level refactor of the host's own code; doing it in the same step as pulling out one island
202
+ couples two changes and hides the risky one. Extract the island, look at it in the lagoon, then decide
203
+ about the region.
204
+
205
+ THAT DECISION IS A STAGE, NOT A VERDICT: adopt as a thin overlay (islands in the page's own JSX +
206
+ a frame), migrate one region to `root` when you already have a reason to open its page, then set
207
+ `"regionRoot": "required"` when the last one is done. The archipelago-build skill has the arc and
208
+ the two hazards a migration carries that no check sees. A frame that genuinely must draw says so
209
+ once with `inventedArrangement('why', <…/>)` — a warning, and warnings do not fail `motu check`.
210
+
211
+ `motu archipelago init <id>` without `--page` is the opposite case and scaffolds `root` first: a NEW region has no page to
212
+ restructure, so the safe shape is free there.
213
+
214
+ **If the region ALREADY declares a `root`**, `motu island integrate` adds the slot to `slots` for you
215
+ and prints the two things it cannot derive — the prop to add to the root component, and the line the
216
+ page must pass. Do both, or the island is declared and never placed.
217
+
218
+ `region-root` reports which shape a region is in on every run. A project that has finished migrating
219
+ sets `"regionRoot": "required"` in `motu.config.json`, and a frame becomes an error from then on.
220
+
221
+
222
+ ## Guardrails
223
+ - No island-to-island imports, no runtime module loading/federation, no per-island versioning (README
224
+ "Non-goals"). Coordinate only through the store + DOM events.
225
+ - Never widen backend surface beyond the specific `@BrowserCallable` method you need.
226
+ - Keep motu terminology (island/ocean/archipelago/lagoon/mainland) in prose only; imports/types stay
227
+ literal. The end state is the mainland — keep the `ui/` component a plain component that de-wraps.
228
+
229
+ ## Before you hand it over: motu did not test your logic
230
+
231
+ `motu check` is green when the region COMPOSES — islands placed, declared couplings carrying, every
232
+ state rendering. It runs no typecheck and no test runner. Two things are yours:
233
+
234
+ - **logic inside the component** (a filter that stops matching a field, `some` for `every`, an
235
+ off-by-one) — renders fine, composes fine, passes everything here;
236
+ - **the path from a user's action to a declared output** — a flow that EMITS the island's declared
237
+ event enters the region past the component's own handler, so a handler that drops its argument
238
+ passes it. Write `{ click: '<accessible name>' }` instead when the control has one: the component
239
+ fires its own output, by the path a person takes. Accessible name only — no selectors, no `fill`,
240
+ no waits. Anything beyond one click is Playwright's job, not motu's.
241
+
242
+ Measured on a real screen: of eight injected regressions, the project's unit tests caught five and
243
+ motu's flows caught one — and that one was a render assertion the tests missed. Different
244
+ instruments. If the component you just wrote or wrapped carries logic, say in your handover that it
245
+ needs unit tests in the host's own runner, and do not present a green `motu check` as coverage.
246
+
247
+
248
+ ## Before you hand this over
249
+
250
+ A person has to be able to OPEN what you built. Start (or confirm) the detached lagoon, which
251
+ announces itself to the shared host under a slug scoped to your branch:
252
+
253
+ motu lagoon serve --watch --detach
254
+ motu lagoon states # the address of each declared state
255
+
256
+ **End your report with the live URL and the one or two states you want looked at.** A passing check
257
+ describes files; the work is a rendered region, and until someone opens it nobody has seen it. Every
258
+ `motu check` verdict now prints the live URL, or how to start one if there is none.
259
+
260
+ And say plainly what motu did not check: the host's typecheck and its test runner, both of which are
261
+ yours to run.