@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.
Files changed (77) hide show
  1. package/README.md +38 -1
  2. package/build/args.d.ts +8 -0
  3. package/build/args.d.ts.map +1 -1
  4. package/build/args.js +60 -6
  5. package/build/args.js.map +1 -1
  6. package/build/index.d.ts +4 -1
  7. package/build/index.d.ts.map +1 -1
  8. package/build/index.js +2 -0
  9. package/build/index.js.map +1 -1
  10. package/build/naming.d.ts +11 -0
  11. package/build/naming.d.ts.map +1 -0
  12. package/build/naming.js +18 -0
  13. package/build/naming.js.map +1 -0
  14. package/build/run.d.ts.map +1 -1
  15. package/build/run.js +19 -13
  16. package/build/run.js.map +1 -1
  17. package/build/scaffold.d.ts +21 -0
  18. package/build/scaffold.d.ts.map +1 -0
  19. package/build/scaffold.js +18 -0
  20. package/build/scaffold.js.map +1 -0
  21. package/build/template.d.ts +19 -3
  22. package/build/template.d.ts.map +1 -1
  23. package/build/template.js +52 -12
  24. package/build/template.js.map +1 -1
  25. package/package.json +4 -3
  26. package/template/AGENTS.md +28 -1
  27. package/template/README.bare.md +57 -0
  28. package/template/README.md +5 -61
  29. package/template/_agents/scripts/link-skills.sh +310 -15
  30. package/template/_agents/skills/agent-memory/SKILL.md +12 -11
  31. package/template/_agents/skills/getting-started/SKILL.md +26 -86
  32. package/template/_agents/skills/memory-promotion/SKILL.md +10 -4
  33. package/template/_agents/skills/memory-recompact/SKILL.md +15 -14
  34. package/template/_agents/skills/reuse-code/SKILL.md +36 -8
  35. package/template/_agents/skills/self-education/SKILL.md +19 -5
  36. package/template/_agents/skills/skill-authoring/SKILL.md +53 -11
  37. package/template/_bare.json +23 -0
  38. package/template/_gitignore +3 -0
  39. package/template/bunfig.toml +2 -0
  40. package/template/package.json +3 -2
  41. package/template/sources/api/package.json +3 -3
  42. package/template/sources/api/src/app/session/add.ts +7 -6
  43. package/template/sources/api/src/app/session/list.ts +12 -9
  44. package/template/sources/api/src/app/session/remove.ts +7 -6
  45. package/template/sources/api/src/context.bare.ts +11 -0
  46. package/template/sources/api/src/entrypoints.bare.ts +4 -0
  47. package/template/sources/api/src/entrypoints.ts +13 -0
  48. package/template/sources/api/src/index.ts +2 -2
  49. package/template/sources/api/src/types.bare.ts +5 -0
  50. package/template/sources/common/package.json +6 -6
  51. package/template/sources/common/src/consts.bare.ts +8 -0
  52. package/template/sources/common/src/consts.ts +0 -13
  53. package/template/sources/common/src/entrypoints.bare.ts +14 -0
  54. package/template/sources/common/src/entrypoints.ts +60 -0
  55. package/template/sources/common/src/index.bare.ts +3 -0
  56. package/template/sources/common/src/index.ts +1 -1
  57. package/template/sources/common/src/schemas.ts +7 -7
  58. package/template/sources/web/index.html +3 -1
  59. package/template/sources/web/package.json +13 -11
  60. package/template/sources/web/src/components/ui/navigation-menu.tsx +169 -0
  61. package/template/sources/web/src/context.bare.ts +13 -0
  62. package/template/sources/web/src/context.ts +14 -2
  63. package/template/sources/web/src/entrypoints.bare.ts +13 -0
  64. package/template/sources/web/src/entrypoints.ts +16 -0
  65. package/template/sources/web/src/index.css +7 -0
  66. package/template/sources/web/src/index.tsx +2 -2
  67. package/template/sources/web/src/layout/main.tsx +16 -19
  68. package/template/sources/web/src/nav.bare.ts +21 -0
  69. package/template/sources/web/src/nav.ts +28 -0
  70. package/template/sources/web/src/screens/about.tsx +19 -0
  71. package/template/sources/web/src/screens/home.bare.tsx +17 -0
  72. package/template/sources/web/src/screens/session.tsx +28 -14
  73. package/template/sources/web/src/vite-env.d.ts +1 -0
  74. package/template/sources/api/src/modules.ts +0 -11
  75. package/template/sources/common/src/modules.ts +0 -26
  76. package/template/sources/web/src/components/nav/main.tsx +0 -15
  77. package/template/sources/web/src/modules.ts +0 -20
@@ -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
+ ```
@@ -1,65 +1,9 @@
1
1
  # __APP_NAME__
2
2
 
3
- A minimal fullstack [OwlMeans Common](https://github.com/owlmeans/common) app, scaffolded with
4
- [`@owlmeans/create-app`](https://www.npmjs.com/package/@owlmeans/create-app).
3
+ __APP_DESCRIPTION__
5
4
 
6
- No authentication; session data lives in an **in-memory static resource** on the backend.
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
- ## Workspaces
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 the canonical skill store (.agents/skills/) into .claude/skills/ for
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
- # Each skill becomes its own symlink — Claude Code supports per-skill symlink
9
- # entries, but not a symlinked skills directory.
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
- # - prunes symlinks that dangle or no longer have a SKILL.md behind them
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 [ ! -e "$entry" ] || [ ! -f "$SRC/$name/SKILL.md" ]; then
71
- rm -f "$entry" 2>/dev/null && pruned=$((pruned + 1))
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 [ "$skipped" -gt 0 ]; then
77
- echo "link-skills: $linked linked, $pruned pruned, $skipped skipped"
78
- else
79
- echo "link-skills: $linked linked, $pruned pruned"
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. Legacy `.claude/memory/` / `.github/memory/` stores are
18
- retired if one exists, flag it for `memory-recompact` migration instead of writing there.
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): "Verification 2026-06-11 Bug found: script missing embedded-count logic.
120
- Fix deployed to all copies; second run idempotent."
121
- After (knowledge): "`nested-agent-context.sh` exists as byte-identical copies in the archive and
122
- each repo fan every fix out to all copies; verify with `diff`. Correct re-runs are no-ops."
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`. Repeated use of a node to
139
- *perform* tasks, and over-cap nodes full of steps, are promotion triggers.
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