@owlmeans/create-app 0.1.18-rc.6 → 0.1.18-rc.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 (115) hide show
  1. package/README.md +49 -5
  2. package/build/args/types.d.ts +9 -0
  3. package/build/args/types.d.ts.map +1 -0
  4. package/build/args/types.js +2 -0
  5. package/build/args/types.js.map +1 -0
  6. package/build/args.d.ts +3 -20
  7. package/build/args.d.ts.map +1 -1
  8. package/build/args.js +121 -88
  9. package/build/args.js.map +1 -1
  10. package/build/bin.js +3 -3
  11. package/build/bin.js.map +1 -1
  12. package/build/consts.d.ts +6 -0
  13. package/build/consts.d.ts.map +1 -0
  14. package/build/consts.js +6 -0
  15. package/build/consts.js.map +1 -0
  16. package/build/consts.local.d.ts +12 -0
  17. package/build/consts.local.d.ts.map +1 -0
  18. package/build/consts.local.js +49 -0
  19. package/build/consts.local.js.map +1 -0
  20. package/build/index.d.ts +11 -4
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +5 -2
  23. package/build/index.js.map +1 -1
  24. package/build/naming/types.d.ts +14 -0
  25. package/build/naming/types.d.ts.map +1 -0
  26. package/build/naming/types.js +2 -0
  27. package/build/naming/types.js.map +1 -0
  28. package/build/naming.d.ts +4 -0
  29. package/build/naming.d.ts.map +1 -0
  30. package/build/naming.js +18 -0
  31. package/build/naming.js.map +1 -0
  32. package/build/run.d.ts +1 -1
  33. package/build/run.d.ts.map +1 -1
  34. package/build/run.js +23 -17
  35. package/build/run.js.map +1 -1
  36. package/build/scaffold.d.ts +8 -0
  37. package/build/scaffold.d.ts.map +1 -0
  38. package/build/scaffold.js +19 -0
  39. package/build/scaffold.js.map +1 -0
  40. package/build/template/types.d.ts +16 -0
  41. package/build/template/types.d.ts.map +1 -0
  42. package/build/template/types.js +2 -0
  43. package/build/template/types.js.map +1 -0
  44. package/build/template.d.ts +3 -16
  45. package/build/template.d.ts.map +1 -1
  46. package/build/template.js +69 -52
  47. package/build/template.js.map +1 -1
  48. package/build/types.d.ts +61 -0
  49. package/build/types.d.ts.map +1 -0
  50. package/build/types.js +2 -0
  51. package/build/types.js.map +1 -0
  52. package/package.json +5 -4
  53. package/template/AGENTS.md +61 -2
  54. package/template/README.bare.md +70 -0
  55. package/template/README.md +9 -60
  56. package/template/_agents/scripts/link-skills.sh +327 -15
  57. package/template/_agents/skills/agent-memory/SKILL.md +12 -11
  58. package/template/_agents/skills/getting-started/SKILL.md +38 -79
  59. package/template/_agents/skills/logging/SKILL.md +41 -0
  60. package/template/_agents/skills/memory-promotion/SKILL.md +10 -4
  61. package/template/_agents/skills/memory-recompact/SKILL.md +15 -14
  62. package/template/_agents/skills/owlmeans-code-structure/SKILL.md +182 -0
  63. package/template/_agents/skills/reuse-code/SKILL.md +56 -10
  64. package/template/_agents/skills/self-education/SKILL.md +25 -5
  65. package/template/_agents/skills/skill-authoring/SKILL.md +78 -12
  66. package/template/_bare.json +24 -0
  67. package/template/_gitignore +8 -0
  68. package/template/bunfig.toml +2 -0
  69. package/template/package.json +3 -2
  70. package/template/sources/api/package.json +5 -4
  71. package/template/sources/api/src/app/session/add.ts +6 -17
  72. package/template/sources/api/src/app/session/list.ts +6 -12
  73. package/template/sources/api/src/app/session/remove.ts +6 -15
  74. package/template/sources/api/src/config.ts +7 -0
  75. package/template/sources/api/src/context.bare.ts +11 -0
  76. package/template/sources/api/src/entrypoints.bare.ts +4 -0
  77. package/template/sources/api/src/entrypoints.ts +13 -0
  78. package/template/sources/api/src/index.ts +8 -2
  79. package/template/sources/api/src/models/session/types.ts +14 -0
  80. package/template/sources/api/src/models/session.ts +43 -0
  81. package/template/sources/api/src/types.bare.ts +5 -0
  82. package/template/sources/common/package.json +8 -8
  83. package/template/sources/common/src/config.ts +2 -1
  84. package/template/sources/common/src/consts.bare.ts +8 -0
  85. package/template/sources/common/src/consts.ts +0 -13
  86. package/template/sources/common/src/entrypoints.bare.ts +14 -0
  87. package/template/sources/common/src/entrypoints.ts +60 -0
  88. package/template/sources/common/src/index.bare.ts +3 -0
  89. package/template/sources/common/src/index.ts +1 -1
  90. package/template/sources/common/src/schemas.ts +7 -7
  91. package/template/sources/web/index.html +3 -1
  92. package/template/sources/web/package.json +33 -30
  93. package/template/sources/web/src/components/ui/dropdown-menu.tsx +257 -0
  94. package/template/sources/web/src/components/ui/navigation-menu.tsx +169 -0
  95. package/template/sources/web/src/config.ts +8 -0
  96. package/template/sources/web/src/consts.ts +14 -0
  97. package/template/sources/web/src/context.bare.ts +13 -0
  98. package/template/sources/web/src/context.ts +12 -2
  99. package/template/sources/web/src/entrypoints.bare.ts +13 -0
  100. package/template/sources/web/src/entrypoints.ts +16 -0
  101. package/template/sources/web/src/index.css +7 -0
  102. package/template/sources/web/src/index.tsx +2 -2
  103. package/template/sources/web/src/layout/main.tsx +17 -19
  104. package/template/sources/web/src/nav.bare.ts +23 -0
  105. package/template/sources/web/src/nav.ts +23 -0
  106. package/template/sources/web/src/screens/about.tsx +19 -0
  107. package/template/sources/web/src/screens/home.bare.tsx +17 -0
  108. package/template/sources/web/src/screens/session.tsx +30 -17
  109. package/template/sources/web/src/vite-env.d.ts +8 -0
  110. package/template/sources/web/vite.config.ts +3 -2
  111. package/template/CLAUDE.md +0 -13
  112. package/template/sources/api/src/modules.ts +0 -11
  113. package/template/sources/common/src/modules.ts +0 -26
  114. package/template/sources/web/src/components/nav/main.tsx +0 -15
  115. package/template/sources/web/src/modules.ts +0 -20
@@ -1,105 +1,64 @@
1
1
  ---
2
2
  name: getting-started
3
- description: How to build a fullstack OwlMeans Common app from scratch — the common/api/web three-workspace pattern, context bootstrap on server and web, shared entrypoints + elevate(), and a session-scoped in-memory resource with @owlmeans/static-resource. Use when starting a new OwlMeans project, wiring web↔api, or asked how the pieces fit together.
4
- user-invocable: true
3
+ description: Start a protocol-first OwlMeans application with shared contracts, server entrypoints and browser entrypoints.
5
4
  metadata:
6
5
  scope: general
7
6
  ---
8
7
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
9
8
 
10
- # Getting started with OwlMeans Common — fullstack app shape
9
+ # Protocol-first application shape
11
10
 
12
- A minimal OwlMeans app is a **bun-workspace monorepo with three packages**:
11
+ An OwlMeans application owns one shared protocol declaration and creates local entrypoints for each
12
+ runtime. The declaration is never modified by a server or browser.
13
13
 
14
+ ```ts
15
+ // common/src/entrypoints.ts
16
+ export const sessionProtocols = {
17
+ list: protocol(
18
+ route(session.list, '/session', backend()),
19
+ contract.request({ query: typed<SessionQuery>(SessionQuerySchema) }, typed<Session[]>())
20
+ ),
21
+ }
14
22
  ```
15
- sources/
16
- ├── common/ # shared entrypoints (routes), AJV schemas, types, config — the single source of truth
17
- ├── api/ # @owlmeans/server-app backend; handlers attached to the shared entrypoints
18
- └── web/ # @owlmeans/web-panel + shadcn UI; screens attached to the same entrypoints
19
- ```
20
-
21
- The full, runnable walkthrough (scaffolded **and** manual) lives in
22
- [`docs/getting-started.md`](../../../docs/getting-started.md). To generate this exact project, use
23
- [[scaffolding]] (`npm create @owlmeans/app`). This skill is the mental model.
24
-
25
- ## The core idea: one contract, two sides
26
-
27
- Declare each route once in `common` as an **entrypoint**, then `elevate()` it on each side:
28
23
 
29
24
  ```ts
30
- // common/modules.ts — declaration + validation, no implementation
31
- export const sessionModules = [
32
- entrypoint(route(session.base, '/session')),
33
- entrypoint(route(session.list, '/:sid/items', { parent: session.base, method: RouteMethod.GET }),
34
- filter(params<SessionParams>(SessionParamsSchema))),
35
- entrypoint(route(session.add, '/:sid/items', { parent: session.base, method: RouteMethod.POST }),
36
- filter(params<SessionParams>(SessionParamsSchema, body<AddItemPayload>(AddItemSchema)))),
25
+ // api/src/entrypoints.ts
26
+ const api = handlers<Context>()
27
+ export const serverBindings = [
28
+ ...frameworkEntrypoints,
29
+ bind(sessionProtocols.list, api.request(sessionProtocols.list, listSessions)),
37
30
  ]
38
31
  ```
39
32
 
40
33
  ```ts
41
- // api/modules.ts — attach handlers
42
- elevate(sessionModules, session.list, handlers.list)
43
- export const appModules = [...modules, ...sessionModules] // `modules` = framework defaults
44
- ```
34
+ // web/src/entrypoints.ts
35
+ export const clientBindings = [
36
+ ...frameworkEntrypoints,
37
+ ...bindAll(sessionProtocols),
38
+ ]
45
39
 
46
- ```ts
47
- // web/modules.ts — attach screen components, plus call-only elevation for backend routes
48
- elevate(modules, session.list) // callable from the client
49
- modules.push(entrypoint(route(web.session, '/session', frontend({ parent: BASE })), handler(SessionScreen)))
40
+ const sessions = await context.entrypoint(sessionProtocols.list).call({ query: { sid } })
50
41
  ```
51
42
 
52
- Change a route or schema in `common` and both sides stay in sync. See [[entrypoint]], [[route]],
53
- [[server-app]], [[web-client]], [[web-panel]].
54
-
55
- ## Shared config (where services live)
56
-
57
- `common/config.ts` registers both services so the web knows where the API is. `base: 'api'`
58
- prefixes API routes with `/api`:
43
+ Use `schema<T>(...)` or `typed<T>(...)` at the contract boundary. Bind all route parents with their
44
+ children. Keep organization entity values on the wire as `entitySlug`; database relations use
45
+ `entityId` only.
59
46
 
60
- ```ts
61
- const cfg = service({ type: AppType.Frontend, service: APP_WEB, host: 'localhost', port: 3001 })
62
- service({ type: AppType.Backend, service: APP_API, host: 'localhost', port: 3000, base: 'api' }, cfg)
63
- cfg.debug = { all: true }
64
- export const commonConfig = cfg
65
- ```
66
-
67
- api: `config(APP_API, commonConfig)` (+ `cfg.port`). web: `config(APP_WEB, commonConfig)`. See [[config]].
47
+ ## Configuration per runtime
68
48
 
69
- ## Backend bootstrap + in-memory data
49
+ The shared config in `common` (services, alias, security) is imported by both runtimes, so it
50
+ never reads `process.env` or `import.meta.env`, and it never sets `cfg.debug = { all: true }`.
51
+ Each runtime's own `config.ts` adds what depends on its environment — the log level above all
52
+ (`logging`, `log`), behind a type-only `import type {} from '@owlmeans/log'`:
70
53
 
71
54
  ```ts
72
- // api/context.ts
73
- const context = makeContext(cfg, true) // from @owlmeans/server-app
74
- appendStaticResource(context, SESSION_ITEMS) // @owlmeans/static-resource — in-memory, no DB
75
- ```
76
-
77
- Handlers use `handleRequest` / `handleBody` / `handleParams` (validated payload, then context, then
78
- req). Read/write `ctx.getStaticResource<T>(alias)` — full CRUD (`get/load/list/create/save/delete`).
79
- **`static-resource.list()` takes no criteria** — list all and filter in JS (e.g. by `sessionId`).
80
- `main(context, appModules)` starts the server. See [[static-resource]], [[server-app]], [[resource]].
55
+ // api/src/config.ts — Bun runtime env
56
+ cfg.log = { level: process.env.LOG_LEVEL || 'info', debug: process.env.LOG_DEBUG ?? '' }
81
57
 
82
- Swap `@owlmeans/static-resource` for [[mongo-resource]] / [[redis-resource]] when you need
83
- persistence — the handler shape is identical.
84
-
85
- ## Web bootstrap (shadcn)
86
-
87
- `@owlmeans/web-panel`'s `PanelApp` is shadcn/Tailwind v4 (no MUI). The **app provides** the shadcn
88
- primitives at the `@` alias — `web-panel` references `@/lib/utils` and
89
- `@/components/ui/{alert,button,card,input,label,progress}`; copy those into `src/`. Render with
90
- `provide` from `@owlmeans/web-client`:
91
-
92
- ```tsx
93
- basicRender(<PanelApp context={context} provide={provide} />)
58
+ // web/src/config.ts — Vite build-time env, typed in vite-env.d.ts
59
+ const env = import.meta.env
60
+ cfg.log = { level: env.VITE_LOG_LEVEL || (env.PROD ? 'info' : 'debug'), debug: env.VITE_LOG_DEBUG ?? '' }
94
61
  ```
95
62
 
96
- `vite.config.ts` sets `@`→`src`, `@tailwindcss/vite`, and dedupes the owlmeans/react singletons.
97
- `index.css` is `@import "tailwindcss";` + a shadcn `@theme` token block (replaces `@owlmeans/owl-theme`).
98
- A parent `BASE` route renders the layout via `handler(LayoutComponent)`; `HOME` is its default child.
99
- Screens call the backend with `context.entrypoint(alias).call({ params, body })` → `[data, outcome]`.
100
- See [[web-panel]], [[web-client]], [[shadcn-web]], [[client-entrypoint]].
101
-
102
- ## Authentication
103
-
104
- This shape is intentionally **auth-free**. To add it: `@owlmeans/server-auth` + `@owlmeans/client-auth`
105
- and `guard(...)` on entrypoints. See [[auth-protocol]], [[server-auth]], [[client-auth]].
63
+ The server and client contexts apply `cfg.log` themselves; application code logs through
64
+ `logger('<scope>')` from `@owlmeans/log`, never `console.*`.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: logging
3
+ description: Logging policy for any OwlMeans application — no bare console calls; every line through @owlmeans/log with a scope and a deliberate level; what belongs at info vs debug; never log secrets or personal content; debug off in production, with the level set per environment (a server's config file, a browser's build-time env); analytics events as a call option routed to plugins. Use when adding any log line or a catch block, debugging with prints, deciding what an operator should see in production, setting a log level for an environment, or reviewing code that calls console.
4
+ user-invocable: false
5
+ metadata:
6
+ scope: general
7
+ ---
8
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
9
+
10
+ # Logging — the policy
11
+
12
+ The mechanics are the `log` skill (`@owlmeans/log`). This is what to do in application code.
13
+
14
+ 1. **No `console.*`.** `import { logger } from '@owlmeans/log'`, `const log = logger('<scope>')` once per
15
+ module, then `log.debug|info|warn|error(message, data?, options?)`. A scope is lowercase and
16
+ colon-separated (`orders`, `orders:import`).
17
+ 2. **Choose the level on purpose.** `debug` is for diagnosis (per request, progress, dumps) — it is OFF
18
+ in production. `info` is what an operator wants to read in production: a job started or stopped, a
19
+ subscription or payment event, a sign-in, a server listening. `warn`: it degraded and went on.
20
+ `error`: the work failed. A line that fires every few seconds is `debug` or `logThrottle`d.
21
+ 3. **Stable message, variable data.** `log.info('Order shipped', { orderId, carrier })` — not a
22
+ sentence assembled from values. Give a significant event a dotted name: `{ event: 'order.shipped' }`.
23
+ 4. **Pass the `Error`.** `log.error('Import failed', error)` — never `error.message` alone, never
24
+ `JSON.stringify(error)`. In a browser the `Error` object must reach `console.error`; the logger does
25
+ that, and an error reporter hooked there keeps working.
26
+ 5. **Never log secrets or personal content**: tokens, keys, passwords, authorization or cookie headers,
27
+ request or response bodies, query strings, user text (a prompt, a message, a note, a model's
28
+ rationale quoting it), e-mail text, file contents, environment values. Log ids, names, sizes,
29
+ counts, lengths — `noteLength`, not the note. A refused request is its method, path (no query),
30
+ status and error code/message, at debug. A catch that only logs has not told the user anything —
31
+ the user-facing report is a separate step (a message, a state), and `log.error` is the operator's
32
+ record.
33
+ 6. **Analytics are a parameter, not a second API.** `log.info('…', data, { analytics: 'event_name' })`
34
+ hands the call to the registered analytics plugins (`@owlmeans/web-log` for a tag manager); add
35
+ `console: false` to keep it out of the log. Only for events something should count.
36
+ 7. **The level is set per environment, never in code.** A server declares `cfg.log` leaves as config
37
+ file paths (a ConfigMap or a `LOG_LEVEL` environment variable); a browser takes a build-time
38
+ constant (`VITE_LOG_LEVEL`). Development defaults to `debug`, a production build to `info`. Do not
39
+ hard-code `cfg.debug = { all: true }` — `debug.all` does not control logging and must not be on in
40
+ production.
41
+ 8. **Tests read the log with `memoryPlugin()`**; they do not spy on `console`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: memory-promotion
3
- description: Transform procedure-shaped or repeatedly-used memory into skills and instructions — the procedure-shape test, the mandatory distillation rewrite, promote? repeated-touch flags, over-cap trigger, update-vs-create decision, and the post-promotion pointer state. Use when memory content reads as "how to", when a promote? flag is encountered again, when writing memory-derived content into a skill, or during recompaction.
3
+ description: Transform procedure-shaped or repeatedly-used memory into skills — the procedure-shape test, the mandatory distillation rewrite, promote? repeated-touch flags, over-cap trigger, update-vs-create decision, and the post-promotion pointer state. Use when memory content reads as "how to", when a promote? flag is encountered again, when writing memory-derived content into a skill, or during recompaction.
4
4
  user-invocable: true
5
5
  metadata:
6
6
  scope: general
@@ -9,7 +9,7 @@ metadata:
9
9
 
10
10
  # Memory promotion
11
11
 
12
- Memory holds **facts**; **procedures** belong in skills/instructions, where they auto-invoke and
12
+ Memory holds **facts**; **procedures** belong in skills, where they auto-invoke and
13
13
  stop consuming memory-read cycles. Promotion is how the store stays compact and the harness
14
14
  teaches itself.
15
15
 
@@ -79,8 +79,8 @@ Evaluable by reading the file alone — no tooling required.
79
79
 
80
80
  ## Update vs create
81
81
 
82
- **Default is update** — extend the existing skill/instruction whose scope covers the activity,
83
- even partially; keep both twins in sync. Create a NEW pair only when:
82
+ **Default is update** — extend the existing skill whose scope covers the activity, even
83
+ partially. Create a NEW skill only when:
84
84
 
85
85
  - (a) a new subsystem or technology entered the repo;
86
86
  - (b) an activity with no covering skill needed memory read/write more than once (a
@@ -90,6 +90,12 @@ even partially; keep both twins in sync. Create a NEW pair only when:
90
90
  New skills multiply lookup cost — compactness applies to the skill population *and* to each
91
91
  skill's body.
92
92
 
93
+ The covering skill may be one this project does not own — a `.agents/linked-skills/<name>` entry
94
+ symlinked into an upstream repo or installed package, or an installer-placed copy carrying the
95
+ `AUTO-GENERATED` banner. Never write the promotion into either: put the distilled rule in a local
96
+ skill and name it after the activity, because a local skill named after the upstream one shadows
97
+ that skill for the whole project (`self-education` → Skills this project does not own).
98
+
93
99
  ## Procedure
94
100
 
95
101
  1. Collect the flagged / procedure-shaped memory lines.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: memory-recompact
3
- description: Recompact or migrate a whole .agents/memory/ store — rebuild the node map from project structure, merge event-shaped records into subsystem nodes, deduplicate, enforce caps, regenerate the MEMORY.md index, and fold in legacy .claude/memory and .github/memory stores. Use when a store degrades into event logs, indexes bloat or conflict, or for one-time migration.
3
+ description: Recompact a whole .agents/memory/ store — rebuild the node map from project structure, merge event-shaped records into subsystem nodes, deduplicate, enforce caps, regenerate the MEMORY.md index, and fold in memory records kept anywhere else. Use when a store degrades into event logs, when indexes bloat or conflict, or when scattered records have to become one store.
4
4
  disable-model-invocation: true
5
5
  metadata:
6
6
  scope: general
@@ -9,9 +9,9 @@ metadata:
9
9
 
10
10
  # Memory recompaction
11
11
 
12
- Whole-store maintenance for `.agents/memory/` (protocol: `agent-memory`). Also the migration
13
- procedure for legacy `.claude/memory/` + `.github/memory/` stores. Store-wide rewrite — propose
14
- it when triggers appear; the operator invokes it.
12
+ Whole-store maintenance for `.agents/memory/` (protocol: `agent-memory`), and the procedure for
13
+ folding records kept anywhere else into it. Store-wide rewrite — propose it when triggers appear;
14
+ the operator invokes it.
15
15
 
16
16
  ## When
17
17
 
@@ -20,7 +20,8 @@ it when triggers appear; the operator invokes it.
20
20
  - The same fact stated in two or more nodes.
21
21
  - More than ~20% of a node is stale `Status` content.
22
22
  - A node's `updated:` is months behind commits touching its scope.
23
- - Legacy `.claude/memory/` or `.github/memory/` dirs exist → run the migration below.
23
+ - Memory records live outside `.agents/memory/` — a second store, a per-agent directory, a stray
24
+ notes file → fold them in with the merge pass below.
24
25
 
25
26
  ## Build the target node map first
26
27
 
@@ -43,14 +44,14 @@ For each old file or section:
43
44
  4. Procedure-shaped survivors do not enter nodes — route them to `memory-promotion`. Routing
44
45
  means distilling them into general rules, never handing the text over verbatim.
45
46
 
46
- ## Legacy-store merge (migration)
47
+ ## Folding in an outside store
47
48
 
48
- 1. Union `.claude/memory/` and `.github/memory/`. Same-named files are two drifted sources of
49
- ONE node — merge both; the code-consistent version wins.
49
+ 1. Union every source. Two same-named files are two drifted sources of ONE node — merge both;
50
+ the code-consistent version wins.
50
51
  2. Index-only entries with no backing file: extract the fact into its node, or drop if stale.
51
- 3. Old `## Skills` / "Key Files" index sections are dropped — skills self-describe; harness
52
- layout belongs to `AGENTS.md`. Move genuinely non-obvious dispatch hints there.
53
- 4. When the new store verifies (below), delete both legacy dirs entirely.
52
+ 3. `## Skills` / "Key Files" index sections are dropped — skills self-describe; harness layout belongs to `AGENTS.md`; a genuinely non-obvious dispatch hint goes into the
53
+ owning skill's `description:`, never into `AGENTS.md`.
54
+ 4. When the new store verifies (below), delete every merged source entirely.
54
55
 
55
56
  ## Regenerate the index
56
57
 
@@ -62,9 +63,9 @@ index incrementally.
62
63
  - Every node file is listed in the index; every listed node exists; every wiki-link resolves.
63
64
  - All caps met (index ≤ 50 lines; nodes ≤ 120; entries ≤ 3 lines; Status ≤ 5 dated lines).
64
65
  - No dates outside `Status` and `updated:`; no event-keyed filenames.
65
- - Both root instruction files' Memory sections point at `.agents/memory/`.
66
- - Legacy dirs gone; `grep -rn '\.claude/memory\|\.github/memory'` over the repo's harness files
67
- returns nothing but allowlisted historical mentions.
66
+ - The root `AGENTS.md` Memory section points at `.agents/memory/`.
67
+ - `.agents/memory/` is the only memory store left: every merged source directory or file is
68
+ deleted, and nothing in the harness still points at one.
68
69
 
69
70
  ## Report
70
71
 
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: owlmeans-code-structure
3
+ description: "Mandatory object and layout rules for all OwlMeans TypeScript: the functions of one domain are ONE object built by a factory; its interface is declared FIRST (interfaces, never ReturnType<typeof …>); private parts live inside the factory; the context or collaborator is bound into the factory; types, consts and code live in separate files; an object with its own types or sub-helpers gets a same-named folder. Use whenever you write or change any .ts/.tsx in an @owlmeans package, an app built on OwlMeans or a generated target: adding a function, helper, util, service, model, resource, type or constant, creating or splitting a file, or reviewing a diff."
4
+ metadata:
5
+ scope: general
6
+ ---
7
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
8
+
9
+ # OwlMeans code structure
10
+
11
+ Every OwlMeans repo — the libraries, the platform and every generated application — holds to the
12
+ same shape. No tool checks it: this skill is the rule, every diff you write or review is held to it,
13
+ and a violation is a defect, not a style note.
14
+
15
+ ## The rules
16
+
17
+ 1. **One domain, one object.** The functions a file offers to other files form ONE object, built by
18
+ ONE factory. Two domains in a file are two files. A file that exports a single function is not a
19
+ bundle and may stay a plain export.
20
+ 2. **Type first.** Declare the object's `interface` before writing the factory, in the object's
21
+ types file, and make the factory return it: `export const createXxxHelper = (…): XxxHelper => {…}`.
22
+ A type derived from the implementation — `ReturnType<typeof createXxx>`, `Parameters<typeof fn>`,
23
+ `typeof someObject` — never names a helper, util, service, model or resource. The contract's JSDoc
24
+ lives on the interface members.
25
+ 3. **Encapsulate.** The factory's closure holds the implementation: what the interface does not name
26
+ is a private local. Only pure, stateless primitives may stay module-private above the factory.
27
+ A factory that merely collects module-level functions — `createX = () => ({ a, b })` — is the
28
+ thing this rule forbids. No `this`: members are arrow functions, so they can be destructured and
29
+ passed on.
30
+ 4. **Bind what is shared.** When every member would take the same first argument — a context, a
31
+ request, a collaborator, a record — that argument goes to the factory, not to each call.
32
+ 5. **Interfaces over types — always where possible.**
33
+ - `type X = { … }` → `interface X { … }`; `A & B & { … }` → `interface X extends A, B { … }`.
34
+ - `type X = Pick<A, 'a'>` (also `Omit`, `Partial`, `Required`, `Readonly`) → `interface X extends Pick<A, 'a'> {}`.
35
+ - `type Fn = (a: A) => R` → `interface Fn { (a: A): R }`.
36
+ - `type` stays only for what an interface cannot say: unions, mapped, conditional and
37
+ template-literal types, tuples, primitives, `keyof`/indexed access, and protocol trees inferred
38
+ from a DSL (with a one-line reason).
39
+ 6. **Types, consts and code in separate files.** Interfaces and type aliases → `types.ts`; enums and
40
+ constants (including computed constant expressions such as an `Object.freeze([...LIST, ...])`) →
41
+ `consts.ts`; code everywhere else. Package-private declarations go to `types.local.ts` /
42
+ `consts.local.ts`, which no barrel re-exports.
43
+ 7. **A same-named folder for an object that has parts.** `helpers/file.ts` holds the factory;
44
+ `helpers/file/` holds its `types.ts`, `consts.ts` and the sub-helpers only it uses. A directory-level
45
+ `types.ts` is right for declarations two or more objects share, or for a small single-owner
46
+ directory; a types file that serves many unrelated owners is split into their folders.
47
+ 8. **Ready instances.** A zero-argument helper may export one ready instance as the file's last
48
+ statement (`export const pathHelper = createPathHelper()`). A bound object never has one — it is
49
+ reached through its binding target (below). Never build a bound object inside a loop or a render.
50
+
51
+ ## Kinds and names
52
+
53
+ | Kind | What it is | Factory | Interface | Where |
54
+ |---|---|---|---|---|
55
+ | util | package-internal functionality, never re-exported by the package | `createXxxUtils()` / `makeXxxUtils(target)` | `XxxUtils` | `utils/xxx.ts` + `utils/xxx/` |
56
+ | helper | reusable functionality, may be exported, not bound to one record | `createXxxHelper(opts?)` / `makeXxxHelper(target)` | `XxxHelper` | `xxx.ts` + `xxx/` |
57
+ | service | lives in a context, has an alias and a lifecycle | `createXxxService(alias = DEFAULT_ALIAS)` via `createService`, plus `appendXxxService(ctx)` | `XxxService extends InitializedService` | `services/xxx.ts` + `xxx/` |
58
+ | model | logic over ONE plain record (plus collaborators) | `makeXxxModel(record, deps?)` | `XxxModel` | `models/xxx.ts` + `xxx/` |
59
+ | resource | store access and store-level queries | `makeXxxResource(…)` | `XxxResource` | `resources/xxx.ts` + `xxx/` |
60
+
61
+ `create*` builds something standalone or configured by options; `make*` builds something bound to
62
+ its first argument.
63
+
64
+ ## How a bound object is reached
65
+
66
+ | Bound to | Shape | At the call site |
67
+ |---|---|---|
68
+ | the context, with a lifecycle, a cache or a replaceable seam | a service | `ctx.service<XxxService>(ALIAS)`, or an accessor its `append*` installs (`ctx.xxx()`) |
69
+ | the context of an app that owns its context type | `makeXxxHelper(ctx)`, installed lazily on the context: `context.projects = memoHelper.once(() => makeProjectHelper(context))` | `ctx.projects().getDevSlot(id)` |
70
+ | the context, inside a library | `makeXxx(ctx)` plus `export const xxxOf = memoHelper.oncePer(makeXxx)` | `const xxx = xxxOf(ctx)` once per function |
71
+ | one request | `makeRequestScope(ctx, request)` | built once at the top of a handler, never stored |
72
+ | a collaborator (a FileHelper, a deps bag) | `makeXxx(collaborator)`, an accessor on the collaborator or `oncePer` | `files.layout().recordDeviation(d)` |
73
+ | a record | a model | `makeSlotModel(slot).namespace()` |
74
+
75
+ `memoHelper` (`once`, `oncePer`) comes from `@owlmeans/context`. Tests build the object directly:
76
+ `makeProjectHelper(fakeContext)`.
77
+
78
+ ## Examples
79
+
80
+ ```ts
81
+ // helpers/slug/types.ts — the type comes first
82
+ export interface SlugHelper {
83
+ /** The URL-safe form of a title. */
84
+ slugOf: (title: string) => string
85
+ /** A title read back from a slug. */
86
+ titleOf: (slug: string) => string
87
+ }
88
+
89
+ // helpers/slug.ts
90
+ import type { SlugHelper } from './slug/types.js'
91
+
92
+ export const createSlugHelper = (): SlugHelper => {
93
+ const words = (text: string): string[] => text.split(/\W+/).filter(word => word !== '') // private
94
+
95
+ const slugOf = (title: string): string => words(title.toLowerCase()).join('-')
96
+ const titleOf = (slug: string): string => words(slug).join(' ')
97
+
98
+ return { slugOf, titleOf }
99
+ }
100
+ export const slugHelper = createSlugHelper()
101
+ ```
102
+
103
+ A service follows `@owlmeans/api`'s `createApiService(alias): ApiClient` — `createService<ApiClient>`
104
+ with the members in place, `assertContext(client.ctx, …)` inside them, and an `appendApiClient(ctx)`
105
+ that registers it. A model follows `@owlmeans/planning`'s `makeWorkcardModel(record, facade):
106
+ WorkcardModel`.
107
+
108
+ ```ts
109
+ // models/invoice/types.ts
110
+ export interface InvoiceModel { readonly record: Invoice; isPaid: () => boolean; owed: () => number }
111
+ // models/invoice.ts
112
+ export const makeInvoiceModel = (record: Invoice): InvoiceModel => ({
113
+ record,
114
+ isPaid: () => record.paidAt != null,
115
+ owed: () => (record.paidAt != null ? 0 : record.amountMinor),
116
+ })
117
+ ```
118
+
119
+ A model is built from its record when it is needed — never stored on the record, never serialized.
120
+
121
+ **Wrong, and how it is fixed**
122
+
123
+ | Wrong | Right |
124
+ |---|---|
125
+ | `const a = …; const b = …; export const createX = () => ({ a, b })` + `type X = ReturnType<typeof createX>` | `interface X` in `x/types.ts`; `createX = (): X => { const a = …; const b = …; return { a, b } }` |
126
+ | `xHelper.read(files, path)`, `xHelper.write(files, …)` — the same collaborator in every call | `makeX(files): X` and `x.read(path)` |
127
+ | `export const getDevSlot = (ctx, id) => …` beside five more ctx-first functions | `makeProjectHelper(ctx): ProjectHelper`, reached as `ctx.projects()` |
128
+ | `export const SEED_SOURCES = Object.freeze([…])` in a code file | the constant in `consts.ts` |
129
+ | one `types.ts` holding the interfaces of twenty unrelated helpers | each helper's interface in its own `<helper>/types.ts` |
130
+
131
+ ## Exempt
132
+
133
+ These stay plain exports: declaration-builder DSLs that are the framework's vocabulary (`route`,
134
+ `protocol`, `typed`, `contract`, `bind`, `logger`, `frontend`/`backend`, `declare*`, config plugins),
135
+ factories themselves (`make*`, `create*`, `append*`), React components and `use*` hooks, entry
136
+ scripts, error classes and schema files.
137
+
138
+ ## Generated applications
139
+
140
+ Applications the viable agent generates follow the same rules, in these fixed shapes:
141
+
142
+ - **Domain model per entity.** `backend/src/models/<entity>/types.ts` declares
143
+ `interface <Base>Model` first; `models/<entity>/<base>.ts` exports only
144
+ `make<Base>Model = (ctx: Context): <Base>Model => { const op = async (actor: Actor, …) => …; return { op, … } }`.
145
+ It is built where it is used — `await makeTaskModel(ctx).complete(actorOf(request), id)` — and is
146
+ never registered as a context service. Its member names are the endpoint keys of the entity.
147
+ - **Handlers and job processors**: one plain exported function per file
148
+ (`api/src/app/<entity>/<action>.ts` beside a GENERATED `index.ts` barrel, `worker/src/jobs/<job>.ts`);
149
+ a handler's body is one call into the model.
150
+ - **Seed helpers are objects** whose members keep the old function names:
151
+ `actorOf(request): Actor` with `actor.inOrganization(…)`, `actor.assertOrganization(…)`,
152
+ `actor.grantedIds(…)`, `actor.organizationScope(…)`; `recordOwnerOf(request)`, `visitOf(request)`,
153
+ `planningAccessOf(request, ctx)`, `landingHandoff`, `visitKey`. Their types live in the module's
154
+ same-named folder (`lib/actor/types.ts`); the old free functions stay as
155
+ `@deprecated generated-app:factory-objects` wrappers so a project generated before still compiles.
156
+ - A model module generated before this shape keeps its plain functions: its callers import them by
157
+ name, so it is extended in that shape and never converted in place.
158
+ - **Resources** keep their maker and their `xResource(ctx)` accessor; the resource's interface lives
159
+ in `resources/<entity>/types.ts`.
160
+ - Shared types in `common` are interfaces.
161
+ - A file the template shipped keeps the shape the platform gives it.
162
+
163
+ ## Self-check before you finish
164
+
165
+ ```sh
166
+ { git diff --name-only HEAD; git ls-files --others --exclude-standard; } | grep -E '\.tsx?$' \
167
+ | grep -vE '\.(test|spec)\.tsx?$|\.d\.ts$|(^|/)(tests?|fixtures)/' > /tmp/changed.txt
168
+ # derived helper types and wrapper factories
169
+ xargs -r grep -nE 'ReturnType<typeof|= \(\) => \(\{ *[a-zA-Z]+, ' < /tmp/changed.txt
170
+ # object-shaped type aliases that should be interfaces
171
+ xargs -r grep -nE '^(export )?type [A-Za-z0-9_]+(<[^=]*>)? *= *\{' < /tmp/changed.txt
172
+ # types, enums or CONSTANT_CASE constants declared in code files
173
+ grep -vE '(^|/)(types|consts)(\.local)?\.ts$' /tmp/changed.txt \
174
+ | xargs -r grep -nE '^export (interface |enum |type [A-Za-z0-9_]+ *(<[^=]*>)? *=|const [A-Z][A-Z0-9_]+ *[:=])'
175
+ ```
176
+
177
+ Then read the diff once for what no grep sees: a second exported function of one domain, a member
178
+ that takes the same first argument as its siblings, a private helper exposed on the interface, a
179
+ bound object built in a loop, a barrel re-exporting a `.local` file.
180
+
181
+ Classification details, folder decisions and the pitfalls of moving code into closures: `reference.md`
182
+ (in the OwlMeans repositories).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: reuse-code
3
- description: Discovery-first, reuse-first workflow for OwlMeans projects. Use BEFORE planning or building any feature, before proposing a third-party library or a custom solution, and after writing code. Find an existing @owlmeans/* package or existing code first; extend before writing new; simplify what you write.
3
+ description: Discovery-first, reuse-first workflow for OwlMeans projects. Use BEFORE planning or building any feature, before proposing a third-party library or a custom solution, and after writing code. Find an existing @owlmeans/* package or existing code first (membership, invitations, organizations and access come from the IAM); extend before writing new; simplify what you write.
4
4
  user-invocable: true
5
5
  metadata:
6
6
  scope: general
@@ -18,9 +18,17 @@ order — during planning **and** during implementation.
18
18
  Before suggesting any external library or writing custom code, look for an `@owlmeans/*` package that
19
19
  already solves the problem.
20
20
 
21
- - **Consult the deployed skills.** Each installed `@owlmeans/*` package ships a skill at
22
- `.agents/skills/<pkg>/SKILL.md` describing what it
23
- does. Read those first — they are your local catalogue of installed capabilities.
21
+ - **Consult the deployed skills.** `.agents/skills/` is the local catalogue of installed
22
+ capabilities: one directory per skill, each holding a `SKILL.md` that describes what a package
23
+ does and how it is consumed. A directory is named after the **skill**, not the package —
24
+ `@owlmeans/test-ui` deploys `testing-ui`, `@owlmeans/server-auth` deploys `server-auth` **and**
25
+ `supervisor-auth`, `@owlmeans/test` deploys `testing-unit` and `testing-overview` — so list the
26
+ directory instead of guessing a path from a package name.
27
+ - **Read `.agents/linked-skills/` too when it is there.** `.agents/scripts/link-skills.sh` links
28
+ the skills that ship inside the installed `@owlmeans/*` packages into it (and mirrors them into
29
+ `.claude/skills/` for Claude Code), with a skill / origin / description table in its
30
+ `INDEX.md`. It is generated and git-ignored, and a skill of the same name in `.agents/skills/`
31
+ always wins.
24
32
  - **Scan installed packages.** Look in `node_modules/@owlmeans/*` **and**, in a workspace monorepo,
25
33
  the nested `sources/*/node_modules/@owlmeans/*` (bun nests workspace deps).
26
34
  - **Discover packages that aren't installed yet** by researching the **owlmeans/common** repository —
@@ -39,20 +47,56 @@ How you research the repo depends on whether `@owlmeans/*` is linked locally:
39
47
  **https://github.com/owlmeans/common** — `tree.md` and package READMEs — to find the right package.
40
48
 
41
49
  This is the same dev-linked detection `@owlmeans/agent-skills` uses (see its `detectLinked`). After
42
- adding an `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills` to deploy its skill. Prefer an
43
- `@owlmeans/*` package over a third-party library or bespoke code whenever one fits.
50
+ adding an `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills@^0.1.18-rc.52` to deploy its
51
+ skill. Prefer an `@owlmeans/*` package over a third-party library or bespoke code whenever one fits.
52
+
53
+ ### Never add an OwlMeans dependency without an explicit range
54
+
55
+ Write the range yourself, as a caret at the version the rest of this project already uses for its
56
+ other `@owlmeans/*` packages (or the one the package's own skill names on its **Install:** line):
57
+
58
+ ```json
59
+ "dependencies": {
60
+ "@owlmeans/queue": "^<version>"
61
+ }
62
+ ```
63
+
64
+ A `bun add` that names no version — and a hand-written `"latest"`, `"next"`, `"*"` or empty range
65
+ — resolves through a dist-tag instead. OwlMeans publishes prereleases under `next`, so the tag
66
+ named `latest` points at an OLDER version than the one every other package here is pinned to. The
67
+ install succeeds, nothing warns, and the code you wrote against the current API is compiled
68
+ against the previous one. Put the version in the same breath as the package name, or copy the
69
+ `**Install:**` line from that package's own skill, which always carries a current range.
44
70
 
45
71
  ## 2. Reuse or extend before writing custom
46
72
 
47
73
  If an installed package nearly fits, **configure or extend it** rather than writing something new — use
48
- its resources, services, modules, and helpers. A small extension of a framework package beats a new
74
+ its resources, services, entrypoints, and helpers. A small extension of a framework package beats a new
49
75
  parallel implementation.
50
76
 
77
+ ### People, organizations and access come from the IAM
78
+
79
+ An application that signs its people in through an OwlMeans IAM provider already has accounts,
80
+ organizations, memberships, invitations and grants — the provider keeps them. Before writing a
81
+ members table, an invitation flow, a role column or an organization picker:
82
+
83
+ - **Server** (`@owlmeans/server-iam`): `makeIamRuntimeClient(context, request)` lists the person's
84
+ organizations, creates one, lists / adds (find-or-create by e-mail) / updates / removes members,
85
+ and lists, assigns and revokes grants — as the signed-in person, with owner and member rights
86
+ decided by the provider. `makeOrganizationScope(context, request).organizationOf` /
87
+ `.organizationsOf` read the session's organizations; gates and `hasPermission` enforce access;
88
+ `makeEntityScope(req).requireEntityKey()` is the tenant key to store records by.
89
+ - **Browser** (`@owlmeans/client-iam`): `organizationSwitchOf(ctx).listOrganizations` /
90
+ `.switchOrganization` — the acting organization is session state, never a per-request parameter.
91
+ - If the project's own scaffold already ships an organization switcher or a people / access screen,
92
+ extend it rather than adding a second one.
93
+
51
94
  ## 3. No package? Reuse code and extract an abstraction
52
95
 
53
96
  When no package solves it, search the codebase for code that already solves a **similar** problem.
54
97
  Prefer factoring out a shared helper, base, or generic function — extract an abstraction — over
55
- duplicating logic or writing from scratch. Only write genuinely new code when nothing reusable exists.
98
+ duplicating logic or writing from scratch. A shared helper is ONE factory-built object whose interface is declared
99
+ first; it lives in its own `<name>.ts`, with a same-named folder for its types and parts (`/owlmeans-code-structure`). Only write genuinely new code when nothing reusable exists.
56
100
 
57
101
  ## 4. Simplify after writing
58
102
 
@@ -60,5 +104,7 @@ Once code is written, review it: can it be **shorter, clearer, or expressed with
60
104
  Lean on framework utilities, remove dead branches, collapse needless indirection. Less code that reuses
61
105
  the framework is better than more bespoke code.
62
106
 
63
- See `[[dependency-tree]]` for the package map, `[[scaffolding]]` for how a project is assembled, and
64
- `[[bun]]` for adding dependencies.
107
+ See `[[scaffolding]]` for how a project is assembled, and `[[agent-skills]]` for keeping the
108
+ deployed skill catalogue current. The package map itself is `tree.md` at the root of the
109
+ [owlmeans/common](https://github.com/owlmeans/common) repository — layer by layer, every package
110
+ and what it depends on.
@@ -27,10 +27,14 @@ Also recommended after any unplanned change that made an existing skill inaccura
27
27
 
28
28
  For each area the work touched:
29
29
 
30
- 1. Which existing skill covers it? (Check `.agents/skills/`.)
30
+ 1. Which existing skill covers it? Check `.agents/skills/`, and — where the project has one —
31
+ `.agents/linked-skills/`, for ground a skill this project does not own already covers.
31
32
  2. Do its commands, paths, APIs, and behavior claims still hold after the change?
32
33
  3. Fix in place — rewrite the affected lines so they describe current behavior; never append a
33
34
  note about what this change did.
35
+ 4. Never record what the work taught in `AGENTS.md`: the rule goes to the governing skill, the
36
+ incident to `.agents/memory/`. `AGENTS.md` gains at most a new skill's one index line or a new
37
+ repo-wide mandatory rule in one line (`create-skill` → What may enter AGENTS.md).
34
38
 
35
39
  ## Shape of an update
36
40
 
@@ -46,11 +50,24 @@ rewrite recipe is `memory-promotion` → Distillation.
46
50
 
47
51
  Test: a finished skill reads as though the feature was always this way.
48
52
 
49
- ## Non-project skills
53
+ ## Skills this project does not own
50
54
 
51
- If a general or imported skill gained an important usage pattern during the work, add the pattern
52
- to the **deployed copy** in this repo and note it in the report as an upstream candidate —
53
- canonical archive copies change only on explicit operator request.
55
+ A skill that came from somewhere else is not edited here, and the two kinds fail differently:
56
+
57
+ - An entry under `.agents/linked-skills/<name>` is a symlink into the repo or installed package
58
+ that owns the skill. Writing through it edits the owner's own file — an unrequested change in
59
+ another project, which nothing here undoes: the link script only creates and prunes symlinks.
60
+ - A skill placed by the `@owlmeans/agent-skills` installer is a real file at
61
+ `.agents/skills/<name>/SKILL.md` carrying
62
+ `<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->`. The next install
63
+ overwrites every file still carrying that banner, so an edit made under it is lost; strip the
64
+ banner and the file instead becomes a conflict the installer skips and reports.
65
+
66
+ When such a skill gained an important usage pattern during the work, capture the pattern in a
67
+ local skill under `.agents/skills/`, named after the pattern — a local skill named after the
68
+ upstream one shadows it for the whole project — and list the upstream skill in the report as an
69
+ upstream candidate. Changing the skill at its source is a separate change in the owning project,
70
+ made only on explicit operator request.
54
71
 
55
72
  ## External docs
56
73
 
@@ -73,6 +90,9 @@ memory-vs-skill boundary (`agent-memory` / `memory-promotion`).
73
90
 
74
91
  ## Completion gate
75
92
 
93
+ Before reporting, run `sh .agents/scripts/agents-size.sh`; if `AGENTS.md` is over budget, move
94
+ lines out to their skills first.
95
+
76
96
  The completion report must contain a Self-education table:
77
97
 
78
98
  | Item | Action | Path |