@owlmeans/create-app 0.1.18-rc.13 → 0.1.18-rc.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- package/build/args.d.ts +8 -0
- package/build/args.d.ts.map +1 -1
- package/build/args.js +60 -6
- package/build/args.js.map +1 -1
- package/build/index.d.ts +4 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/naming.d.ts +11 -0
- package/build/naming.d.ts.map +1 -0
- package/build/naming.js +18 -0
- package/build/naming.js.map +1 -0
- package/build/run.d.ts.map +1 -1
- package/build/run.js +19 -13
- package/build/run.js.map +1 -1
- package/build/scaffold.d.ts +21 -0
- package/build/scaffold.d.ts.map +1 -0
- package/build/scaffold.js +18 -0
- package/build/scaffold.js.map +1 -0
- package/build/template.d.ts +19 -3
- package/build/template.d.ts.map +1 -1
- package/build/template.js +52 -12
- package/build/template.js.map +1 -1
- package/package.json +4 -3
- package/template/AGENTS.md +20 -1
- package/template/README.bare.md +57 -0
- package/template/README.md +15 -8
- package/template/_agents/scripts/link-skills.sh +310 -15
- package/template/_agents/skills/agent-memory/SKILL.md +12 -11
- package/template/_agents/skills/getting-started/SKILL.md +66 -19
- package/template/_agents/skills/memory-promotion/SKILL.md +10 -4
- package/template/_agents/skills/memory-recompact/SKILL.md +15 -14
- package/template/_agents/skills/reuse-code/SKILL.md +36 -8
- package/template/_agents/skills/self-education/SKILL.md +19 -5
- package/template/_agents/skills/skill-authoring/SKILL.md +53 -11
- package/template/_bare.json +23 -0
- package/template/_gitignore +3 -0
- package/template/bunfig.toml +2 -0
- package/template/package.json +2 -1
- package/template/sources/api/src/app/session/list.ts +6 -4
- package/template/sources/api/src/app/session/remove.ts +1 -1
- package/template/sources/api/src/context.bare.ts +11 -0
- package/template/sources/api/src/context.ts +0 -4
- package/template/sources/api/src/entrypoints.bare.ts +6 -0
- package/template/sources/api/src/entrypoints.ts +11 -0
- package/template/sources/api/src/index.ts +2 -2
- package/template/sources/api/src/types.bare.ts +5 -0
- package/template/sources/common/src/consts.bare.ts +8 -0
- package/template/sources/common/src/entrypoints.bare.ts +8 -0
- package/template/sources/common/src/{modules.ts → entrypoints.ts} +1 -1
- package/template/sources/common/src/index.bare.ts +3 -0
- package/template/sources/common/src/index.ts +1 -1
- package/template/sources/web/index.html +3 -1
- package/template/sources/web/src/context.bare.ts +13 -0
- package/template/sources/web/src/context.ts +0 -4
- package/template/sources/web/src/entrypoints.bare.ts +14 -0
- package/template/sources/web/src/entrypoints.ts +22 -0
- package/template/sources/web/src/index.tsx +2 -2
- package/template/sources/web/src/nav.bare.ts +21 -0
- package/template/sources/web/src/screens/home.bare.tsx +17 -0
- package/template/sources/web/src/screens/session.tsx +7 -7
- package/template/sources/web/src/vite-env.d.ts +1 -0
- package/template/sources/api/src/modules.ts +0 -11
- package/template/sources/web/src/modules.ts +0 -22
|
@@ -14,21 +14,28 @@ A minimal OwlMeans app is a **bun-workspace monorepo with three packages**:
|
|
|
14
14
|
```
|
|
15
15
|
sources/
|
|
16
16
|
├── common/ # shared entrypoints (routes), AJV schemas, types, config — the single source of truth
|
|
17
|
+
│ # entrypoints.ts
|
|
17
18
|
├── api/ # @owlmeans/server-app backend; handlers attached to the shared entrypoints
|
|
19
|
+
│ # context.ts, entrypoints.ts, app/<area>/*, index.ts
|
|
18
20
|
└── web/ # @owlmeans/web-panel + shadcn UI; screens attached to the same entrypoints
|
|
21
|
+
# context.ts, entrypoints.ts, nav.ts, layout/, screens/, render.tsx, index.tsx
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
Three workspaces is the whole shape — a backend that needs its own long-running worker or a second
|
|
25
|
+
API adds a workspace beside them and shares the same `common`.
|
|
26
|
+
|
|
27
|
+
The full, runnable walkthrough (scaffolded **and** manual) lives in the
|
|
28
|
+
[OwlMeans getting-started guide](https://github.com/owlmeans/common/blob/main/docs/getting-started.md).
|
|
29
|
+
To generate this exact project, use [[scaffolding]] (`npm create @owlmeans/app`). This skill is the
|
|
30
|
+
mental model.
|
|
24
31
|
|
|
25
32
|
## The core idea: one contract, two sides
|
|
26
33
|
|
|
27
34
|
Declare each route once in `common` as an **entrypoint**, then `elevate()` it on each side:
|
|
28
35
|
|
|
29
36
|
```ts
|
|
30
|
-
// common/
|
|
31
|
-
export const
|
|
37
|
+
// common/entrypoints.ts — declaration + validation, no implementation
|
|
38
|
+
export const sessionEntrypoints = [
|
|
32
39
|
entrypoint(route(session.base, '/session')),
|
|
33
40
|
entrypoint(route(session.list, '/:sid/items', { parent: session.base, method: RouteMethod.GET }),
|
|
34
41
|
filter(params<SessionParams>(SessionParamsSchema))),
|
|
@@ -38,17 +45,25 @@ export const sessionModules = [
|
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
```ts
|
|
41
|
-
// api/
|
|
42
|
-
elevate(
|
|
43
|
-
export const
|
|
48
|
+
// api/entrypoints.ts — attach handlers
|
|
49
|
+
elevate(sessionEntrypoints, session.list, handlers.list)
|
|
50
|
+
export const appEntrypoints = [...entrypoints, ...sessionEntrypoints] // `entrypoints` = framework defaults
|
|
44
51
|
```
|
|
45
52
|
|
|
46
53
|
```ts
|
|
47
|
-
// web/
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
// web/entrypoints.ts — attach screen components, plus call-only elevation for backend routes
|
|
55
|
+
const entrypoints = [...baseEntrypoints, ...sessionEntrypoints] // `baseEntrypoints` from web-panel
|
|
56
|
+
elevate(entrypoints, session.list) // callable from the client
|
|
57
|
+
entrypoints.push(entrypoint(route(web.session, '/session', frontend({ parent: BASE })), handler(SessionScreen)))
|
|
58
|
+
export const appEntrypoints = entrypoints
|
|
50
59
|
```
|
|
51
60
|
|
|
61
|
+
A route declaration is plain data: its `path` is the SEGMENT it contributes under its `parent`, and
|
|
62
|
+
nothing ever rewrites it. `session.list` reads `/:sid/items` under `session.base`'s `/session`,
|
|
63
|
+
under the api service's `base: 'api'` — so the address is `GET /api/session/:sid/items`, computed
|
|
64
|
+
on demand by whoever asks. `elevate` is idempotent, so re-elevating an alias is allowed and guards
|
|
65
|
+
given at elevation are added to the declared ones.
|
|
66
|
+
|
|
52
67
|
Change a route or schema in `common` and both sides stay in sync. See [[entrypoint]], [[route]],
|
|
53
68
|
[[server-app]], [[web-client]], [[web-panel]].
|
|
54
69
|
|
|
@@ -61,6 +76,8 @@ prefixes API routes with `/api`:
|
|
|
61
76
|
const cfg = service({ type: AppType.Frontend, service: APP_WEB, host: 'localhost', port: 3001 })
|
|
62
77
|
service({ type: AppType.Backend, service: APP_API, host: 'localhost', port: 3000, base: 'api' }, cfg)
|
|
63
78
|
cfg.debug = { all: true }
|
|
79
|
+
cfg.alias = APP
|
|
80
|
+
cfg.security = { unsecure: true } // local dev serves the API over plain HTTP
|
|
64
81
|
export const commonConfig = cfg
|
|
65
82
|
```
|
|
66
83
|
|
|
@@ -75,9 +92,20 @@ appendStaticResource(context, SESSION_ITEMS) // @owlmeans/static-resource —
|
|
|
75
92
|
```
|
|
76
93
|
|
|
77
94
|
Handlers use `handleRequest` / `handleBody` / `handleParams` (validated payload, then context, then
|
|
78
|
-
req). Read/write `ctx.getStaticResource<T>(alias)` — full
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
req). Read/write `ctx.getStaticResource<T>(alias)` — the full resource contract
|
|
96
|
+
(`get/load/list/count/create/update/save/delete/take/purge`), so the resource answers the whole
|
|
97
|
+
question rather than the handler filtering afterwards:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const { items } = await resource.list(
|
|
101
|
+
{ sessionId: params.sid },
|
|
102
|
+
{ sort: [{ field: 'createdAt', order: 'desc' }] }
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`list` returns `{ items, total }`; the in-memory backends are unpaged unless a `size` is asked for.
|
|
107
|
+
`main(context, appEntrypoints)` starts the server. See [[static-resource]], [[server-app]],
|
|
108
|
+
[[resource]].
|
|
81
109
|
|
|
82
110
|
Swap `@owlmeans/static-resource` for [[mongo-resource]] / [[redis-resource]] when you need
|
|
83
111
|
persistence — the handler shape is identical.
|
|
@@ -86,19 +114,38 @@ persistence — the handler shape is identical.
|
|
|
86
114
|
|
|
87
115
|
`@owlmeans/web-panel`'s `PanelApp` is shadcn/Tailwind v4 (no MUI). The **app provides** the shadcn
|
|
88
116
|
primitives at the `@` alias — `web-panel` references `@/lib/utils` and
|
|
89
|
-
`@/components/ui/{alert,button,card,input,label,progress}`; copy those into `src/`.
|
|
90
|
-
`
|
|
117
|
+
`@/components/ui/{alert,button,card,input,label,navigation-menu,progress}`; copy those into `src/`.
|
|
118
|
+
Routing resolves itself from the active router plugin, so `PanelApp` takes no router prop —
|
|
119
|
+
`render.tsx` is one line and `index.tsx` calls it:
|
|
91
120
|
|
|
92
121
|
```tsx
|
|
93
|
-
|
|
122
|
+
// render.tsx — import { render as basicRender } from '@owlmeans/web-client'
|
|
123
|
+
basicRender(<PanelApp context={context} />)
|
|
94
124
|
```
|
|
95
125
|
|
|
96
126
|
`vite.config.ts` sets `@`→`src`, `@tailwindcss/vite`, and dedupes the owlmeans/react singletons.
|
|
97
127
|
`index.css` is `@import "tailwindcss";` + a shadcn `@theme` token block (replaces `@owlmeans/owl-theme`).
|
|
98
|
-
A parent `BASE` route renders the layout via `handler(
|
|
99
|
-
|
|
128
|
+
A parent `BASE` route renders the layout via `handler(MainLayout)`; `HOME` is its default child, declared `frontend({ default: true, parent: BASE })`.
|
|
129
|
+
`index.tsx` calls `context.registerEntrypoints(appEntrypoints)` and `context.serviceRoute(...)` for
|
|
130
|
+
each service, then renders.
|
|
131
|
+
|
|
132
|
+
Screens address the backend through three explicit verbs on the entrypoint:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
const items = await ctx.entrypoint<ClientEntrypoint<Item[]>>(session.list).call({ params: { sid } })
|
|
136
|
+
const { value, outcome } = await ctx.entrypoint<ClientEntrypoint<Item>>(session.add).invoke({ body })
|
|
137
|
+
const href = await ctx.entrypoint<ClientEntrypoint<string>>(web.about).url()
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`call` resolves to the VALUE and throws the reply's error; `invoke` gives `{ value, outcome }` when
|
|
141
|
+
the outcome decides what happens next; `url` gives the address (`{ absolute: true }` forces a fully
|
|
142
|
+
qualified one). A screen entrypoint answers `url()` and refuses `call()` — a screen is navigated to.
|
|
100
143
|
See [[web-panel]], [[web-client]], [[shadcn-web]], [[client-entrypoint]].
|
|
101
144
|
|
|
145
|
+
The screen keeps nothing in component state: `makeContext` registers a `@owlmeans/state` resource,
|
|
146
|
+
the fetch writes what came back into it with `store.replace(items)`, and `useStoreList` renders the
|
|
147
|
+
live subscription. See [[state]].
|
|
148
|
+
|
|
102
149
|
## Authentication
|
|
103
150
|
|
|
104
151
|
This shape is intentionally **auth-free**. To add it: `@owlmeans/server-auth` + `@owlmeans/client-auth`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory-promotion
|
|
3
|
-
description: Transform procedure-shaped or repeatedly-used memory into skills
|
|
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
|
|
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
|
|
83
|
-
|
|
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
|
|
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`)
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
47
|
+
## Folding in an outside store
|
|
47
48
|
|
|
48
|
-
1. Union
|
|
49
|
-
|
|
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.
|
|
52
|
-
|
|
53
|
-
4. When the new store verifies (below), delete
|
|
52
|
+
3. `## Skills` / "Key Files" index sections are dropped — skills self-describe; harness layout
|
|
53
|
+
belongs to `AGENTS.md`. Move genuinely non-obvious dispatch hints there.
|
|
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
|
-
-
|
|
66
|
-
-
|
|
67
|
-
|
|
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
|
|
|
@@ -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.**
|
|
22
|
-
|
|
23
|
-
does.
|
|
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,13 +47,31 @@ 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
|
|
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.12` 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:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@owlmeans/queue": "^0.1.18-rc.9"
|
|
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,
|
|
74
|
+
its resources, services, entrypoints, and helpers. A small extension of a framework package beats a new
|
|
49
75
|
parallel implementation.
|
|
50
76
|
|
|
51
77
|
## 3. No package? Reuse code and extract an abstraction
|
|
@@ -60,5 +86,7 @@ Once code is written, review it: can it be **shorter, clearer, or expressed with
|
|
|
60
86
|
Lean on framework utilities, remove dead branches, collapse needless indirection. Less code that reuses
|
|
61
87
|
the framework is better than more bespoke code.
|
|
62
88
|
|
|
63
|
-
See `[[
|
|
64
|
-
`
|
|
89
|
+
See `[[scaffolding]]` for how a project is assembled, and `[[agent-skills]]` for keeping the
|
|
90
|
+
deployed skill catalogue current. The package map itself is `tree.md` at the root of the
|
|
91
|
+
[owlmeans/common](https://github.com/owlmeans/common) repository — layer by layer, every package
|
|
92
|
+
and what it depends on.
|
|
@@ -27,7 +27,8 @@ 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?
|
|
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.
|
|
@@ -46,11 +47,24 @@ rewrite recipe is `memory-promotion` → Distillation.
|
|
|
46
47
|
|
|
47
48
|
Test: a finished skill reads as though the feature was always this way.
|
|
48
49
|
|
|
49
|
-
##
|
|
50
|
+
## Skills this project does not own
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
A skill that came from somewhere else is not edited here, and the two kinds fail differently:
|
|
53
|
+
|
|
54
|
+
- An entry under `.agents/linked-skills/<name>` is a symlink into the repo or installed package
|
|
55
|
+
that owns the skill. Writing through it edits the owner's own file — an unrequested change in
|
|
56
|
+
another project, which nothing here undoes: the link script only creates and prunes symlinks.
|
|
57
|
+
- A skill placed by the `@owlmeans/agent-skills` installer is a real file at
|
|
58
|
+
`.agents/skills/<name>/SKILL.md` carrying
|
|
59
|
+
`<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->`. The next install
|
|
60
|
+
overwrites every file still carrying that banner, so an edit made under it is lost; strip the
|
|
61
|
+
banner and the file instead becomes a conflict the installer skips and reports.
|
|
62
|
+
|
|
63
|
+
When such a skill gained an important usage pattern during the work, capture the pattern in a
|
|
64
|
+
local skill under `.agents/skills/`, named after the pattern — a local skill named after the
|
|
65
|
+
upstream one shadows it for the whole project — and list the upstream skill in the report as an
|
|
66
|
+
upstream candidate. Changing the skill at its source is a separate change in the owning project,
|
|
67
|
+
made only on explicit operator request.
|
|
54
68
|
|
|
55
69
|
## External docs
|
|
56
70
|
|
|
@@ -9,13 +9,26 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
# Authoring agent guidance (skills)
|
|
11
11
|
|
|
12
|
-
OwlMeans
|
|
12
|
+
An OwlMeans project carries agent guidance in up to three places:
|
|
13
13
|
|
|
14
14
|
| What | Where | Loaded |
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| Always-on project context | `AGENTS.md` at the repo root | every session |
|
|
17
|
+
| Always-on policy, kept out of `AGENTS.md` | `.agents/rules/<topic>.md`, pulled in from `AGENTS.md` by an `@.agents/rules/<topic>.md` import line | every session |
|
|
17
18
|
| Topic guidance | `.agents/skills/<name>/SKILL.md` | on demand, by topic or `/<name>` |
|
|
18
19
|
|
|
20
|
+
The middle tier is optional and belongs to a repo that has grown standing policy of its own. A
|
|
21
|
+
project scaffolded by `@owlmeans/create-app` starts without a `.agents/rules/` directory: its
|
|
22
|
+
generated `AGENTS.md` states the git policy inline and points at the seeded `git` skill for the
|
|
23
|
+
rest. Follow the layout the project already has — add a rules file only where `AGENTS.md` already
|
|
24
|
+
imports one.
|
|
25
|
+
|
|
26
|
+
**Rule or skill?** A rule is policy that has to hold whether or not anyone thought to load
|
|
27
|
+
anything — the git workflow is the standing example, whether a repo keeps it as
|
|
28
|
+
`.agents/rules/git.md` or inline in `AGENTS.md`. A skill is guidance for a task, loaded when that
|
|
29
|
+
task comes up. If it only matters while you are doing X, write a skill; if breaking it is wrong at
|
|
30
|
+
any moment, put it where `AGENTS.md` loads it every session.
|
|
31
|
+
|
|
19
32
|
`.agents/skills/` is the [Agent Skills](https://agentskills.io) standard location: GitHub Copilot
|
|
20
33
|
and Codex discover it natively. Claude Code reads skills only from `.claude/skills/`, so each skill
|
|
21
34
|
is bridged there by a generated symlink — see "Refresh the Claude Code links" below. **Write a skill
|
|
@@ -38,20 +51,35 @@ once; never author a per-agent copy** (`.github/instructions/*.instructions.md`,
|
|
|
38
51
|
name: my-skill # REQUIRED, must equal the directory name (lowercase, hyphens, ≤64 chars)
|
|
39
52
|
description: What it does and WHEN to use it. # REQUIRED, ≤1024 chars — the auto-invocation signal
|
|
40
53
|
user-invocable: true # false = background knowledge only, hidden from the / menu
|
|
41
|
-
allowed-tools: Bash(bun *) Read # optional —
|
|
54
|
+
allowed-tools: Bash(bun *), Read # optional — COMMA-separated; tools usable without per-call approval
|
|
42
55
|
metadata: # optional — anything non-standard goes here
|
|
43
56
|
scope: general
|
|
44
57
|
---
|
|
45
58
|
```
|
|
46
59
|
|
|
60
|
+
The `name` is not free: it is the key every store is keyed by, and a LOCAL skill always wins over
|
|
61
|
+
one that arrives from a dependency. Naming a skill after a package you depend on therefore hides
|
|
62
|
+
that package's own guidance completely, and the installer reports the file as a conflict on every
|
|
63
|
+
run. Give a skill about your own use of `@owlmeans/payment` a name of its own — `billing`, or your
|
|
64
|
+
product's name with a suffix — never `payment`.
|
|
65
|
+
|
|
47
66
|
The `description` is the most important field: every agent uses it to decide when to load the
|
|
48
67
|
skill, so state both the topic and the trigger ("Use when …"). Keep it under 1024 characters —
|
|
49
|
-
Copilot rejects longer ones.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
Copilot rejects longer ones. It is YAML, so a value containing `: ` (colon-space) has to be quoted
|
|
69
|
+
or the file stops parsing and the skill silently disappears from every agent.
|
|
70
|
+
|
|
71
|
+
`allowed-tools` is parsed as a list split on commas, newlines and YAML `-` bullets — never on
|
|
72
|
+
plain spaces. `Bash(bun *) Read` is read as one tool named `Bash(bun *) Read`, which matches
|
|
73
|
+
nothing; write `Bash(bun *), Read`.
|
|
74
|
+
|
|
75
|
+
Six keys are what the Agent Skills frontmatter parser in `@owlmeans/agent-skills` stores:
|
|
76
|
+
`name`, `description`, `license`, `compatibility`, `allowed-tools` and nested `metadata`.
|
|
77
|
+
Project-specific keys — including the OwlMeans `scope: general` routing marker, which sends a skill
|
|
78
|
+
to the installer bundle rather than to one package — belong under `metadata:`, so a skill stays
|
|
79
|
+
valid in every agent that reads it. The invocation switches sit outside what that parser stores and
|
|
80
|
+
are written at the top level alongside it: `user-invocable` (`false` hides a skill from the `/`
|
|
81
|
+
menu, marking it background knowledge), plus `disable-model-invocation` and `argument-hint`, which
|
|
82
|
+
Claude Code understands. Set any of them only when the skill needs it.
|
|
55
83
|
|
|
56
84
|
## Refresh the Claude Code links
|
|
57
85
|
|
|
@@ -61,9 +89,23 @@ After creating, renaming, or deleting a skill, run:
|
|
|
61
89
|
sh .agents/scripts/link-skills.sh
|
|
62
90
|
```
|
|
63
91
|
|
|
64
|
-
It creates `.claude/skills/<name>` → `../../.agents/skills/<name>` for every skill and prunes
|
|
65
|
-
whose skill is gone. The links are gitignored and are recreated at session start
|
|
66
|
-
|
|
92
|
+
It creates `.claude/skills/<name>` → `../../.agents/skills/<name>` for every local skill and prunes
|
|
93
|
+
links whose skill is gone. The links are gitignored and are recreated at session start (a committed
|
|
94
|
+
`SessionStart` hook, and on every install where the project declares a root `prepare` script that
|
|
95
|
+
calls it), but a skill added mid-session is invisible to Claude Code until the script runs.
|
|
96
|
+
|
|
97
|
+
The same script also brings in the skills of everything this project depends on, from whichever of
|
|
98
|
+
two sources applies. In a linked checkout — a repo whose root `package.json` lists an upstream
|
|
99
|
+
repo's packages as workspace entries — it resolves those upstream repos first, recursing into each
|
|
100
|
+
one's own manifest up to four levels, and links the skills from the upstream's own
|
|
101
|
+
`.agents/skills/`. A project that declares no such linked upstream falls back to what a plain npm
|
|
102
|
+
install gives it: the read-only `agent-meta/skills/` copies shipped inside each installed
|
|
103
|
+
`@owlmeans/*` package. Either way the links land in `.agents/linked-skills/<name>` (Copilot, Codex)
|
|
104
|
+
and `.claude/skills/<name>` (Claude Code), with a generated `.agents/linked-skills/INDEX.md` listing
|
|
105
|
+
skill, origin and description. Load one by name exactly like a local skill. A local skill of the
|
|
106
|
+
same name always wins, and a nearer dependency wins over a farther one. The whole
|
|
107
|
+
`.agents/linked-skills/` directory is generated and git-ignored — never edit or commit it, and never
|
|
108
|
+
edit an `agent-meta/` copy: fix the skill in the package that ships it.
|
|
67
109
|
|
|
68
110
|
## Skill vs memory
|
|
69
111
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"remove": [
|
|
3
|
+
"sources/common/src/types.ts",
|
|
4
|
+
"sources/common/src/schemas.ts",
|
|
5
|
+
"sources/api/src/consts.ts",
|
|
6
|
+
"sources/api/src/app",
|
|
7
|
+
"sources/web/src/screens/about.tsx",
|
|
8
|
+
"sources/web/src/screens/session.tsx"
|
|
9
|
+
],
|
|
10
|
+
"overrides": {
|
|
11
|
+
"README.md": "README.bare.md",
|
|
12
|
+
"sources/common/src/consts.ts": "sources/common/src/consts.bare.ts",
|
|
13
|
+
"sources/common/src/entrypoints.ts": "sources/common/src/entrypoints.bare.ts",
|
|
14
|
+
"sources/common/src/index.ts": "sources/common/src/index.bare.ts",
|
|
15
|
+
"sources/api/src/context.ts": "sources/api/src/context.bare.ts",
|
|
16
|
+
"sources/api/src/entrypoints.ts": "sources/api/src/entrypoints.bare.ts",
|
|
17
|
+
"sources/api/src/types.ts": "sources/api/src/types.bare.ts",
|
|
18
|
+
"sources/web/src/context.ts": "sources/web/src/context.bare.ts",
|
|
19
|
+
"sources/web/src/entrypoints.ts": "sources/web/src/entrypoints.bare.ts",
|
|
20
|
+
"sources/web/src/nav.ts": "sources/web/src/nav.bare.ts",
|
|
21
|
+
"sources/web/src/screens/home.tsx": "sources/web/src/screens/home.bare.tsx"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/template/_gitignore
CHANGED
package/template/package.json
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "bun run --filter './sources/common' build && bun run --filter './sources/*' --parallel dev",
|
|
12
12
|
"build": "bun run --filter './sources/*' build",
|
|
13
|
-
"typecheck": "bun run --filter './sources/*' typecheck"
|
|
13
|
+
"typecheck": "bun run --filter './sources/*' typecheck",
|
|
14
|
+
"prepare": "sh -c 'test -f .agents/scripts/link-skills.sh && sh .agents/scripts/link-skills.sh || true'"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"nodemon": "^3.1.14"
|
|
@@ -7,9 +7,11 @@ export const list = handleParams<SessionParams>(async (params, context) => {
|
|
|
7
7
|
const ctx = context as Context
|
|
8
8
|
const resource = ctx.getStaticResource<SessionItem>(SESSION_ITEMS)
|
|
9
9
|
|
|
10
|
-
// The
|
|
11
|
-
const { items } = await resource.list
|
|
10
|
+
// The resource answers the whole question — this session's items, newest first.
|
|
11
|
+
const { items } = await resource.list(
|
|
12
|
+
{ sessionId: params.sid },
|
|
13
|
+
{ sort: [{ field: 'createdAt', order: 'desc' }] }
|
|
14
|
+
)
|
|
15
|
+
|
|
12
16
|
return items
|
|
13
|
-
.filter(item => item.sessionId === params.sid)
|
|
14
|
-
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))
|
|
15
17
|
})
|
|
@@ -7,7 +7,7 @@ export const remove = handleParams<ItemParams>(async (params, context) => {
|
|
|
7
7
|
const ctx = context as Context
|
|
8
8
|
const resource = ctx.getStaticResource<SessionItem>(SESSION_ITEMS)
|
|
9
9
|
|
|
10
|
-
const existing = await resource.load
|
|
10
|
+
const existing = await resource.load(params.id)
|
|
11
11
|
// Only remove the item if it belongs to the requesting session.
|
|
12
12
|
if (existing == null || existing.sessionId !== params.sid) {
|
|
13
13
|
return { removed: false }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { makeContext as makeBasicContext } from '@owlmeans/server-app'
|
|
2
|
+
import type { Config, Context } from './types.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where the app's resources are registered. `@owlmeans/static-resource` is already a dependency,
|
|
6
|
+
* so `appendStaticResource<C, T>(context, ALIAS)` gives you an in-memory store with no database
|
|
7
|
+
* behind it; a mongo/postgres resource takes its place once the data has to outlive the process.
|
|
8
|
+
* Whatever you append here must also widen `Context` in `types.ts`, or the getter will not exist.
|
|
9
|
+
*/
|
|
10
|
+
export const makeContext = <C extends Config, T extends Context<C>>(cfg: C): T =>
|
|
11
|
+
makeBasicContext<C, T>(cfg, true)
|
|
@@ -10,9 +10,5 @@ export const makeContext = <C extends Config, T extends Context<C>>(cfg: C): T =
|
|
|
10
10
|
// data lives in process memory and is cleared when the api restarts.
|
|
11
11
|
appendStaticResource<C, T>(context, SESSION_ITEMS)
|
|
12
12
|
|
|
13
|
-
// A child context has to inherit THIS factory, not the layer's — otherwise a derived context
|
|
14
|
-
// is built without the resource registered above and every lookup on it throws.
|
|
15
|
-
context.makeContext = makeContext as typeof context.makeContext
|
|
16
|
-
|
|
17
13
|
return context
|
|
18
14
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { entrypoints } from '@owlmeans/server-app'
|
|
2
|
+
import { sharedEntrypoints } from '__APP_SLUG__-common'
|
|
3
|
+
|
|
4
|
+
// Handlers attach to the shared declarations, never to a route re-declared here:
|
|
5
|
+
// `elevate(sharedEntrypoints, alias, handler)` from '@owlmeans/server-app'.
|
|
6
|
+
export const appEntrypoints = [...entrypoints, ...sharedEntrypoints]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { elevate, entrypoints } from '@owlmeans/server-app'
|
|
2
|
+
import { session, sessionEntrypoints } from '__APP_SLUG__-common'
|
|
3
|
+
import * as handlers from './app/session/index.js'
|
|
4
|
+
|
|
5
|
+
// Attach handler implementations to the shared entrypoint declarations.
|
|
6
|
+
elevate(sessionEntrypoints, session.base)
|
|
7
|
+
elevate(sessionEntrypoints, session.list, handlers.list)
|
|
8
|
+
elevate(sessionEntrypoints, session.add, handlers.add)
|
|
9
|
+
elevate(sessionEntrypoints, session.remove, handlers.remove)
|
|
10
|
+
|
|
11
|
+
export const appEntrypoints = [...entrypoints, ...sessionEntrypoints]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { main } from '@owlmeans/server-app'
|
|
2
2
|
import config from './config.js'
|
|
3
3
|
import { makeContext } from './context.js'
|
|
4
|
-
import {
|
|
4
|
+
import { appEntrypoints } from './entrypoints.js'
|
|
5
5
|
import type { Config, Context } from './types.js'
|
|
6
6
|
|
|
7
7
|
const context = makeContext<Config, Context>(config)
|
|
8
8
|
|
|
9
|
-
main<{}, Config, Context>(context,
|
|
9
|
+
main<{}, Config, Context>(context, appEntrypoints)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Service aliases shared between web and api. */
|
|
2
|
+
export const APP = '__APP_SLUG__'
|
|
3
|
+
export const APP_WEB = '__APP_SLUG__-web'
|
|
4
|
+
export const APP_API = '__APP_SLUG__-api'
|
|
5
|
+
|
|
6
|
+
/** Local development ports. */
|
|
7
|
+
export const WEB_PORT = 3001
|
|
8
|
+
export const API_PORT = 3000
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CommonEntrypoint } from '@owlmeans/entrypoint'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The entrypoint declarations both sides share: the api elevates them with handlers, the web
|
|
5
|
+
* elevates them with screens or just calls them. Routes resolve under the api service `base`
|
|
6
|
+
* (`/api`), so an `entrypoint(route(alias, '/items', ...))` added here answers on `/api/items`.
|
|
7
|
+
*/
|
|
8
|
+
export const sharedEntrypoints: CommonEntrypoint[] = []
|
|
@@ -9,7 +9,7 @@ import type { AddItemPayload, ItemParams, SessionParams } from './types.js'
|
|
|
9
9
|
* elevates them with screen components and calls them. Routes resolve under the
|
|
10
10
|
* api service `base` (`/api`), so e.g. `session.list` → `GET /api/session/:sid/items`.
|
|
11
11
|
*/
|
|
12
|
-
export const
|
|
12
|
+
export const sessionEntrypoints = [
|
|
13
13
|
entrypoint(route(session.base, '/session')),
|
|
14
14
|
entrypoint(
|
|
15
15
|
route(session.list, '/:sid/items', { parent: session.base, method: RouteMethod.GET }),
|