@owlmeans/create-app 0.1.18-rc.2 → 0.1.18-rc.21
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 +38 -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 +28 -1
- package/template/README.bare.md +57 -0
- package/template/README.md +5 -61
- 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 +26 -86
- 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 +3 -2
- package/template/sources/api/package.json +3 -3
- package/template/sources/api/src/app/session/add.ts +7 -6
- package/template/sources/api/src/app/session/list.ts +12 -9
- package/template/sources/api/src/app/session/remove.ts +7 -6
- package/template/sources/api/src/context.bare.ts +11 -0
- package/template/sources/api/src/entrypoints.bare.ts +4 -0
- package/template/sources/api/src/entrypoints.ts +13 -0
- package/template/sources/api/src/index.ts +2 -2
- package/template/sources/api/src/types.bare.ts +5 -0
- package/template/sources/common/package.json +6 -6
- package/template/sources/common/src/consts.bare.ts +8 -0
- package/template/sources/common/src/consts.ts +0 -13
- package/template/sources/common/src/entrypoints.bare.ts +14 -0
- package/template/sources/common/src/entrypoints.ts +60 -0
- package/template/sources/common/src/index.bare.ts +3 -0
- package/template/sources/common/src/index.ts +1 -1
- package/template/sources/common/src/schemas.ts +7 -7
- package/template/sources/web/index.html +3 -1
- package/template/sources/web/package.json +13 -11
- package/template/sources/web/src/components/ui/navigation-menu.tsx +169 -0
- package/template/sources/web/src/context.bare.ts +13 -0
- package/template/sources/web/src/context.ts +14 -2
- package/template/sources/web/src/entrypoints.bare.ts +13 -0
- package/template/sources/web/src/entrypoints.ts +16 -0
- package/template/sources/web/src/index.css +7 -0
- package/template/sources/web/src/index.tsx +2 -2
- package/template/sources/web/src/layout/main.tsx +16 -19
- package/template/sources/web/src/nav.bare.ts +21 -0
- package/template/sources/web/src/nav.ts +28 -0
- package/template/sources/web/src/screens/about.tsx +19 -0
- package/template/sources/web/src/screens/home.bare.tsx +17 -0
- package/template/sources/web/src/screens/session.tsx +28 -14
- package/template/sources/web/src/vite-env.d.ts +1 -0
- package/template/sources/api/src/modules.ts +0 -11
- package/template/sources/common/src/modules.ts +0 -26
- package/template/sources/web/src/components/nav/main.tsx +0 -15
- package/template/sources/web/src/modules.ts +0 -20
package/template/AGENTS.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Project purpose
|
|
4
4
|
|
|
5
|
+
__APP_DESCRIPTION__
|
|
6
|
+
|
|
5
7
|
<!-- OWLMEANS:PROJECT-PURPOSE -->
|
|
6
8
|
> **Agents — this project has not been described yet.** Before doing anything else on the first
|
|
7
9
|
> session, ask the user what this project is for: its purpose, domain, goals, and key constraints.
|
|
8
10
|
> Then replace this whole block with a short description of the project, and remove this notice.
|
|
11
|
+
<!-- /OWLMEANS:PROJECT-PURPOSE -->
|
|
9
12
|
|
|
10
13
|
## Git Workflow (mandatory)
|
|
11
14
|
|
|
@@ -76,6 +79,14 @@ package (in the installed packages and at https://github.com/owlmeans/common) or
|
|
|
76
79
|
already solves the problem **before** proposing a third-party library or a custom solution, and
|
|
77
80
|
simplify whatever you do write. This is required for every planning and development task.
|
|
78
81
|
|
|
82
|
+
## Runtime config advertisement
|
|
83
|
+
|
|
84
|
+
The config endpoint is default-deny. A backend config field reaches the browser only when the
|
|
85
|
+
package that owns its browser consumer imports `apiConfigPlugin({ allow, deny? })` from
|
|
86
|
+
`@owlmeans/api-config` at module scope. Name public nested fields precisely; use `every()` and a
|
|
87
|
+
nested `deny` selector when a public collection carries a credential. Databases, queues, SMTP,
|
|
88
|
+
tokens, secrets and internal addresses never belong in an `allow` selector.
|
|
89
|
+
|
|
79
90
|
## Skills
|
|
80
91
|
|
|
81
92
|
Reusable guidance lives in `.agents/skills/<name>/SKILL.md`, deployed by `@owlmeans/agent-skills`
|
|
@@ -83,13 +94,29 @@ from the installed `@owlmeans/*` packages. Agents load a skill by topic, or you
|
|
|
83
94
|
explicitly. Copilot and Codex read `.agents/skills/` directly; Claude Code reads the generated
|
|
84
95
|
symlinks in `.claude/skills/` (see `CLAUDE.md`).
|
|
85
96
|
|
|
86
|
-
- After adding or updating any `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills` to refresh
|
|
97
|
+
- After adding or updating any `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills@^0.1.18-rc.11` to refresh
|
|
87
98
|
the deployed skills.
|
|
88
99
|
- Deployed files carry an `AUTO-GENERATED` banner and are refreshed in place — never hand-edit them.
|
|
89
100
|
- To capture your own guidance, see the `skill-authoring` skill; to turn repeatedly-used memory into
|
|
90
101
|
a skill, `memory-promotion`. Keep it inside this repository, in `.agents/skills/` — never in a
|
|
91
102
|
per-agent or home directory.
|
|
92
103
|
|
|
104
|
+
<!-- OWLMEANS:LINKED-SKILLS -->
|
|
105
|
+
### Skills linked from installed packages
|
|
106
|
+
|
|
107
|
+
`sh .agents/scripts/link-skills.sh` also links in the skills that ship inside the installed
|
|
108
|
+
`@owlmeans/*` packages (`node_modules/@owlmeans/<pkg>/agent-meta/skills/<name>`), so they load
|
|
109
|
+
by name (`/<name>`) like a local skill. They land in `.agents/linked-skills/<name>` for Copilot
|
|
110
|
+
and Codex and in `.claude/skills/<name>` for Claude Code, with a `skill / origin repo /
|
|
111
|
+
description` table in `.agents/linked-skills/INDEX.md`. A local skill of the same name always
|
|
112
|
+
wins. The directory is generated and git-ignored — never edit or commit it.
|
|
113
|
+
|
|
114
|
+
The root `prepare` script runs it on every `bun install`, and a session-start hook runs it
|
|
115
|
+
again, so a fresh checkout carries the links for every agent — Copilot and Codex included,
|
|
116
|
+
not just Claude Code.
|
|
117
|
+
|
|
118
|
+
<!-- /OWLMEANS:LINKED-SKILLS -->
|
|
119
|
+
|
|
93
120
|
## Develop
|
|
94
121
|
|
|
95
122
|
```sh
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
__APP_DESCRIPTION__
|
|
4
|
+
|
|
5
|
+
A fullstack [OwlMeans Common](https://github.com/owlmeans/common) app, scaffolded with
|
|
6
|
+
[`@owlmeans/create-app`](https://www.npmjs.com/package/@owlmeans/create-app) as a bare shell —
|
|
7
|
+
the wiring is complete and there is no example code to delete.
|
|
8
|
+
|
|
9
|
+
## Workspaces
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
sources/
|
|
13
|
+
├── common/ # shared entrypoints (routes) and config — used by api AND web
|
|
14
|
+
├── api/ # @owlmeans/server-app backend
|
|
15
|
+
└── web/ # @owlmeans/web-panel + shadcn UI: navigation, layout and screens
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Develop
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
bun install
|
|
22
|
+
bun run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- API: http://localhost:3000
|
|
26
|
+
- Web: http://localhost:3001
|
|
27
|
+
|
|
28
|
+
> **Note:** The API runs over plain HTTP in dev (`cfg.security = { unsecure: true }` in
|
|
29
|
+
> `sources/common/src/config.ts`). If you edit `sources/common`, restart `bun run dev` to
|
|
30
|
+
> rebuild it before the API and web pick up the changes.
|
|
31
|
+
|
|
32
|
+
## Adding your first feature
|
|
33
|
+
|
|
34
|
+
1. **`sources/common/src/entrypoints.ts`** — declare the route as an OwlMeans *entrypoint* and
|
|
35
|
+
give it an AJV filter. Both sides import this one declaration; neither re-declares the route.
|
|
36
|
+
2. **`sources/api`** — register a resource in `src/context.ts` (widening `Context` in
|
|
37
|
+
`src/types.ts`), then `bind()` the entrypoint with a handler in `src/entrypoints.ts`.
|
|
38
|
+
3. **`sources/web`** — add a screen under `src/screens`, hang it off a frontend route in
|
|
39
|
+
`src/entrypoints.ts`, list it in `src/nav.ts`, and call the backend with
|
|
40
|
+
`context.entrypoint(protocol).call({ params, body })`.
|
|
41
|
+
|
|
42
|
+
See the [OwlMeans getting-started guide](https://github.com/owlmeans/common/blob/main/docs/getting-started.md)
|
|
43
|
+
for a worked example of all three steps.
|
|
44
|
+
|
|
45
|
+
## Agent guidance
|
|
46
|
+
|
|
47
|
+
This project ships agent context in `AGENTS.md` and a `CLAUDE.md` bridge, with skills in
|
|
48
|
+
`.agents/skills/` and a shared memory store at `.agents/memory/`.
|
|
49
|
+
|
|
50
|
+
Files carrying an `AUTO-GENERATED` banner are managed by
|
|
51
|
+
[`@owlmeans/agent-skills`](https://www.npmjs.com/package/@owlmeans/agent-skills) — don't hand-edit
|
|
52
|
+
them; write your own guidance as separate, un-bannered files. Refresh after adding or upgrading
|
|
53
|
+
`@owlmeans/*` packages:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.11
|
|
57
|
+
```
|
package/template/README.md
CHANGED
|
@@ -1,65 +1,9 @@
|
|
|
1
1
|
# __APP_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[`@owlmeans/create-app`](https://www.npmjs.com/package/@owlmeans/create-app).
|
|
3
|
+
__APP_DESCRIPTION__
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
An OwlMeans application has shared protocol declarations in `sources/common`, server bindings in
|
|
6
|
+
`sources/api`, and browser bindings/screens in `sources/web`. Contracts, request schemas, guards
|
|
7
|
+
and gates are declared once in common; runtime packages bind them without changing the declaration.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
sources/
|
|
12
|
-
├── common/ # shared entrypoints (routes), schemas and types — used by api AND web
|
|
13
|
-
├── api/ # @owlmeans/server-app backend; session items in @owlmeans/static-resource
|
|
14
|
-
└── web/ # @owlmeans/web-panel + shadcn UI: navigation, layout and screens
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Develop
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
bun install
|
|
21
|
-
bun run dev
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
- API: http://localhost:3000
|
|
25
|
-
- Web: http://localhost:3001
|
|
26
|
-
|
|
27
|
-
Open the **Session** page and add/remove items — they are stored per browser session
|
|
28
|
-
(a `sid` kept in `localStorage`) in an in-memory resource on the API. Restarting the API
|
|
29
|
-
clears them; opening a different browser/incognito window gets an isolated session.
|
|
30
|
-
|
|
31
|
-
> **Note:** The API runs over plain HTTP in dev (`cfg.security = { unsecure: true }` in
|
|
32
|
-
> `sources/common/src/config.ts`). If you edit `sources/common`, restart `bun run dev` to
|
|
33
|
-
> rebuild it before the API and web pick up the changes.
|
|
34
|
-
|
|
35
|
-
## How it fits together
|
|
36
|
-
|
|
37
|
-
1. **`sources/common`** declares the API routes as OwlMeans *entrypoints* (`session.list`,
|
|
38
|
-
`session.add`, `session.remove`) plus their AJV schemas and shared types.
|
|
39
|
-
2. **`sources/api`** registers a `@owlmeans/static-resource` in its context and `elevate()`s
|
|
40
|
-
each entrypoint with a handler that does CRUD against it, keyed by the session id.
|
|
41
|
-
3. **`sources/web`** `elevate()`s the same entrypoints to screen components and calls them with
|
|
42
|
-
`context.entrypoint(alias).call({ params, body })`.
|
|
43
|
-
|
|
44
|
-
See [OwlMeans getting-started guide](https://github.com/owlmeans/common/blob/main/docs/getting-started.md)
|
|
45
|
-
for a full walkthrough and the manual (non-scaffolded) version of this project.
|
|
46
|
-
|
|
47
|
-
## Agent guidance
|
|
48
|
-
|
|
49
|
-
This project ships agent context in `CLAUDE.md` and `.github/copilot-instructions.md`. Both carry the
|
|
50
|
-
same four mandatory sections a real OwlMeans monorepo uses — **Git Workflow**, **Reporting**,
|
|
51
|
-
**Memory**, **Self-Education** — plus a project-purpose placeholder the agent fills in on its first
|
|
52
|
-
session.
|
|
53
|
-
|
|
54
|
-
Project memory is a single shared graph store at `.agents/memory/` (index `MEMORY.md`), used by both
|
|
55
|
-
Claude Code and Copilot — never write memory anywhere else.
|
|
56
|
-
|
|
57
|
-
Reusable guidance lives in `.claude/skills/` (Claude Code) and `.github/instructions/` (Copilot).
|
|
58
|
-
Files carrying an `AUTO-GENERATED` banner are managed by
|
|
59
|
-
[`@owlmeans/agent-skills`](https://www.npmjs.com/package/@owlmeans/agent-skills) — don't hand-edit
|
|
60
|
-
them; write your own guidance as separate, un-bannered files. Refresh after adding or upgrading
|
|
61
|
-
`@owlmeans/*` packages:
|
|
62
|
-
|
|
63
|
-
```sh
|
|
64
|
-
npx @owlmeans/agent-skills
|
|
65
|
-
```
|
|
9
|
+
Use `bun run build` from the project root to build every workspace.
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# link-skills.sh
|
|
3
3
|
#
|
|
4
|
-
# Bridge
|
|
5
|
-
# Claude Code, which discovers skills only under .claude/skills/. Copilot and
|
|
6
|
-
# Codex read .agents/skills/ natively and need nothing from this script.
|
|
4
|
+
# Bridge every skill an agent should see into the two places agents look:
|
|
7
5
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# 1. LOCAL .agents/skills/<name> -> .claude/skills/<name>
|
|
7
|
+
# 2. LINKED <upstream>/.agents/skills/<n> -> .agents/linked-skills/<n>
|
|
8
|
+
# -> .claude/skills/<n>
|
|
9
|
+
#
|
|
10
|
+
# Copilot and Codex read .agents/skills/ and .agents/linked-skills/ natively;
|
|
11
|
+
# Claude Code discovers skills only under .claude/skills/, so both passes land
|
|
12
|
+
# there too. Each skill becomes its own symlink — Claude Code supports
|
|
13
|
+
# per-skill symlink entries, but not a symlinked skills directory.
|
|
10
14
|
#
|
|
11
15
|
# USAGE
|
|
12
16
|
# sh .agents/scripts/link-skills.sh
|
|
@@ -14,28 +18,61 @@
|
|
|
14
18
|
# BEHAVIOUR
|
|
15
19
|
# - creates .claude/skills/<name> -> ../../.agents/skills/<name> for every
|
|
16
20
|
# .agents/skills/<name>/SKILL.md
|
|
17
|
-
# -
|
|
21
|
+
# - resolves the upstream repos this one depends on from the root
|
|
22
|
+
# package.json workspace entries of the form "libraries/<dep>/packages/...",
|
|
23
|
+
# recursing into each upstream's own package.json (visited list, depth cap
|
|
24
|
+
# of 4). A repo that declares no libraries/* workspace entries falls back
|
|
25
|
+
# to the layout a standalone npm consumer gets: the generated
|
|
26
|
+
# agent-meta/skills/<name> copies that ship inside the installed @owlmeans
|
|
27
|
+
# packages. That fallback scans node_modules/@owlmeans at the project root
|
|
28
|
+
# AND in every directory up to two levels below it (sources/api/node_modules
|
|
29
|
+
# and the like), because a workspace package keeps its own node_modules
|
|
30
|
+
# whenever the linker does not hoist its dependencies to the root. It never
|
|
31
|
+
# descends into a node_modules tree, skips any directory whose real path
|
|
32
|
+
# leaves the root (a projects/<repo> or libraries/<repo> symlink is another
|
|
33
|
+
# checkout, not this project), and records each physical package once, keyed
|
|
34
|
+
# by resolved path, so a package reachable through several workspaces
|
|
35
|
+
# contributes its skills a single time.
|
|
36
|
+
# - links every upstream skill that no local skill shadows into BOTH
|
|
37
|
+
# .agents/linked-skills/<name> and .claude/skills/<name>, pointing at the
|
|
38
|
+
# upstream's real directory (never at another symlink). A local skill
|
|
39
|
+
# always wins; a nearer dependency wins over a farther one.
|
|
40
|
+
# - writes .agents/linked-skills/INDEX.md — skill / origin repo / description
|
|
41
|
+
# in deterministic order — and rewrites it only when its content changes
|
|
42
|
+
# - prunes symlinks in both directories that dangle or no longer have a
|
|
43
|
+
# SKILL.md behind them, and removes .agents/linked-skills/ entirely when
|
|
44
|
+
# the repo has no upstream skills
|
|
18
45
|
# - never touches real directories or files (warns instead), never removes
|
|
19
46
|
# .gitkeep, and always exits 0 so it can run as a SessionStart hook
|
|
20
47
|
#
|
|
21
48
|
# The committed .claude/settings.json runs this at SessionStart. Run it by
|
|
22
49
|
# hand after creating, renaming, or deleting a skill mid-session.
|
|
50
|
+
#
|
|
51
|
+
# .agents/linked-skills/ is generated and git-ignored: the upstream repos are
|
|
52
|
+
# themselves symlinks, so the links only make sense inside a linked checkout.
|
|
23
53
|
|
|
24
54
|
set -u
|
|
25
55
|
|
|
26
|
-
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
27
|
-
ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
|
|
56
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd -P)
|
|
57
|
+
ROOT=$(cd "$SCRIPT_DIR/../.." && pwd -P)
|
|
28
58
|
SRC="$ROOT/.agents/skills"
|
|
29
59
|
DST="$ROOT/.claude/skills"
|
|
60
|
+
LNK="$ROOT/.agents/linked-skills"
|
|
61
|
+
MAX_DEPTH=4
|
|
62
|
+
|
|
63
|
+
NL='
|
|
64
|
+
'
|
|
65
|
+
TAB=$(printf '\t')
|
|
30
66
|
|
|
31
67
|
linked=0
|
|
68
|
+
dep_linked=0
|
|
32
69
|
pruned=0
|
|
33
70
|
skipped=0
|
|
34
71
|
|
|
35
72
|
mkdir -p "$DST" 2>/dev/null || true
|
|
36
73
|
[ -f "$DST/.gitkeep" ] || : > "$DST/.gitkeep" 2>/dev/null || true
|
|
37
74
|
|
|
38
|
-
# ---- link pass
|
|
75
|
+
# ---- local link pass -------------------------------------------------------
|
|
39
76
|
if [ -d "$SRC" ]; then
|
|
40
77
|
for skill_dir in "$SRC"/*/; do
|
|
41
78
|
[ -d "$skill_dir" ] || continue
|
|
@@ -59,6 +96,240 @@ if [ -d "$SRC" ]; then
|
|
|
59
96
|
done
|
|
60
97
|
fi
|
|
61
98
|
|
|
99
|
+
# ---- dependency resolution -------------------------------------------------
|
|
100
|
+
# An OwlMeans monorepo declares a linked upstream by listing that upstream's
|
|
101
|
+
# packages explicitly as "libraries/<dep>/packages/<pkg>" workspace entries.
|
|
102
|
+
# The dep name is therefore the second path segment of any such entry.
|
|
103
|
+
deps_of() {
|
|
104
|
+
pkg="$1/package.json"
|
|
105
|
+
[ -f "$pkg" ] || return 0
|
|
106
|
+
grep -o '"libraries/[A-Za-z0-9._-][A-Za-z0-9._-]*/packages' "$pkg" 2>/dev/null \
|
|
107
|
+
| sed 's|^"libraries/||; s|/packages$||' \
|
|
108
|
+
| awk '!seen[$0]++'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Resolve libraries/<dep> to the real directory it points at, so every link we
|
|
112
|
+
# create later targets a real path rather than a chain of symlinks.
|
|
113
|
+
resolve_repo() {
|
|
114
|
+
p="$1/libraries/$2"
|
|
115
|
+
[ -d "$p" ] || return 0
|
|
116
|
+
(cd "$p" 2>/dev/null && pwd -P)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Breadth-first walk: the frontier holds one depth level, so the first repo to
|
|
120
|
+
# claim a skill name is always the nearest dependency.
|
|
121
|
+
visited="$NL$ROOT$NL"
|
|
122
|
+
order=""
|
|
123
|
+
frontier=""
|
|
124
|
+
while IFS= read -r dep; do
|
|
125
|
+
[ -n "$dep" ] || continue
|
|
126
|
+
rp=$(resolve_repo "$ROOT" "$dep")
|
|
127
|
+
[ -n "$rp" ] && frontier="$frontier$rp$NL"
|
|
128
|
+
done <<EOF
|
|
129
|
+
$(deps_of "$ROOT")
|
|
130
|
+
EOF
|
|
131
|
+
|
|
132
|
+
depth=1
|
|
133
|
+
while [ "$depth" -le "$MAX_DEPTH" ] && [ -n "$frontier" ]; do
|
|
134
|
+
next=""
|
|
135
|
+
while IFS= read -r rp; do
|
|
136
|
+
[ -n "$rp" ] || continue
|
|
137
|
+
case "$visited" in
|
|
138
|
+
*"$NL$rp$NL"*) continue ;;
|
|
139
|
+
esac
|
|
140
|
+
visited="$visited$rp$NL"
|
|
141
|
+
order="$order$rp$NL"
|
|
142
|
+
while IFS= read -r dep; do
|
|
143
|
+
[ -n "$dep" ] || continue
|
|
144
|
+
crp=$(resolve_repo "$rp" "$dep")
|
|
145
|
+
[ -n "$crp" ] && next="$next$crp$NL"
|
|
146
|
+
done <<EOF
|
|
147
|
+
$(deps_of "$rp")
|
|
148
|
+
EOF
|
|
149
|
+
done <<EOF
|
|
150
|
+
$frontier
|
|
151
|
+
EOF
|
|
152
|
+
frontier="$next"
|
|
153
|
+
depth=$((depth + 1))
|
|
154
|
+
done
|
|
155
|
+
|
|
156
|
+
# ---- candidate collection --------------------------------------------------
|
|
157
|
+
TMPD=$(mktemp -d 2>/dev/null) || TMPD="${TMPDIR:-/tmp}/link-skills.$$"
|
|
158
|
+
mkdir -p "$TMPD" 2>/dev/null || true
|
|
159
|
+
CAND="$TMPD/candidates"
|
|
160
|
+
: > "$CAND" 2>/dev/null || CAND=""
|
|
161
|
+
|
|
162
|
+
seen_names="$NL"
|
|
163
|
+
chain=""
|
|
164
|
+
|
|
165
|
+
record_candidate() {
|
|
166
|
+
cname="$1"
|
|
167
|
+
corigin="$2"
|
|
168
|
+
cpath="$3"
|
|
169
|
+
[ -f "$cpath/SKILL.md" ] || return 0
|
|
170
|
+
# A local skill always shadows an upstream one of the same name.
|
|
171
|
+
[ -f "$SRC/$cname/SKILL.md" ] && return 0
|
|
172
|
+
case "$seen_names" in
|
|
173
|
+
*"$NL$cname$NL"*) return 0 ;;
|
|
174
|
+
esac
|
|
175
|
+
seen_names="$seen_names$cname$NL"
|
|
176
|
+
printf '%s\t%s\t%s\n' "$cname" "$corigin" "$cpath" >> "$CAND"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if [ -n "$CAND" ] && [ -n "$order" ]; then
|
|
180
|
+
while IFS= read -r rp; do
|
|
181
|
+
[ -n "$rp" ] || continue
|
|
182
|
+
origin=$(basename "$rp")
|
|
183
|
+
chain="$chain$origin -> "
|
|
184
|
+
for skill_dir in "$rp"/.agents/skills/*/; do
|
|
185
|
+
[ -d "$skill_dir" ] || continue
|
|
186
|
+
skill_dir="${skill_dir%/}"
|
|
187
|
+
record_candidate "$(basename "$skill_dir")" "$origin" "$skill_dir"
|
|
188
|
+
done
|
|
189
|
+
done <<EOF
|
|
190
|
+
$order
|
|
191
|
+
EOF
|
|
192
|
+
chain="${chain% -> }"
|
|
193
|
+
elif [ -n "$CAND" ]; then
|
|
194
|
+
# Standalone consumer: no libraries/* upstreams, so the skills ship inside
|
|
195
|
+
# the installed packages as generated agent-meta/ copies. Those packages
|
|
196
|
+
# are not necessarily at the project root: a workspace whose @owlmeans deps
|
|
197
|
+
# belong to its sources/* packages keeps them in each package's own
|
|
198
|
+
# node_modules unless the linker hoists. So collect the @owlmeans scope
|
|
199
|
+
# directory of the root and of every directory up to two levels below it.
|
|
200
|
+
# A directory is scanned only when it is part of THIS project: node_modules
|
|
201
|
+
# trees are never descended into, and a directory whose real path leaves the
|
|
202
|
+
# root — a projects/<repo> or libraries/<repo> symlink into a neighbouring
|
|
203
|
+
# checkout — belongs to that other repository, not to this one.
|
|
204
|
+
in_tree() {
|
|
205
|
+
rp=$(cd "$1" 2>/dev/null && pwd -P) || return 1
|
|
206
|
+
case "$rp/" in
|
|
207
|
+
"$ROOT"/*) return 0 ;;
|
|
208
|
+
esac
|
|
209
|
+
return 1
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
scope_dirs="$ROOT/node_modules/@owlmeans$NL"
|
|
213
|
+
for lvl1 in "$ROOT"/*/; do
|
|
214
|
+
[ -d "$lvl1" ] || continue
|
|
215
|
+
lvl1="${lvl1%/}"
|
|
216
|
+
[ "$(basename "$lvl1")" = "node_modules" ] && continue
|
|
217
|
+
in_tree "$lvl1" || continue
|
|
218
|
+
scope_dirs="$scope_dirs$lvl1/node_modules/@owlmeans$NL"
|
|
219
|
+
for lvl2 in "$lvl1"/*/; do
|
|
220
|
+
[ -d "$lvl2" ] || continue
|
|
221
|
+
lvl2="${lvl2%/}"
|
|
222
|
+
[ "$(basename "$lvl2")" = "node_modules" ] && continue
|
|
223
|
+
in_tree "$lvl2" || continue
|
|
224
|
+
scope_dirs="$scope_dirs$lvl2/node_modules/@owlmeans$NL"
|
|
225
|
+
done
|
|
226
|
+
done
|
|
227
|
+
|
|
228
|
+
# The same physical package is reachable through several workspaces once the
|
|
229
|
+
# linker hoists or symlinks it, so key the dedup on the resolved path.
|
|
230
|
+
seen_pkgs="$NL"
|
|
231
|
+
while IFS= read -r scope_dir; do
|
|
232
|
+
[ -n "$scope_dir" ] || continue
|
|
233
|
+
[ -d "$scope_dir" ] || continue
|
|
234
|
+
for pkg_dir in "$scope_dir"/*/; do
|
|
235
|
+
[ -d "$pkg_dir" ] || continue
|
|
236
|
+
pkg_dir="${pkg_dir%/}"
|
|
237
|
+
real=$(cd "$pkg_dir" 2>/dev/null && pwd -P) || continue
|
|
238
|
+
[ -n "$real" ] || continue
|
|
239
|
+
case "$seen_pkgs" in
|
|
240
|
+
*"$NL$real$NL"*) continue ;;
|
|
241
|
+
esac
|
|
242
|
+
seen_pkgs="$seen_pkgs$real$NL"
|
|
243
|
+
origin="@owlmeans/$(basename "$pkg_dir")"
|
|
244
|
+
for skill_dir in "$real"/agent-meta/skills/*/; do
|
|
245
|
+
[ -d "$skill_dir" ] || continue
|
|
246
|
+
skill_dir="${skill_dir%/}"
|
|
247
|
+
record_candidate "$(basename "$skill_dir")" "$origin" "$skill_dir"
|
|
248
|
+
done
|
|
249
|
+
done
|
|
250
|
+
done <<EOF
|
|
251
|
+
$scope_dirs
|
|
252
|
+
EOF
|
|
253
|
+
[ -s "$CAND" ] && chain="node_modules/@owlmeans/*/agent-meta/skills"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
cand_has() {
|
|
257
|
+
[ -n "$CAND" ] && [ -f "$CAND" ] || return 1
|
|
258
|
+
awk -F"$TAB" -v n="$1" '$1 == n { found = 1 } END { exit found ? 0 : 1 }' "$CAND"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# ---- dependency link pass --------------------------------------------------
|
|
262
|
+
if [ -n "$CAND" ] && [ -s "$CAND" ]; then
|
|
263
|
+
mkdir -p "$LNK" 2>/dev/null || true
|
|
264
|
+
while IFS="$TAB" read -r name origin path; do
|
|
265
|
+
[ -n "$name" ] || continue
|
|
266
|
+
ok=1
|
|
267
|
+
for target in "$LNK/$name" "$DST/$name"; do
|
|
268
|
+
if [ -e "$target" ] && [ ! -L "$target" ]; then
|
|
269
|
+
echo "link-skills: skip '$name' — a real file/directory shadows it in $(dirname "$target")" >&2
|
|
270
|
+
ok=0
|
|
271
|
+
continue
|
|
272
|
+
fi
|
|
273
|
+
ln -sfn "$path" "$target" 2>/dev/null || {
|
|
274
|
+
echo "link-skills: failed to link '$name' from $origin" >&2
|
|
275
|
+
ok=0
|
|
276
|
+
}
|
|
277
|
+
done
|
|
278
|
+
if [ "$ok" -eq 1 ]; then
|
|
279
|
+
dep_linked=$((dep_linked + 1))
|
|
280
|
+
else
|
|
281
|
+
skipped=$((skipped + 1))
|
|
282
|
+
fi
|
|
283
|
+
done < "$CAND"
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
# ---- index -----------------------------------------------------------------
|
|
287
|
+
# Read the description straight out of the SKILL.md frontmatter; fall back to
|
|
288
|
+
# the first heading. Pipes would break the table, so they are folded away.
|
|
289
|
+
describe() {
|
|
290
|
+
f="$1/SKILL.md"
|
|
291
|
+
[ -f "$f" ] || return 0
|
|
292
|
+
d=$(sed -n '/^---[[:space:]]*$/,/^---[[:space:]]*$/p' "$f" \
|
|
293
|
+
| sed -n 's/^description:[[:space:]]*//p' | head -1)
|
|
294
|
+
[ -n "$d" ] || d=$(sed -n 's/^#[[:space:]][[:space:]]*//p' "$f" | head -1)
|
|
295
|
+
d=$(printf '%s' "$d" | sed "s/^[\"']//; s/[\"']\$//; s/|/\//g")
|
|
296
|
+
if [ "${#d}" -gt 160 ]; then
|
|
297
|
+
d=$(printf '%s' "$d" | cut -c1-157)...
|
|
298
|
+
fi
|
|
299
|
+
printf '%s' "$d"
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if [ -n "$CAND" ] && [ -s "$CAND" ]; then
|
|
303
|
+
{
|
|
304
|
+
echo "# Linked Skills"
|
|
305
|
+
echo ""
|
|
306
|
+
echo "Generated by \`.agents/scripts/link-skills.sh\` — never edit by hand, and never"
|
|
307
|
+
echo "commit: the entries are symlinks into checkouts that only exist locally."
|
|
308
|
+
echo ""
|
|
309
|
+
echo "These skills belong to the repositories this one depends on. They are readable at"
|
|
310
|
+
echo "\`.agents/linked-skills/<name>/SKILL.md\` and are loaded by Claude Code through"
|
|
311
|
+
echo "\`.claude/skills/\` like any local skill. A local skill of the same name always wins,"
|
|
312
|
+
echo "and a nearer dependency wins over a farther one."
|
|
313
|
+
echo ""
|
|
314
|
+
echo "Dependency order: $chain"
|
|
315
|
+
echo ""
|
|
316
|
+
echo "| skill | origin repo | description |"
|
|
317
|
+
echo "|---|---|---|"
|
|
318
|
+
LC_ALL=C sort -t"$TAB" -k1,1 "$CAND" | while IFS="$TAB" read -r name origin path; do
|
|
319
|
+
[ -n "$name" ] || continue
|
|
320
|
+
printf '| %s | %s | %s |\n' "$name" "$origin" "$(describe "$path")"
|
|
321
|
+
done
|
|
322
|
+
} > "$TMPD/INDEX.md" 2>/dev/null
|
|
323
|
+
|
|
324
|
+
if [ -f "$TMPD/INDEX.md" ]; then
|
|
325
|
+
if cmp -s "$TMPD/INDEX.md" "$LNK/INDEX.md" 2>/dev/null; then
|
|
326
|
+
:
|
|
327
|
+
else
|
|
328
|
+
cp "$TMPD/INDEX.md" "$LNK/INDEX.md" 2>/dev/null || true
|
|
329
|
+
fi
|
|
330
|
+
fi
|
|
331
|
+
fi
|
|
332
|
+
|
|
62
333
|
# ---- prune pass ------------------------------------------------------------
|
|
63
334
|
if [ -d "$DST" ]; then
|
|
64
335
|
for entry in "$DST"/* "$DST"/.[!.]*; do
|
|
@@ -67,16 +338,40 @@ if [ -d "$DST" ]; then
|
|
|
67
338
|
[ "$name" = ".gitkeep" ] && continue
|
|
68
339
|
[ -L "$entry" ] || continue
|
|
69
340
|
|
|
70
|
-
if [
|
|
71
|
-
|
|
341
|
+
if [ -f "$SRC/$name/SKILL.md" ]; then
|
|
342
|
+
continue
|
|
343
|
+
fi
|
|
344
|
+
if cand_has "$name" && [ -f "$entry/SKILL.md" ]; then
|
|
345
|
+
continue
|
|
72
346
|
fi
|
|
347
|
+
rm -f "$entry" 2>/dev/null && pruned=$((pruned + 1))
|
|
73
348
|
done
|
|
74
349
|
fi
|
|
75
350
|
|
|
76
|
-
if [ "$
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
351
|
+
if [ -d "$LNK" ]; then
|
|
352
|
+
for entry in "$LNK"/*; do
|
|
353
|
+
[ -e "$entry" ] || [ -L "$entry" ] || continue
|
|
354
|
+
name=$(basename "$entry")
|
|
355
|
+
[ "$name" = "INDEX.md" ] && continue
|
|
356
|
+
[ -L "$entry" ] || continue
|
|
357
|
+
|
|
358
|
+
if cand_has "$name" && [ -f "$entry/SKILL.md" ]; then
|
|
359
|
+
continue
|
|
360
|
+
fi
|
|
361
|
+
rm -f "$entry" 2>/dev/null && pruned=$((pruned + 1))
|
|
362
|
+
done
|
|
363
|
+
# Nothing linked at all — leave no empty generated directory behind.
|
|
364
|
+
if [ -z "${CAND:-}" ] || [ ! -s "$CAND" ]; then
|
|
365
|
+
rm -f "$LNK/INDEX.md" 2>/dev/null || true
|
|
366
|
+
rmdir "$LNK" 2>/dev/null || true
|
|
367
|
+
fi
|
|
80
368
|
fi
|
|
81
369
|
|
|
370
|
+
rm -rf "$TMPD" 2>/dev/null || true
|
|
371
|
+
|
|
372
|
+
summary="link-skills: $linked linked, $pruned pruned"
|
|
373
|
+
[ "$dep_linked" -gt 0 ] && summary="$summary, $dep_linked from dependencies"
|
|
374
|
+
[ "$skipped" -gt 0 ] && summary="$summary, $skipped skipped"
|
|
375
|
+
echo "$summary"
|
|
376
|
+
|
|
82
377
|
exit 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-memory
|
|
3
|
-
description: Shared agent memory protocol — the .agents/memory/ graph store every agent reads and writes: MEMORY.md index, subsystem nodes, compact-on-write merging, size caps, and the read protocol. Use when reading or writing project memory, when asked to remember something, or to decide where knowledge belongs.
|
|
3
|
+
description: "Shared agent memory protocol — the .agents/memory/ graph store every agent reads and writes: MEMORY.md index, subsystem nodes, compact-on-write merging, size caps, and the read protocol. Use when reading or writing project memory, when asked to remember something, or to decide where knowledge belongs."
|
|
4
4
|
user-invocable: true
|
|
5
5
|
metadata:
|
|
6
6
|
scope: general
|
|
@@ -14,8 +14,8 @@ the `MEMORY.md` index. Compactness is the core value — every write
|
|
|
14
14
|
merges and compacts; nothing is ever appended as a log.
|
|
15
15
|
|
|
16
16
|
Never write memory to `.claude/memory/`, `.github/memory/`, `~/.claude/`, `~/.copilot/`, or
|
|
17
|
-
anywhere outside the repository.
|
|
18
|
-
|
|
17
|
+
anywhere outside the repository. Records found in any such place are folded into
|
|
18
|
+
`.agents/memory/` by `memory-recompact`, never added to.
|
|
19
19
|
|
|
20
20
|
## Store layout
|
|
21
21
|
|
|
@@ -105,8 +105,8 @@ Every write is a merge, never an append:
|
|
|
105
105
|
4. **Compact** — reread the node; collapse redundancy; delete anything the code or git history
|
|
106
106
|
now states; prune resolved `Status` lines.
|
|
107
107
|
5. **Cap** — over soft cap: compact harder. Over hard cap: split by sub-scope into a linked child
|
|
108
|
-
node, or promote procedure-shaped overflow (`memory-promotion` where present, else
|
|
109
|
-
`skill-authoring`).
|
|
108
|
+
node, or promote procedure-shaped overflow (`memory-promotion` where present, else the
|
|
109
|
+
project's `skill-authoring` or `create-skill` skill).
|
|
110
110
|
6. **Index** — update the hook line if the node's center of gravity moved; bump `updated:`.
|
|
111
111
|
|
|
112
112
|
## Knowledge, not events
|
|
@@ -116,10 +116,10 @@ correctly, stated without reference to this session?* Keep invariants, cause→e
|
|
|
116
116
|
counter-moves, recognition fingerprints (symptoms). Drop dates, phase numbers, who did what,
|
|
117
117
|
attempt sequences, and anything recoverable from code or git.
|
|
118
118
|
|
|
119
|
-
Before (event log): "
|
|
120
|
-
|
|
121
|
-
After (knowledge): "
|
|
122
|
-
|
|
119
|
+
Before (event log): "2026-06-11 — CI red on the second run; the migration step was not idempotent,
|
|
120
|
+
patched it, re-ran green."
|
|
121
|
+
After (knowledge): "The migration step is idempotent — run against an already-migrated database
|
|
122
|
+
it writes nothing; a second run that still reports work means the first did not converge."
|
|
123
123
|
|
|
124
124
|
## Size caps (hard)
|
|
125
125
|
|
|
@@ -135,8 +135,9 @@ each repo — fan every fix out to all copies; verify with `diff`. Correct re-ru
|
|
|
135
135
|
## Memory vs skill
|
|
136
136
|
|
|
137
137
|
Fact-shaped ("what is true") stays here. Procedure-shaped ("to do X, do Y") becomes a skill —
|
|
138
|
-
follow `memory-promotion` where present, otherwise `skill-authoring
|
|
139
|
-
*perform* tasks, and over-cap nodes full of
|
|
138
|
+
follow `memory-promotion` where present, otherwise the project's `skill-authoring` or
|
|
139
|
+
`create-skill` skill. Repeated use of a node to *perform* tasks, and over-cap nodes full of
|
|
140
|
+
steps, are promotion triggers.
|
|
140
141
|
|
|
141
142
|
Promotion is a **rewrite, never a move**: never paste node text into a skill. "Record the rule,
|
|
142
143
|
not the story" and the size caps above bind skill bodies at least as tightly as they bind nodes
|