@owlmeans/agent-skills 0.1.10 → 0.1.12
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/agent-meta/instructions/agent-memory.instructions.md +43 -0
- package/agent-meta/instructions/reuse-code.instructions.md +50 -0
- package/agent-meta/instructions/skill-authoring.instructions.md +60 -0
- package/agent-meta/manifest.json +44 -2
- package/agent-meta/skills/agent-memory/SKILL.md +52 -0
- package/agent-meta/skills/reuse-code/SKILL.md +63 -0
- package/agent-meta/skills/scaffolding/SKILL.md +18 -0
- package/agent-meta/skills/skill-authoring/SKILL.md +79 -0
- package/build/discover.d.ts.map +1 -1
- package/build/discover.js +85 -10
- package/build/discover.js.map +1 -1
- package/build/linked.d.ts +9 -0
- package/build/linked.d.ts.map +1 -1
- package/build/linked.js +9 -0
- package/build/linked.js.map +1 -1
- package/package.json +1 -1
- package/src/discover.ts +92 -9
- package/src/linked.ts +9 -0
- package/tests/installer.test.ts +51 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "How project memory works in an OwlMeans app — where to store facts/decisions/gotchas for Copilot (.github/memory/) and Claude Code (.claude/memory/), the MEMORY.md index, and when to read/write memory. Apply when remembering something about the project or deciding where a note belongs."
|
|
3
|
+
applyTo: "**/.github/memory/**, **/.claude/memory/**"
|
|
4
|
+
scope: general
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# Project memory
|
|
9
|
+
|
|
10
|
+
Memory is how durable, project-specific knowledge survives across sessions. Keep it **inside the
|
|
11
|
+
project**, never outside the repository.
|
|
12
|
+
|
|
13
|
+
| Tool | Memory dir | Index |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| GitHub Copilot | `.github/memory/` | `.github/memory/MEMORY.md` |
|
|
16
|
+
| Claude Code | `.claude/memory/` | `.claude/memory/MEMORY.md` |
|
|
17
|
+
|
|
18
|
+
Each memory is one Markdown file holding one fact, decision, or gotcha. The `MEMORY.md` index keeps a
|
|
19
|
+
one-line pointer per file and is loaded every session, so keep it current.
|
|
20
|
+
|
|
21
|
+
## When to read
|
|
22
|
+
|
|
23
|
+
- **Start of every session** — read `MEMORY.md`, then open any relevant file.
|
|
24
|
+
- **Before a non-trivial task** — check for and read a memory file on that topic.
|
|
25
|
+
|
|
26
|
+
## When to write
|
|
27
|
+
|
|
28
|
+
After a task produces knowledge not obvious from the code or git history: a decision and its
|
|
29
|
+
rationale, a gotcha, or project state/goals. Do not record what the repo or git history already says,
|
|
30
|
+
or what only matters to the current conversation.
|
|
31
|
+
|
|
32
|
+
## How to write one
|
|
33
|
+
|
|
34
|
+
1. Create `.github/memory/<topic>.md` (and the Claude copy under `.claude/memory/`) with a short slug
|
|
35
|
+
and a one-line description.
|
|
36
|
+
2. Write the single fact; for decisions/gotchas add why it matters and how to apply it.
|
|
37
|
+
3. Add a one-line pointer to `MEMORY.md`.
|
|
38
|
+
4. Update an existing file instead of duplicating; delete memories that prove wrong.
|
|
39
|
+
|
|
40
|
+
## Memory vs skill
|
|
41
|
+
|
|
42
|
+
A repeatable procedure is a skill (see the `skill-authoring` instruction). Memory is for facts about
|
|
43
|
+
this project.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Discovery-first, reuse-first workflow for OwlMeans projects. Apply before planning or building any feature, before proposing a third-party library or custom solution, and after writing code: find an existing @owlmeans/* package or existing code first, extend before writing new, and simplify what you write."
|
|
3
|
+
applyTo: "**/*.ts, **/*.tsx, **/package.json"
|
|
4
|
+
scope: general
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# Reuse before you build
|
|
9
|
+
|
|
10
|
+
OwlMeans ships a large framework of `@owlmeans/*` packages. Before proposing a third-party library,
|
|
11
|
+
designing a custom solution, or finishing a feature, exhaust what already exists. Apply these four
|
|
12
|
+
steps in order — during planning **and** implementation.
|
|
13
|
+
|
|
14
|
+
## 1. Find an `@owlmeans/*` package first
|
|
15
|
+
|
|
16
|
+
- **Consult the deployed instructions.** Each installed `@owlmeans/*` package ships
|
|
17
|
+
`.github/instructions/<pkg>.instructions.md` (and `.claude/skills/<pkg>/SKILL.md`) describing what it
|
|
18
|
+
does — your local catalogue of installed capabilities.
|
|
19
|
+
- **Scan installed packages** in `node_modules/@owlmeans/*` and, in a workspace, the nested
|
|
20
|
+
`sources/*/node_modules/@owlmeans/*`.
|
|
21
|
+
- **Discover not-yet-installed packages** by researching the **owlmeans/common** repository — its
|
|
22
|
+
`tree.md` dependency map and per-package READMEs.
|
|
23
|
+
|
|
24
|
+
### Local vs. web research (the symlink rule)
|
|
25
|
+
|
|
26
|
+
- `ls -la node_modules/@owlmeans/<pkg>`: a **symlink escaping `node_modules`** — or a project whose own
|
|
27
|
+
workspace publishes `@owlmeans/*` packages (**this is the common repo**) — means the source is local.
|
|
28
|
+
**Research locally** (`tree.md`, the package sources, the canonical instructions); do **not** use the
|
|
29
|
+
internet.
|
|
30
|
+
- Otherwise (clean npm install), **research the web**: https://github.com/owlmeans/common.
|
|
31
|
+
|
|
32
|
+
After adding an `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills` to deploy its instruction.
|
|
33
|
+
Prefer an `@owlmeans/*` package over a third-party library or bespoke code.
|
|
34
|
+
|
|
35
|
+
## 2. Reuse or extend before writing custom
|
|
36
|
+
|
|
37
|
+
If an installed package nearly fits, configure or extend it (its resources, services, modules, helpers)
|
|
38
|
+
rather than writing a parallel implementation.
|
|
39
|
+
|
|
40
|
+
## 3. No package? Reuse code and extract an abstraction
|
|
41
|
+
|
|
42
|
+
Search the codebase for code solving a **similar** problem. Factor out a shared helper or abstraction
|
|
43
|
+
instead of duplicating or starting from scratch. Write new code only when nothing reusable exists.
|
|
44
|
+
|
|
45
|
+
## 4. Simplify after writing
|
|
46
|
+
|
|
47
|
+
Review new code: can it be shorter, clearer, fewer moving parts? Lean on framework utilities, drop dead
|
|
48
|
+
branches. Less code that reuses the framework beats more bespoke code.
|
|
49
|
+
|
|
50
|
+
See the `dependency-tree`, `scaffolding`, and `bun` instructions.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "How to add agent guidance to an OwlMeans project — author a Claude Code skill (.claude/skills/<name>/SKILL.md) and the matching Copilot instruction (.github/instructions/<name>.instructions.md), keep them in sync, and choose skill vs memory. Apply when capturing knowledge as a skill or instruction."
|
|
3
|
+
applyTo: "**/.claude/skills/**, **/.github/instructions/**"
|
|
4
|
+
scope: general
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# Authoring agent guidance (skills + instructions)
|
|
9
|
+
|
|
10
|
+
OwlMeans projects carry agent guidance in two parallel places so both Claude Code and GitHub
|
|
11
|
+
Copilot can use it:
|
|
12
|
+
|
|
13
|
+
| Tool | Location | Shape |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Claude Code | `.claude/skills/<name>/SKILL.md` | one directory per skill, `SKILL.md` is the entrypoint |
|
|
16
|
+
| GitHub Copilot | `.github/instructions/<name>.instructions.md` | one file per topic |
|
|
17
|
+
|
|
18
|
+
Author **both** for any guidance you want available to either tool, using the same `<name>` on both
|
|
19
|
+
sides so they read as one topic.
|
|
20
|
+
|
|
21
|
+
## SKILL.md frontmatter (Claude Code)
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
---
|
|
25
|
+
name: my-skill # the /slash-command (lowercase, hyphens)
|
|
26
|
+
description: What it does and WHEN to use it. # primary auto-invocation signal
|
|
27
|
+
user-invocable: true # false = background knowledge only
|
|
28
|
+
allowed-tools: Bash(bun *) Read # optional
|
|
29
|
+
---
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `description` decides when Claude auto-invokes the skill — describe the topic and the trigger.
|
|
33
|
+
|
|
34
|
+
## Instruction frontmatter (Copilot)
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
---
|
|
38
|
+
description: "Short summary — apply when …"
|
|
39
|
+
applyTo: "**/*.ts, **/relevant/**" # globs that auto-attach this instruction
|
|
40
|
+
---
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`applyTo` controls when Copilot attaches the instruction. Use the patterns the guidance is about;
|
|
44
|
+
fall back to `**/*.ts, **/*.tsx` if broadly relevant.
|
|
45
|
+
|
|
46
|
+
## Keep the pair in sync
|
|
47
|
+
|
|
48
|
+
Change one side, change the other. They need not be identical prose but must not contradict.
|
|
49
|
+
Cross-reference related guidance by name.
|
|
50
|
+
|
|
51
|
+
## Skill vs memory
|
|
52
|
+
|
|
53
|
+
- **Skill / instruction** — a reusable procedure or reference worth auto-invoking.
|
|
54
|
+
- **Memory** (`.claude/memory/`, `.github/memory/`) — a fact, decision, or gotcha specific to this
|
|
55
|
+
project's history. See the `agent-memory` instruction.
|
|
56
|
+
|
|
57
|
+
## After adding a skill
|
|
58
|
+
|
|
59
|
+
List it in `.github/memory/MEMORY.md` (and `.claude/memory/MEMORY.md`), and reference it from
|
|
60
|
+
`.github/copilot-instructions.md` / `CLAUDE.md` if it should be discoverable every session.
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": "@owlmeans/agent-skills",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"generatedAt": "2026-06-
|
|
4
|
+
"version": "0.1.12",
|
|
5
|
+
"generatedAt": "2026-06-17T14:51:24.131Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -19,6 +19,20 @@
|
|
|
19
19
|
"file": "skills/shadcn-web/SKILL.md",
|
|
20
20
|
"canonicalPath": ".claude/skills/shadcn-web/SKILL.md"
|
|
21
21
|
},
|
|
22
|
+
{
|
|
23
|
+
"kind": "skill",
|
|
24
|
+
"name": "agent-memory",
|
|
25
|
+
"category": "general",
|
|
26
|
+
"file": "skills/agent-memory/SKILL.md",
|
|
27
|
+
"canonicalPath": ".claude/skills/agent-memory/SKILL.md"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"kind": "skill",
|
|
31
|
+
"name": "reuse-code",
|
|
32
|
+
"category": "general",
|
|
33
|
+
"file": "skills/reuse-code/SKILL.md",
|
|
34
|
+
"canonicalPath": ".claude/skills/reuse-code/SKILL.md"
|
|
35
|
+
},
|
|
22
36
|
{
|
|
23
37
|
"kind": "skill",
|
|
24
38
|
"name": "scaffolding",
|
|
@@ -26,6 +40,13 @@
|
|
|
26
40
|
"file": "skills/scaffolding/SKILL.md",
|
|
27
41
|
"canonicalPath": ".claude/skills/scaffolding/SKILL.md"
|
|
28
42
|
},
|
|
43
|
+
{
|
|
44
|
+
"kind": "skill",
|
|
45
|
+
"name": "skill-authoring",
|
|
46
|
+
"category": "general",
|
|
47
|
+
"file": "skills/skill-authoring/SKILL.md",
|
|
48
|
+
"canonicalPath": ".claude/skills/skill-authoring/SKILL.md"
|
|
49
|
+
},
|
|
29
50
|
{
|
|
30
51
|
"kind": "skill",
|
|
31
52
|
"name": "shadcn-versions",
|
|
@@ -33,6 +54,13 @@
|
|
|
33
54
|
"file": "skills/shadcn-versions/SKILL.md",
|
|
34
55
|
"canonicalPath": ".claude/skills/shadcn-versions/SKILL.md"
|
|
35
56
|
},
|
|
57
|
+
{
|
|
58
|
+
"kind": "instruction",
|
|
59
|
+
"name": "reuse-code",
|
|
60
|
+
"category": "general",
|
|
61
|
+
"file": "instructions/reuse-code.instructions.md",
|
|
62
|
+
"canonicalPath": ".github/instructions/reuse-code.instructions.md"
|
|
63
|
+
},
|
|
36
64
|
{
|
|
37
65
|
"kind": "instruction",
|
|
38
66
|
"name": "getting-started",
|
|
@@ -47,6 +75,13 @@
|
|
|
47
75
|
"file": "instructions/scaffolding.instructions.md",
|
|
48
76
|
"canonicalPath": ".github/instructions/scaffolding.instructions.md"
|
|
49
77
|
},
|
|
78
|
+
{
|
|
79
|
+
"kind": "instruction",
|
|
80
|
+
"name": "skill-authoring",
|
|
81
|
+
"category": "general",
|
|
82
|
+
"file": "instructions/skill-authoring.instructions.md",
|
|
83
|
+
"canonicalPath": ".github/instructions/skill-authoring.instructions.md"
|
|
84
|
+
},
|
|
50
85
|
{
|
|
51
86
|
"kind": "instruction",
|
|
52
87
|
"name": "shadcn-versions",
|
|
@@ -54,6 +89,13 @@
|
|
|
54
89
|
"file": "instructions/shadcn-versions.instructions.md",
|
|
55
90
|
"canonicalPath": ".github/instructions/shadcn-versions.instructions.md"
|
|
56
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"kind": "instruction",
|
|
94
|
+
"name": "agent-memory",
|
|
95
|
+
"category": "general",
|
|
96
|
+
"file": "instructions/agent-memory.instructions.md",
|
|
97
|
+
"canonicalPath": ".github/instructions/agent-memory.instructions.md"
|
|
98
|
+
},
|
|
57
99
|
{
|
|
58
100
|
"kind": "instruction",
|
|
59
101
|
"name": "shadcn-web",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-memory
|
|
3
|
+
description: How project memory works in an OwlMeans app — where to store facts/decisions/gotchas for Claude Code (.claude/memory/) and Copilot (.github/memory/), the MEMORY.md index, and when to read and write memory. Use when asked to remember something about the project, or to decide where a note belongs.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
scope: general
|
|
6
|
+
---
|
|
7
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
8
|
+
|
|
9
|
+
# Project memory
|
|
10
|
+
|
|
11
|
+
Memory is how durable, project-specific knowledge survives across sessions. Keep it **inside the
|
|
12
|
+
project**, never in `~/.claude/` or `~/.copilot/`.
|
|
13
|
+
|
|
14
|
+
| Tool | Memory dir | Index |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Claude Code | `.claude/memory/` | `.claude/memory/MEMORY.md` |
|
|
17
|
+
| GitHub Copilot | `.github/memory/` | `.github/memory/MEMORY.md` |
|
|
18
|
+
|
|
19
|
+
Each memory is one Markdown file holding one fact, decision, or gotcha. The `MEMORY.md` index has a
|
|
20
|
+
one-line pointer per file (`- [Title](file.md) — hook`) and is loaded every session, so it must stay
|
|
21
|
+
current.
|
|
22
|
+
|
|
23
|
+
## When to read memory
|
|
24
|
+
|
|
25
|
+
- **At the start of every session** — read `MEMORY.md`, then open any file relevant to the task.
|
|
26
|
+
- **Before a non-trivial task** — check for a memory file on that topic and read it first.
|
|
27
|
+
- Don't rely on assumptions when a memory file exists; read it.
|
|
28
|
+
|
|
29
|
+
## When to write memory
|
|
30
|
+
|
|
31
|
+
After a task produces knowledge that isn't obvious from the code or git history:
|
|
32
|
+
|
|
33
|
+
- A **decision** and why it was made (and what was rejected).
|
|
34
|
+
- A **gotcha** or sharp edge discovered the hard way.
|
|
35
|
+
- **Project state / goals** not derivable from the repo.
|
|
36
|
+
|
|
37
|
+
Do **not** record what the code or git history already says, or what only matters to the current
|
|
38
|
+
conversation.
|
|
39
|
+
|
|
40
|
+
## How to write one
|
|
41
|
+
|
|
42
|
+
1. Create `.claude/memory/<topic>.md` (and the Copilot copy under `.github/memory/`) with a short
|
|
43
|
+
slug name and a one-line description.
|
|
44
|
+
2. Write the single fact; for decisions/gotchas, add why it matters and how to apply it.
|
|
45
|
+
3. Add a one-line pointer to the matching `MEMORY.md` index.
|
|
46
|
+
4. Before saving, check for an existing file that already covers it — update that instead of
|
|
47
|
+
duplicating. Delete memories that turn out to be wrong.
|
|
48
|
+
|
|
49
|
+
## Memory vs skill
|
|
50
|
+
|
|
51
|
+
If you are writing a **repeatable procedure** ("to do X, do Y"), that is a skill — see the
|
|
52
|
+
`skill-authoring` skill. Memory is for **facts about this project** ("we chose X because Y").
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reuse-code
|
|
3
|
+
description: Discovery-first, reuse-first workflow for OwlMeans projects. Use BEFORE planning or building any feature, before proposing a third-party library or a custom solution, and after writing code. Find an existing @owlmeans/* package or existing code first; extend before writing new; simplify what you write.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
scope: general
|
|
6
|
+
---
|
|
7
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
8
|
+
|
|
9
|
+
# Reuse before you build
|
|
10
|
+
|
|
11
|
+
OwlMeans ships a large framework of `@owlmeans/*` packages. Before you propose a third-party library,
|
|
12
|
+
design a custom solution, or finish a feature, exhaust what already exists. Apply these four steps in
|
|
13
|
+
order — during planning **and** during implementation.
|
|
14
|
+
|
|
15
|
+
## 1. Find an `@owlmeans/*` package first
|
|
16
|
+
|
|
17
|
+
Before suggesting any external library or writing custom code, look for an `@owlmeans/*` package that
|
|
18
|
+
already solves the problem.
|
|
19
|
+
|
|
20
|
+
- **Consult the deployed skills.** Each installed `@owlmeans/*` package ships a skill at
|
|
21
|
+
`.claude/skills/<pkg>/SKILL.md` (and `.github/instructions/<pkg>.instructions.md`) describing what it
|
|
22
|
+
does. Read those first — they are your local catalogue of installed capabilities.
|
|
23
|
+
- **Scan installed packages.** Look in `node_modules/@owlmeans/*` **and**, in a workspace monorepo,
|
|
24
|
+
the nested `sources/*/node_modules/@owlmeans/*` (bun nests workspace deps).
|
|
25
|
+
- **Discover packages that aren't installed yet** by researching the **owlmeans/common** repository —
|
|
26
|
+
its dependency map (`tree.md`) and the per-package READMEs name every package and its purpose.
|
|
27
|
+
|
|
28
|
+
### Local vs. web research (the symlink rule)
|
|
29
|
+
|
|
30
|
+
How you research the repo depends on whether `@owlmeans/*` is linked locally:
|
|
31
|
+
|
|
32
|
+
- Run `ls -la node_modules/@owlmeans/<pkg>` (try a package you know is installed). If it is a **symlink
|
|
33
|
+
whose target escapes `node_modules`** — or if the project's *own* workspace publishes `@owlmeans/*`
|
|
34
|
+
packages (i.e. **this is the common repo**) — you have the source locally. **Research locally:** read
|
|
35
|
+
`tree.md`, browse the framework's package sources, and read the canonical `.claude/skills/`. **Do
|
|
36
|
+
not** use the internet.
|
|
37
|
+
- Otherwise (a clean install from npm), **research the web**: fetch/search
|
|
38
|
+
**https://github.com/owlmeans/common** — `tree.md` and package READMEs — to find the right package.
|
|
39
|
+
|
|
40
|
+
This is the same dev-linked detection `@owlmeans/agent-skills` uses (see its `detectLinked`). After
|
|
41
|
+
adding an `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills` to deploy its skill. Prefer an
|
|
42
|
+
`@owlmeans/*` package over a third-party library or bespoke code whenever one fits.
|
|
43
|
+
|
|
44
|
+
## 2. Reuse or extend before writing custom
|
|
45
|
+
|
|
46
|
+
If an installed package nearly fits, **configure or extend it** rather than writing something new — use
|
|
47
|
+
its resources, services, modules, and helpers. A small extension of a framework package beats a new
|
|
48
|
+
parallel implementation.
|
|
49
|
+
|
|
50
|
+
## 3. No package? Reuse code and extract an abstraction
|
|
51
|
+
|
|
52
|
+
When no package solves it, search the codebase for code that already solves a **similar** problem.
|
|
53
|
+
Prefer factoring out a shared helper, base, or generic function — extract an abstraction — over
|
|
54
|
+
duplicating logic or writing from scratch. Only write genuinely new code when nothing reusable exists.
|
|
55
|
+
|
|
56
|
+
## 4. Simplify after writing
|
|
57
|
+
|
|
58
|
+
Once code is written, review it: can it be **shorter, clearer, or expressed with fewer moving parts**?
|
|
59
|
+
Lean on framework utilities, remove dead branches, collapse needless indirection. Less code that reuses
|
|
60
|
+
the framework is better than more bespoke code.
|
|
61
|
+
|
|
62
|
+
See `[[dependency-tree]]` for the package map, `[[scaffolding]]` for how a project is assembled, and
|
|
63
|
+
`[[bun]]` for adding dependencies.
|
|
@@ -50,6 +50,11 @@ primitives + layout/nav/screens). Finish with `npx @owlmeans/agent-skills` to ad
|
|
|
50
50
|
```
|
|
51
51
|
my-app/
|
|
52
52
|
├── package.json # bun workspaces: sources/*
|
|
53
|
+
├── CLAUDE.md # agent context: memory + skill directives + project-purpose placeholder
|
|
54
|
+
├── .github/
|
|
55
|
+
│ ├── copilot-instructions.md # same directives for Copilot
|
|
56
|
+
│ └── memory/MEMORY.md # starter memory index
|
|
57
|
+
├── .claude/memory/MEMORY.md # starter memory index
|
|
53
58
|
├── sources/common/ # consts, types, schemas, config, modules (entrypoints)
|
|
54
59
|
├── sources/api/ # context.ts (appendStaticResource), app/session/*, modules.ts, index.ts
|
|
55
60
|
└── sources/web/ # vite + tailwind v4, components/ui/*, layout, nav, screens, render.tsx
|
|
@@ -58,3 +63,16 @@ my-app/
|
|
|
58
63
|
The session demo stores items in `@owlmeans/static-resource`, namespaced by a client-generated
|
|
59
64
|
`sid` kept in `localStorage` — no database, no authentication. See [[static-resource]],
|
|
60
65
|
[[server-app]], [[web-panel]].
|
|
66
|
+
|
|
67
|
+
## Generated agent guidance
|
|
68
|
+
|
|
69
|
+
`CLAUDE.md` and `.github/copilot-instructions.md` carry the same memory and skill directives as a
|
|
70
|
+
real OwlMeans project, plus a **project-purpose placeholder** (`<!-- OWLMEANS:PROJECT-PURPOSE -->`).
|
|
71
|
+
On the first agent session that block instructs the agent to ask the user what the project is for and
|
|
72
|
+
replace it in both files. The deployed skills include the general management skills
|
|
73
|
+
[[skill-authoring]], [[agent-memory]] and the mandatory [[reuse-code]] (find an existing `@owlmeans/*`
|
|
74
|
+
package or code before proposing third-party libraries or custom solutions) so the project can grow its own guidance.
|
|
75
|
+
`CLAUDE.md` and `.github/copilot-instructions.md` reference [[reuse-code]] as mandatory. After adding any
|
|
76
|
+
`@owlmeans/*` dependency, re-run `npx @owlmeans/agent-skills` — discovery scans **every**
|
|
77
|
+
`node_modules/@owlmeans` in the workspace (root and nested under `sources/*`), so package-specific
|
|
78
|
+
skills are picked up even though bun nests them.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-authoring
|
|
3
|
+
description: How to add agent guidance to an OwlMeans project — author a Claude Code skill (.claude/skills/<name>/SKILL.md) and its matching GitHub Copilot instruction (.github/instructions/<name>.instructions.md), keep the two in sync, choose frontmatter, and decide skill vs memory. Use when asked to capture knowledge as a skill, add a slash command, or document a repeatable procedure.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
scope: general
|
|
6
|
+
---
|
|
7
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
8
|
+
|
|
9
|
+
# Authoring agent guidance (skills + instructions)
|
|
10
|
+
|
|
11
|
+
OwlMeans projects carry agent guidance in two parallel places so both Claude Code and GitHub
|
|
12
|
+
Copilot can use it:
|
|
13
|
+
|
|
14
|
+
| Tool | Location | Shape |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Claude Code | `.claude/skills/<name>/SKILL.md` | one directory per skill, `SKILL.md` is the entrypoint |
|
|
17
|
+
| GitHub Copilot | `.github/instructions/<name>.instructions.md` | one file per topic |
|
|
18
|
+
|
|
19
|
+
Author **both** for any guidance you want available to either tool. Keep the same `<name>` on both
|
|
20
|
+
sides so they read as one topic.
|
|
21
|
+
|
|
22
|
+
## SKILL.md (Claude Code)
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
.claude/skills/<name>/
|
|
26
|
+
├── SKILL.md # required — frontmatter + body
|
|
27
|
+
├── reference.md # optional — deeper detail loaded on demand
|
|
28
|
+
└── scripts/ # optional — shell scripts the skill runs
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Frontmatter:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
---
|
|
35
|
+
name: my-skill # the /slash-command (lowercase, hyphens)
|
|
36
|
+
description: What it does and WHEN to use it. # primary auto-invocation signal
|
|
37
|
+
user-invocable: true # false = background knowledge only, hidden from the / menu
|
|
38
|
+
allowed-tools: Bash(bun *) Read # optional — tools usable without per-call approval
|
|
39
|
+
---
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The `description` is the most important field: Claude uses it to decide when to auto-invoke the
|
|
43
|
+
skill, so describe both the topic and the trigger ("Use when …").
|
|
44
|
+
|
|
45
|
+
## <name>.instructions.md (Copilot)
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
---
|
|
49
|
+
description: "Short summary — apply when …"
|
|
50
|
+
applyTo: "**/*.ts, **/relevant/**" # globs that auto-attach this instruction
|
|
51
|
+
---
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`applyTo` controls when Copilot pulls the instruction in. Use the file patterns the guidance is
|
|
55
|
+
actually about; fall back to `**/*.ts, **/*.tsx` if it is broadly relevant.
|
|
56
|
+
|
|
57
|
+
## Keep the pair in sync
|
|
58
|
+
|
|
59
|
+
When you change one side, change the other. They do not have to be identical prose, but they must
|
|
60
|
+
not contradict each other. Cross-reference related guidance by name (e.g. "see the `getting-started`
|
|
61
|
+
skill").
|
|
62
|
+
|
|
63
|
+
## Skill vs memory
|
|
64
|
+
|
|
65
|
+
- **Skill / instruction** — a reusable procedure or reference you (or the tools) will want again,
|
|
66
|
+
worth auto-invoking. Lives in `.claude/skills/` + `.github/instructions/`.
|
|
67
|
+
- **Memory** — a fact, decision, or gotcha specific to this project's history/state. Lives in
|
|
68
|
+
`.claude/memory/` + `.github/memory/`. See the `agent-memory` skill.
|
|
69
|
+
|
|
70
|
+
If you find yourself writing "last time we…", that is memory. If you are writing "to do X, do Y",
|
|
71
|
+
that is a skill.
|
|
72
|
+
|
|
73
|
+
## After adding a skill
|
|
74
|
+
|
|
75
|
+
1. If it replaces an ad-hoc `.claude/<topic>.md`, remove that file.
|
|
76
|
+
2. Mention it in `.claude/memory/MEMORY.md` (and `.github/memory/MEMORY.md`) so future sessions know
|
|
77
|
+
it exists.
|
|
78
|
+
3. Reference it from `CLAUDE.md` / `.github/copilot-instructions.md` if it should be discoverable
|
|
79
|
+
every session.
|
package/build/discover.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,GAAG,aAAa,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAA;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,aAAa,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,aAAa,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAA;IAC1D,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAA;CACjB;
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,GAAG,aAAa,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAA;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,aAAa,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,aAAa,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAA;IAC1D,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAA;CACjB;AA+KD,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,2BAA2B;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iCAAiC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,mEAAmE;AACnE,eAAO,MAAM,QAAQ,GAAI,WAAW,MAAM,EAAE,OAAM,eAAoB,KAAG,eAAe,EA4CvF,CAAA"}
|
package/build/discover.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
/** Parse a manifest.json, return null if invalid/unparseable. */
|
|
@@ -14,27 +14,85 @@ const parseManifest = (manifestPath) => {
|
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
const OWLMEANS_SCOPE = '@owlmeans';
|
|
18
|
+
/**
|
|
19
|
+
* Collect every existing `<...>/node_modules/@owlmeans` directory in the project
|
|
20
|
+
* tree. A scaffolded app is a bun workspace whose `@owlmeans/*` deps belong to its
|
|
21
|
+
* `sources/*` packages (the root has none), so bun may keep them under
|
|
22
|
+
* `sources/<pkg>/node_modules/@owlmeans` rather than at the root. Scanning only the
|
|
23
|
+
* root would miss them — so we walk the whole tree.
|
|
24
|
+
*
|
|
25
|
+
* The walk is bounded: it never descends into a `node_modules` directory (it just
|
|
26
|
+
* records its `@owlmeans` scope), skips hidden dirs (`.git`, `.github`, …), and
|
|
27
|
+
* tracks visited realpaths to avoid symlink cycles.
|
|
28
|
+
*/
|
|
29
|
+
const collectScopeDirs = (targetDir) => {
|
|
30
|
+
const found = [];
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const walk = (dir) => {
|
|
33
|
+
let real;
|
|
34
|
+
try {
|
|
35
|
+
real = realpathSync(dir);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (seen.has(real))
|
|
41
|
+
return;
|
|
42
|
+
seen.add(real);
|
|
43
|
+
let dirents;
|
|
44
|
+
try {
|
|
45
|
+
dirents = readdirSync(dir, { withFileTypes: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const dirent of dirents) {
|
|
51
|
+
const name = dirent.name;
|
|
52
|
+
if (name === 'node_modules') {
|
|
53
|
+
const scope = join(dir, 'node_modules', OWLMEANS_SCOPE);
|
|
54
|
+
if (existsSync(scope))
|
|
55
|
+
found.push(scope);
|
|
56
|
+
// never descend into node_modules
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (name.startsWith('.'))
|
|
60
|
+
continue;
|
|
61
|
+
if (!dirent.isDirectory())
|
|
62
|
+
continue;
|
|
63
|
+
walk(join(dir, name));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
walk(targetDir);
|
|
67
|
+
return found;
|
|
68
|
+
};
|
|
69
|
+
/** Read all @owlmeans packages under a single `.../node_modules/@owlmeans` dir. */
|
|
70
|
+
const scanScopeDir = (scopeDir) => {
|
|
23
71
|
let pkgDirs;
|
|
24
72
|
try {
|
|
25
|
-
pkgDirs = readdirSync(
|
|
73
|
+
pkgDirs = readdirSync(scopeDir);
|
|
26
74
|
}
|
|
27
75
|
catch {
|
|
28
76
|
return [];
|
|
29
77
|
}
|
|
78
|
+
const packages = [];
|
|
30
79
|
for (const pkgDir of pkgDirs) {
|
|
31
|
-
const
|
|
80
|
+
const pkgPath = join(scopeDir, pkgDir);
|
|
81
|
+
const agentMetaDir = join(pkgPath, 'agent-meta');
|
|
82
|
+
const manifestPath = join(agentMetaDir, 'manifest.json');
|
|
32
83
|
if (!existsSync(manifestPath))
|
|
33
84
|
continue;
|
|
34
85
|
const manifest = parseManifest(manifestPath);
|
|
35
86
|
if (manifest == null)
|
|
36
87
|
continue;
|
|
37
|
-
|
|
88
|
+
let realDir;
|
|
89
|
+
try {
|
|
90
|
+
realDir = realpathSync(pkgPath);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
realDir = pkgPath;
|
|
94
|
+
}
|
|
95
|
+
const entries = [];
|
|
38
96
|
for (const e of manifest.entries) {
|
|
39
97
|
const sourcePath = join(agentMetaDir, e.file);
|
|
40
98
|
if (!existsSync(sourcePath))
|
|
@@ -50,6 +108,23 @@ const scanNodeModules = (targetDir) => {
|
|
|
50
108
|
isExtra: false,
|
|
51
109
|
});
|
|
52
110
|
}
|
|
111
|
+
if (entries.length > 0)
|
|
112
|
+
packages.push({ realDir, entries });
|
|
113
|
+
}
|
|
114
|
+
return packages;
|
|
115
|
+
};
|
|
116
|
+
// Scan every node_modules/@owlmeans/*/agent-meta/ in the project tree for manifests.
|
|
117
|
+
const scanNodeModules = (targetDir) => {
|
|
118
|
+
const entries = [];
|
|
119
|
+
const seenPkgDirs = new Set();
|
|
120
|
+
for (const scopeDir of collectScopeDirs(targetDir)) {
|
|
121
|
+
for (const pkg of scanScopeDir(scopeDir)) {
|
|
122
|
+
// Skip a package already read via another (symlinked) location.
|
|
123
|
+
if (seenPkgDirs.has(pkg.realDir))
|
|
124
|
+
continue;
|
|
125
|
+
seenPkgDirs.add(pkg.realDir);
|
|
126
|
+
entries.push(...pkg.entries);
|
|
127
|
+
}
|
|
53
128
|
}
|
|
54
129
|
return entries;
|
|
55
130
|
};
|
package/build/discover.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"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"}
|
package/build/linked.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ export interface LinkedResult {
|
|
|
10
10
|
*
|
|
11
11
|
* A package is considered linked when its node_modules/@owlmeans/<pkg>
|
|
12
12
|
* entry is a symlink whose real path escapes the node_modules tree.
|
|
13
|
+
*
|
|
14
|
+
* This intentionally scans only the ROOT node_modules, even though discovery
|
|
15
|
+
* (see discover.ts) now walks the whole tree. Dev-linked monorepos (e.g. a project
|
|
16
|
+
* that symlinks the common source under libraries/*) hoist @owlmeans/* to the root
|
|
17
|
+
* node_modules as escaping symlinks, so the root scan catches them. A standalone
|
|
18
|
+
* scaffolded app has no root @owlmeans scope at all (its deps nest under
|
|
19
|
+
* sources/*\/node_modules), so this correctly returns { linked: false } and the
|
|
20
|
+
* install proceeds. Scanning nested node_modules here would risk false positives on
|
|
21
|
+
* bun's in-tree store symlinks, so we deliberately do not.
|
|
13
22
|
*/
|
|
14
23
|
export declare const detectLinked: (targetDir: string) => LinkedResult;
|
|
15
24
|
//# sourceMappingURL=linked.d.ts.map
|
package/build/linked.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linked.d.ts","sourceRoot":"","sources":["../src/linked.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAA;IACf,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED
|
|
1
|
+
{"version":3,"file":"linked.d.ts","sourceRoot":"","sources":["../src/linked.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAA;IACf,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,KAAG,YAiChD,CAAA"}
|
package/build/linked.js
CHANGED
|
@@ -7,6 +7,15 @@ import { join, resolve } from 'node:path';
|
|
|
7
7
|
*
|
|
8
8
|
* A package is considered linked when its node_modules/@owlmeans/<pkg>
|
|
9
9
|
* entry is a symlink whose real path escapes the node_modules tree.
|
|
10
|
+
*
|
|
11
|
+
* This intentionally scans only the ROOT node_modules, even though discovery
|
|
12
|
+
* (see discover.ts) now walks the whole tree. Dev-linked monorepos (e.g. a project
|
|
13
|
+
* that symlinks the common source under libraries/*) hoist @owlmeans/* to the root
|
|
14
|
+
* node_modules as escaping symlinks, so the root scan catches them. A standalone
|
|
15
|
+
* scaffolded app has no root @owlmeans scope at all (its deps nest under
|
|
16
|
+
* sources/*\/node_modules), so this correctly returns { linked: false } and the
|
|
17
|
+
* install proceeds. Scanning nested node_modules here would risk false positives on
|
|
18
|
+
* bun's in-tree store symlinks, so we deliberately do not.
|
|
10
19
|
*/
|
|
11
20
|
export const detectLinked = (targetDir) => {
|
|
12
21
|
const nmScope = join(targetDir, 'node_modules', '@owlmeans');
|
package/build/linked.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linked.js","sourceRoot":"","sources":["../src/linked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC1E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQzC
|
|
1
|
+
{"version":3,"file":"linked.js","sourceRoot":"","sources":["../src/linked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC1E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAgB,EAAE;IAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,EAAE,WAAW,CAAC,CAAA;IAC5D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;IAEhE,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;IACxC,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAA;IAEtD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAClC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;YAC/B,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC,CAAA;gBACjC,SAAQ;YACV,CAAC;YACD,qEAAqE;YACrE,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;YAClC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;gBACpE,QAAQ,CAAC,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAA;AAClD,CAAC,CAAA"}
|
package/package.json
CHANGED
package/src/discover.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
@@ -45,26 +45,91 @@ const parseManifest = (manifestPath: string): Manifest | null => {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
const scanNodeModules = (targetDir: string): DiscoveredEntry[] => {
|
|
50
|
-
const nmDir = join(targetDir, 'node_modules', '@owlmeans')
|
|
51
|
-
if (!existsSync(nmDir)) return []
|
|
48
|
+
const OWLMEANS_SCOPE = '@owlmeans'
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Collect every existing `<...>/node_modules/@owlmeans` directory in the project
|
|
52
|
+
* tree. A scaffolded app is a bun workspace whose `@owlmeans/*` deps belong to its
|
|
53
|
+
* `sources/*` packages (the root has none), so bun may keep them under
|
|
54
|
+
* `sources/<pkg>/node_modules/@owlmeans` rather than at the root. Scanning only the
|
|
55
|
+
* root would miss them — so we walk the whole tree.
|
|
56
|
+
*
|
|
57
|
+
* The walk is bounded: it never descends into a `node_modules` directory (it just
|
|
58
|
+
* records its `@owlmeans` scope), skips hidden dirs (`.git`, `.github`, …), and
|
|
59
|
+
* tracks visited realpaths to avoid symlink cycles.
|
|
60
|
+
*/
|
|
61
|
+
const collectScopeDirs = (targetDir: string): string[] => {
|
|
62
|
+
const found: string[] = []
|
|
63
|
+
const seen = new Set<string>()
|
|
64
|
+
|
|
65
|
+
const walk = (dir: string): void => {
|
|
66
|
+
let real: string
|
|
67
|
+
try {
|
|
68
|
+
real = realpathSync(dir)
|
|
69
|
+
} catch {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(real)) return
|
|
73
|
+
seen.add(real)
|
|
74
|
+
|
|
75
|
+
let dirents: import('node:fs').Dirent[]
|
|
76
|
+
try {
|
|
77
|
+
dirents = readdirSync(dir, { withFileTypes: true })
|
|
78
|
+
} catch {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const dirent of dirents) {
|
|
83
|
+
const name = dirent.name
|
|
84
|
+
if (name === 'node_modules') {
|
|
85
|
+
const scope = join(dir, 'node_modules', OWLMEANS_SCOPE)
|
|
86
|
+
if (existsSync(scope)) found.push(scope)
|
|
87
|
+
// never descend into node_modules
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
if (name.startsWith('.')) continue
|
|
91
|
+
if (!dirent.isDirectory()) continue
|
|
92
|
+
walk(join(dir, name))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
walk(targetDir)
|
|
97
|
+
return found
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ScopePackage {
|
|
101
|
+
/** Realpath of the package dir, used to dedup the same physical package read via
|
|
102
|
+
* multiple (symlinked) locations. */
|
|
103
|
+
realDir: string
|
|
104
|
+
entries: DiscoveredEntry[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Read all @owlmeans packages under a single `.../node_modules/@owlmeans` dir. */
|
|
108
|
+
const scanScopeDir = (scopeDir: string): ScopePackage[] => {
|
|
54
109
|
let pkgDirs: string[]
|
|
55
110
|
try {
|
|
56
|
-
pkgDirs = readdirSync(
|
|
111
|
+
pkgDirs = readdirSync(scopeDir)
|
|
57
112
|
} catch {
|
|
58
113
|
return []
|
|
59
114
|
}
|
|
60
115
|
|
|
116
|
+
const packages: ScopePackage[] = []
|
|
61
117
|
for (const pkgDir of pkgDirs) {
|
|
62
|
-
const
|
|
118
|
+
const pkgPath = join(scopeDir, pkgDir)
|
|
119
|
+
const agentMetaDir = join(pkgPath, 'agent-meta')
|
|
120
|
+
const manifestPath = join(agentMetaDir, 'manifest.json')
|
|
63
121
|
if (!existsSync(manifestPath)) continue
|
|
64
122
|
const manifest = parseManifest(manifestPath)
|
|
65
123
|
if (manifest == null) continue
|
|
66
124
|
|
|
67
|
-
|
|
125
|
+
let realDir: string
|
|
126
|
+
try {
|
|
127
|
+
realDir = realpathSync(pkgPath)
|
|
128
|
+
} catch {
|
|
129
|
+
realDir = pkgPath
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const entries: DiscoveredEntry[] = []
|
|
68
133
|
for (const e of manifest.entries) {
|
|
69
134
|
const sourcePath = join(agentMetaDir, e.file)
|
|
70
135
|
if (!existsSync(sourcePath)) continue
|
|
@@ -79,6 +144,24 @@ const scanNodeModules = (targetDir: string): DiscoveredEntry[] => {
|
|
|
79
144
|
isExtra: false,
|
|
80
145
|
})
|
|
81
146
|
}
|
|
147
|
+
if (entries.length > 0) packages.push({ realDir, entries })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return packages
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Scan every node_modules/@owlmeans/*/agent-meta/ in the project tree for manifests.
|
|
154
|
+
const scanNodeModules = (targetDir: string): DiscoveredEntry[] => {
|
|
155
|
+
const entries: DiscoveredEntry[] = []
|
|
156
|
+
const seenPkgDirs = new Set<string>()
|
|
157
|
+
|
|
158
|
+
for (const scopeDir of collectScopeDirs(targetDir)) {
|
|
159
|
+
for (const pkg of scanScopeDir(scopeDir)) {
|
|
160
|
+
// Skip a package already read via another (symlinked) location.
|
|
161
|
+
if (seenPkgDirs.has(pkg.realDir)) continue
|
|
162
|
+
seenPkgDirs.add(pkg.realDir)
|
|
163
|
+
entries.push(...pkg.entries)
|
|
164
|
+
}
|
|
82
165
|
}
|
|
83
166
|
|
|
84
167
|
return entries
|
package/src/linked.ts
CHANGED
|
@@ -14,6 +14,15 @@ export interface LinkedResult {
|
|
|
14
14
|
*
|
|
15
15
|
* A package is considered linked when its node_modules/@owlmeans/<pkg>
|
|
16
16
|
* entry is a symlink whose real path escapes the node_modules tree.
|
|
17
|
+
*
|
|
18
|
+
* This intentionally scans only the ROOT node_modules, even though discovery
|
|
19
|
+
* (see discover.ts) now walks the whole tree. Dev-linked monorepos (e.g. a project
|
|
20
|
+
* that symlinks the common source under libraries/*) hoist @owlmeans/* to the root
|
|
21
|
+
* node_modules as escaping symlinks, so the root scan catches them. A standalone
|
|
22
|
+
* scaffolded app has no root @owlmeans scope at all (its deps nest under
|
|
23
|
+
* sources/*\/node_modules), so this correctly returns { linked: false } and the
|
|
24
|
+
* install proceeds. Scanning nested node_modules here would risk false positives on
|
|
25
|
+
* bun's in-tree store symlinks, so we deliberately do not.
|
|
17
26
|
*/
|
|
18
27
|
export const detectLinked = (targetDir: string): LinkedResult => {
|
|
19
28
|
const nmScope = join(targetDir, 'node_modules', '@owlmeans')
|
package/tests/installer.test.ts
CHANGED
|
@@ -142,6 +142,57 @@ describe('discover()', () => {
|
|
|
142
142
|
const entries = discover(tmpDir, { extras: false, copilotOnly: true })
|
|
143
143
|
expect(entries.every(e => e.kind === 'instruction')).toBe(true)
|
|
144
144
|
})
|
|
145
|
+
|
|
146
|
+
it('discovers entries nested in workspace package node_modules (bun monorepo)', () => {
|
|
147
|
+
// Scaffolded bun workspace: root has no @owlmeans; deps live under sources/*.
|
|
148
|
+
writeFixturePackage(join(tmpDir, 'sources', 'web', 'node_modules'), '@owlmeans/web-panel', '0.1.9', [
|
|
149
|
+
{ kind: 'skill', name: 'web-panel' },
|
|
150
|
+
{ kind: 'instruction', name: 'web-panel' },
|
|
151
|
+
])
|
|
152
|
+
writeFixturePackage(join(tmpDir, 'sources', 'api', 'node_modules'), '@owlmeans/server-app', '0.1.9', [
|
|
153
|
+
{ kind: 'skill', name: 'server-app' },
|
|
154
|
+
])
|
|
155
|
+
const entries = discover(tmpDir, { extras: false })
|
|
156
|
+
expect(entries.some(e => e.kind === 'skill' && e.name === 'web-panel')).toBe(true)
|
|
157
|
+
expect(entries.some(e => e.kind === 'instruction' && e.name === 'web-panel')).toBe(true)
|
|
158
|
+
expect(entries.some(e => e.kind === 'skill' && e.name === 'server-app')).toBe(true)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('combines root and nested node_modules', () => {
|
|
162
|
+
writeFixturePackage(join(tmpDir, 'node_modules'), '@owlmeans/context', '0.1.9', [
|
|
163
|
+
{ kind: 'skill', name: 'context' },
|
|
164
|
+
])
|
|
165
|
+
writeFixturePackage(join(tmpDir, 'sources', 'web', 'node_modules'), '@owlmeans/web-panel', '0.1.9', [
|
|
166
|
+
{ kind: 'skill', name: 'web-panel' },
|
|
167
|
+
])
|
|
168
|
+
const entries = discover(tmpDir, { extras: false })
|
|
169
|
+
expect(entries.some(e => e.name === 'context')).toBe(true)
|
|
170
|
+
expect(entries.some(e => e.name === 'web-panel')).toBe(true)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('does not double-count a package symlinked into multiple workspaces', () => {
|
|
174
|
+
// Physical package under the root store…
|
|
175
|
+
writeFixturePackage(join(tmpDir, 'node_modules'), '@owlmeans/context', '0.1.9', [
|
|
176
|
+
{ kind: 'skill', name: 'context' },
|
|
177
|
+
])
|
|
178
|
+
const realPkg = join(tmpDir, 'node_modules', '@owlmeans', 'context')
|
|
179
|
+
// …symlinked into a workspace's node_modules (as bun's store linker may do).
|
|
180
|
+
const nestedScope = join(tmpDir, 'sources', 'web', 'node_modules', '@owlmeans')
|
|
181
|
+
mkdirSync(nestedScope, { recursive: true })
|
|
182
|
+
symlinkSync(realPkg, join(nestedScope, 'context'))
|
|
183
|
+
|
|
184
|
+
const entries = discover(tmpDir, { extras: false })
|
|
185
|
+
expect(entries.filter(e => e.name === 'context')).toHaveLength(1)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('ignores hidden directories while walking', () => {
|
|
189
|
+
// A stray manifest under a hidden dir must not be picked up.
|
|
190
|
+
writeFixturePackage(join(tmpDir, '.git', 'node_modules'), '@owlmeans/ghost', '0.1.9', [
|
|
191
|
+
{ kind: 'skill', name: 'ghost' },
|
|
192
|
+
])
|
|
193
|
+
const entries = discover(tmpDir, { extras: false })
|
|
194
|
+
expect(entries.some(e => e.name === 'ghost')).toBe(false)
|
|
195
|
+
})
|
|
145
196
|
})
|
|
146
197
|
|
|
147
198
|
// ---------------------------------------------------------------------------
|