Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f20e911
feat(backend): add pgvector, skill_usage_events, comments extension, …
latentloop07 Apr 26, 2026
78fe5de
fix(backend): use gen_random_uuid server-side default on skill_usage_…
latentloop07 Apr 26, 2026
b390445
feat(backend): add SkillUsageEvent model + extend Comment + add Skill…
latentloop07 Apr 26, 2026
8876d4a
chore(backend): clean up skill_usage_event.py imports and tighten typ…
latentloop07 Apr 26, 2026
0b0d17c
feat(backend): add embedding service with openai provider and LRU cache
latentloop07 Apr 26, 2026
2b84a36
chore(backend): clean up embedding service per code review
latentloop07 Apr 26, 2026
0271cab
feat(backend): add openclaw schemas + extend comment schemas with age…
latentloop07 Apr 26, 2026
0be6aeb
chore(backend): tighten openclaw schemas per code review
latentloop07 Apr 26, 2026
4471e49
fix(backend): ContextBundleSkill.collections matches ARRAY(Text) storage
latentloop07 Apr 26, 2026
10ae26e
feat(backend): add POST /v1/openclaw/context-bundle with pgvector cos…
latentloop07 Apr 26, 2026
97b80a6
chore(backend): tighten openclaw context-bundle per code review
latentloop07 Apr 26, 2026
1c33d07
feat(backend): generate skill embeddings on create/update + backfill …
latentloop07 Apr 26, 2026
dda66a8
chore(backend): DRY embedding wiring + restore re-embed test
latentloop07 Apr 26, 2026
256af95
feat(backend): add POST/GET /v1/openclaw/usage endpoints
latentloop07 Apr 26, 2026
4c61a27
chore(backend): tighten openclaw usage tests + move inline note
latentloop07 Apr 26, 2026
d70c496
feat(backend): extend comments endpoint for agent reflections
latentloop07 Apr 26, 2026
62e1759
chore(backend): close PATCH-ignores-extra-fields gap + clarify dual-c…
latentloop07 Apr 26, 2026
63dc6da
chore(release): 0.4.0 — openclaw foundation
latentloop07 Apr 26, 2026
2f6b66d
feat(openclaw): author skillnote-awareness meta-skill body
latentloop07 Apr 26, 2026
4b80509
feat(openclaw): author skillnote-resolver subagent skill
latentloop07 Apr 26, 2026
f6392b8
feat(backend): /setup/openclaw bash installer + /v1/openclaw-bundle.zip
latentloop07 Apr 26, 2026
7c73ec3
feat(web): add OpenClaw setup card to Settings page
latentloop07 Apr 26, 2026
bba70ce
docs: OpenClaw integration guide
latentloop07 Apr 26, 2026
d901308
chore(openclaw): drop embedding infra; subagent is the ranker
latentloop07 Apr 27, 2026
f4b562b
fix(compose): mount plugin-openclaw at /openclaw for bundle endpoint
latentloop07 Apr 27, 2026
21c2fa2
docs: add OpenClaw integration section to README
latentloop07 Apr 27, 2026
8bc2f6e
fix(openclaw): three data-integrity bugs found during live E2E testing
latentloop07 Apr 27, 2026
2bacdbd
fix(openclaw): fan-out usage events into skill_call_events for analytics
latentloop07 Apr 27, 2026
2f9d1f2
fix(analytics): fan-out agent comment ratings into skill_ratings
latentloop07 Apr 27, 2026
a02724e
fix(openclaw,comments,analytics): 5 data-integrity bugs + regression …
latentloop07 Apr 27, 2026
6f1a2fa
feat(openclaw): rewrite skillnote skill for ClawhHub distribution v2.0.0
latentloop07 Apr 27, 2026
269adee
feat(openclaw): ClawhHub skill family v2 — skillnote + resolver + doctor
latentloop07 Apr 28, 2026
0b78c24
feat(openclaw): add GET /v1/openclaw-skill for weekly skill self-update
latentloop07 Apr 28, 2026
7645ae5
fix(openclaw,analytics): production hardening — 10 bugs fixed
latentloop07 Apr 28, 2026
1a8671d
fix(api,analytics): add skill id to list response and fix ambiguous c…
latentloop07 Apr 28, 2026
1b5701b
fix(openclaw): correct collection_id type in API reference (string, n…
latentloop07 Apr 28, 2026
f7572db
fix(openclaw): enforce closed set of valid comment_type values
latentloop07 Apr 28, 2026
6943f4b
fix(openclaw): make AGENTS.md graft use explicit exec/curl — fixes pa…
latentloop07 Apr 28, 2026
d144fc2
feat(openclaw): include content_md in context-bundle + improve resolv…
latentloop07 Apr 28, 2026
38f4ccd
feat(openclaw): remove skillnote-resolver, inline skill logic into si…
latentloop07 Apr 28, 2026
678e378
feat(openclaw): sync skills to disk before each task, drop runtime co…
latentloop07 Apr 28, 2026
9e50657
fix(openclaw): pass skills JSON via tempfile to avoid heredoc quoting…
latentloop07 Apr 28, 2026
b6f4240
feat(openclaw): add daily self-update check to sync.sh
latentloop07 Apr 28, 2026
6e9c9e7
feat(openclaw): add rating footer injection, log-watcher analytics, a…
latentloop07 Apr 28, 2026
892c098
feat(openclaw): agent-driven install flow + self-bootstrapping backend
latentloop07 May 2, 2026
4592f91
docs(openclaw): document the AGENTS.md graft anti-pattern + sync.sh fix
latentloop07 May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ target/

# Next.js build output
.next/

# Vendored reference source (not committed)
claude-code-source/
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
All notable changes to SkillNote will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/).

## [0.4.0] - 2026-04-27

### Added
- **SkillNote × OpenClaw foundation** — living skill registry for OpenClaw agents. The OpenClaw resolver subagent runs in the agent harness with full LLM reasoning over the SkillNote catalog and does the relevance ranking itself; SkillNote ships the universe with rich metadata. New endpoints under `/v1/openclaw/`:
- `POST /v1/openclaw/context-bundle` — returns up to `max_skills` skills (sorted by `usage_count_30d` desc then `rating_avg` desc) plus the full collections list and per-skill staleness/rating/recent-comment metadata. The subagent re-ranks via LLM. Optional `collection_filter` narrows the catalog when the agent has a hint.
- `POST /v1/openclaw/usage` — agents log a usage event after acting. Validates known skill IDs; rejects task summaries > 1000 chars (agents must summarize, not dump raw user messages).
- `GET /v1/openclaw/usage` — list events with `?limit`, `?since`, `?skill_id`, `?before` cursor pagination. Used by Settings → OpenClaw card to detect "connected" status.
- **`skill_usage_events` table** — agent_name, task_summary, collection_id, skill_ids (JSONB), resolver_confidence, risk_level, outcome, channel, metadata_json, created_at. Indexed on created_at + collection_id.
- **Comments extension** — `author_type` (human/agent), `comment_type` (agent_observation, agent_issue, agent_patch_suggestion, agent_success_note, agent_deprecation_warning, ...), `rating` (1-5), `linked_usage_id` FK to skill_usage_events. Backwards-compatible — legacy `{author, body}` POSTs still work.
- **OpenClaw plugin bundle** — 2 skills (`skillnote-awareness`, `skillnote-resolver`) plus `config.template.json`, served as a checksummed ZIP from `GET /v1/openclaw-bundle.zip`. Bash installer at `GET /setup/openclaw` writes everything to `~/.openclaw/` after host substitution.
- **Settings → OpenClaw card** — copy-the-curl install, "connected" indicator wired to `GET /v1/openclaw/usage`, link to the integration docs.

### Tests
- `+11` integration tests for `/v1/openclaw/context-bundle` (usage+rating ranking, tie-breaking, collection filter, staleness rules, N+1 sentinel, recent-comment truncation).
- `+13` integration tests for `/v1/openclaw/usage` (POST validation, JSONB containment filter, cursor pagination).
- `+10` integration tests for comments extension (legacy compat, agent fields, linked_usage_id existence, PATCH ignores extra fields).

## [0.3.4] - 2026-04-26

### Fixed
Expand Down
156 changes: 151 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<a href="#marketplace">Marketplace</a> &middot;
<a href="#agent-reviews">Reviews</a> &middot;
<a href="#live-sync">Live Sync</a> &middot;
<a href="#openclaw-integration">OpenClaw</a> &middot;
<a href="#the-web-ui">Web UI</a> &middot;
<a href="#built-on-claude-codes-native-apis">How It Works</a>
</p>
Expand All @@ -58,22 +59,135 @@ Your skills. Your servers. Your rules.

## Quick Start

Spin up the registry locally:

```bash
git clone https://github.com/luna-prompts/skillnote.git
cd skillnote
./install.sh
```

The install script builds and starts all containers, waits for health checks, and prints the Claude Code plugin command when ready.
The install script builds and starts all containers, waits for health checks, and prints the connect command when ready.

Then wire up your AI agent:

Then connect Claude Code:
<details>
<summary><b>Connect Claude Code</b></summary>

#### Recommended — one-liner

```bash
curl -sf http://localhost:8082/setup | bash
curl -sf http://localhost:8082/setup/agent | bash -s -- --agent claude-code
source ~/.zshrc
```

Now run `claude` in any project. SkillNote picks up your skills automatically.
Same `setup/agent` endpoint works for any harness — pass `--agent claude-code` or `--agent openclaw`. Run `claude` in any project; SkillNote picks up your skills automatically and the collection picker appears on first launch.

#### Or — paste this prompt to Claude Code

If you prefer to let Claude Code install itself (works from a fresh install):

```text
I want you to install SkillNote on my machine and wire it into this Claude Code session.
SkillNote is a skill registry I'm running at http://localhost:8082.

Do the full install yourself — don't ask me to run commands.

1. Check if SkillNote is already installed:
- Look for ~/.claude/plugins/skillnote/
- If it exists, skip to step 3.

2. If not installed, run the official installer:
- curl -sf http://localhost:8082/setup | bash

3. Reload the shell so the plugin is picked up:
- source ~/.zshrc (or ~/.bashrc)

4. Confirm it works:
- Run: claude --version
- List the installed plugin: ls ~/.claude/plugins/skillnote/
- Tell me what collection picker options you see when running `claude`.

Don't ask for confirmation between steps. Just run the commands and report results.
```

#### What gets installed

| Path | Role |
| ---- | ---- |
| `~/.claude/plugins/skillnote/` | The plugin code: hooks, slash commands, status line, collection picker |
| `.skillnote.json` (per project) | Pinned active collection — survives across sessions |

</details>

<details>
<summary><b>Connect OpenClaw</b></summary>

OpenClaw is a chat-first runtime. Four install methods, in order of recommendation:

#### Method 1 — Copy prompt (recommended, zero terminal)

The dominant install UX in the OpenClaw ecosystem. Skip all CLI work — paste a one-prompt setup into your agent and it installs everything itself. The Connect page in your SkillNote web UI generates a personalized prompt with your URL pre-baked. To get yours:

```bash
curl -sf http://localhost:8082/setup/agent-prompt?agent=openclaw
```

Or open the web UI's Connect page → OpenClaw tab → "Copy prompt" tab and click copy. Paste the result into a fresh OpenClaw session — the agent verifies the backend is reachable, installs via clawhub, configures the URL, runs the first sync, and reports back.

#### Method 2 — clawhub

For users who already use OpenClaw's plugin manager:

```bash
export SKILLNOTE_BASE_URL="http://localhost:8082"
clawhub install skillnote

# Make the env var persistent:
echo 'export SKILLNOTE_BASE_URL="http://localhost:8082"' >> ~/.zshrc
```

clawhub doesn't accept a host argument, so set `SKILLNOTE_BASE_URL` first — the skill reads it on first load via the layered host resolution (env → file → fail loudly). Auto-handles plugin updates via the daily version check baked into `sync.sh`.

#### Method 3 — curl one-liner

```bash
curl -sf http://localhost:8082/setup/agent | bash -s -- --agent openclaw
```

Same unified installer as Claude Code (just swap the `--agent` flag). Pre-fills config with your URL and kicks off the first sync. Use when `clawhub` isn't available or you want immediate visible "Synced N skills" feedback.

#### Method 4 — manual

```bash
# 1. Download bundle and extract into ~/.openclaw/skills/
mkdir -p ~/.openclaw/skills ~/.openclaw/skillnote
curl -sf http://localhost:8082/v1/openclaw-bundle.zip -o /tmp/skillnote.zip
unzip -qo /tmp/skillnote.zip -d ~/.openclaw/skills/
rm /tmp/skillnote.zip

# 2. Write config with your SkillNote URL
echo '{"host":"http://localhost:8082","user_id":"openclaw-main"}' \
> ~/.openclaw/skillnote/config.json

# 3. Make sync.sh executable
chmod +x ~/.openclaw/skills/skillnote/sync.sh

# 4. Restart OpenClaw to pick up the skill
```

For air-gapped environments or when you want full control over each step.

#### What gets installed

| Path | Role |
| ---- | ---- |
| `~/.openclaw/skills/skillnote/` | The skill itself + `sync.sh` + `log-watcher.py` |
| `~/.openclaw/skills/sn-*/` | Per-skill mirrors synced from your registry every 60s |
| `~/.openclaw/skillnote/config.json` | Your registry URL and agent ID |
| `~/.openclaw/workspace/AGENTS.md` | Persistent `<skillnote v1>` block — keeps the registry active across sessions |

</details>

---

Expand Down Expand Up @@ -162,6 +276,38 @@ Your team's knowledge compounds. What one person corrects once becomes a skill e

---

## OpenClaw Integration

SkillNote ships a native integration for [OpenClaw](https://github.com/openclaw/openclaw), the open-source chat-first AI agent runtime.

Once installed, your OpenClaw agent automatically:

- Consults your SkillNote registry before each task and applies the relevant skills
- Logs every skill it uses so you can see real activity in the web UI
- Leaves one-line observations and ratings on skills it found helpful or stale

No prompts, no collection pickers. The agent picks skills on its own — you're only involved when confidence is low or a skill carries risk.

### Install

See the **Connect OpenClaw** section in [Quick Start](#quick-start) above for all four install methods (clawhub, curl, manual, agent prompt).

The single `skillnote` skill includes:

- **`SKILL.md`** — always-injected instructions teaching OpenClaw when to consult the registry and how to rate skills
- **`sync.sh`** — fetches the catalog every 60s, writes per-skill mirrors to `~/.openclaw/skills/sn-*/`
- **`log-watcher.py`** — background daemon that parses session JSONL to track which skills the agent actually read

No subagent or LLM resolver step — OpenClaw reads the synced `sn-*/SKILL.md` files directly via its native skill system.

### What you see

- **Settings → OpenClaw**: live connection status. Green dot means the agent can reach your registry.
- **Analytics**: usage events appear here as the agent works.
- **Skill pages → Reviews tab**: agent observations (`agent_observation`, `agent_issue`, `agent_success_note`) appear alongside your human reviews.

---

## The Web UI

### Dashboard & Editor
Expand Down Expand Up @@ -251,9 +397,9 @@ SkillNote is built for Claude Code today. Native plugins for other agents are on
| Agent | Status |
| --- | --- |
| **Claude Code** | Supported |
| **OpenClaw** | Supported |
| **Cursor** | Planned |
| **Codex CLI** | Planned |
| **OpenClaw** | Planned |
| **Antigravity** | Planned |
| **OpenHands** | Planned |

Expand Down
119 changes: 119 additions & 0 deletions backend/alembic/versions/0015_openclaw_foundation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""0015 openclaw foundation — pgvector, skill_usage_events, comments extension

Adds:
- CREATE EXTENSION vector (pgvector)
- skills.embedding vector(1536) + HNSW cosine index
- skill_usage_events table for openclaw usage logging
- comments table extended with author_type, comment_type, rating, linked_usage_id

Revision ID: 0015_openclaw_foundation
Revises: 0014_subpath_not_null
Create Date: 2026-04-26
"""

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID, JSONB

revision = "0015_openclaw_foundation"
down_revision = "0014_subpath_not_null"
branch_labels = None
depends_on = None


def upgrade() -> None:
# a. Enable pgvector extension if available.
#
# This historical migration originally CREATEd the extension + the
# `skills.embedding` column unconditionally. Migration 0016 drops both,
# and the project no longer ships pgvector or the openai/voyage SDK.
# On a fresh DB running on the current `postgres:16` image (which
# doesn't bundle pgvector), a hard `CREATE EXTENSION vector` would
# crash 0015's replay. To keep this migration sequence replay-safe on
# fresh installs while preserving the historical record on dev/staging
# databases that already applied it, we now skip the pgvector parts
# when the extension is unavailable. The matching tear-down in 0016 is
# already guarded with `IF EXISTS`, so this remains symmetric.
bind = op.get_bind()
has_pgvector = bind.execute(
sa.text(
"SELECT 1 FROM pg_available_extensions WHERE name = 'vector'"
)
).scalar() is not None

if has_pgvector:
# pgvector import is lazy: backend pyproject no longer ships the
# package by default. Re-add it (`pip install pgvector`) only if
# you intentionally want to replay this migration with the column.
from pgvector.sqlalchemy import Vector

op.execute("CREATE EXTENSION IF NOT EXISTS vector")

# b. Add embedding column to skills
op.add_column('skills', sa.Column('embedding', Vector(1536), nullable=True))
op.execute(
"CREATE INDEX ix_skills_embedding_hnsw "
"ON skills USING hnsw (embedding vector_cosine_ops)"
)

# c. Create skill_usage_events table
op.create_table(
'skill_usage_events',
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column('agent_name', sa.String(255), nullable=False),
sa.Column('task_summary', sa.Text, nullable=False),
sa.Column(
'collection_id',
sa.Text,
sa.ForeignKey('collections.name', ondelete='SET NULL'),
nullable=True,
),
sa.Column('skill_ids', JSONB, nullable=False, server_default=sa.text("'[]'::jsonb")),
sa.Column('resolver_confidence', sa.Float, nullable=True),
sa.Column('risk_level', sa.String(32), nullable=True),
sa.Column('outcome', sa.String(32), nullable=True),
sa.Column('channel', sa.String(64), nullable=True),
sa.Column('metadata_json', JSONB, nullable=True),
sa.Column(
'created_at',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
)
op.create_index('ix_skill_usage_events_created_at', 'skill_usage_events', ['created_at'])
op.create_index('ix_skill_usage_events_collection_id', 'skill_usage_events', ['collection_id'])

# d. Extend comments table
op.add_column('comments', sa.Column('author_type', sa.String(16), nullable=False, server_default='human'))
# Drop server_default after backfill so future inserts must be explicit
op.alter_column('comments', 'author_type', server_default=None)
op.add_column('comments', sa.Column('comment_type', sa.String(64), nullable=True))
op.add_column('comments', sa.Column('rating', sa.Integer, nullable=True))
op.add_column('comments', sa.Column('linked_usage_id', UUID(as_uuid=True), nullable=True))
op.create_foreign_key(
'fk_comments_linked_usage_id',
'comments',
'skill_usage_events',
['linked_usage_id'],
['id'],
ondelete='SET NULL',
)


def downgrade() -> None:
# Exact reverse order
op.drop_constraint('fk_comments_linked_usage_id', 'comments', type_='foreignkey')
op.drop_column('comments', 'linked_usage_id')
op.drop_column('comments', 'rating')
op.drop_column('comments', 'comment_type')
op.drop_column('comments', 'author_type')

op.drop_index('ix_skill_usage_events_collection_id', table_name='skill_usage_events')
op.drop_index('ix_skill_usage_events_created_at', table_name='skill_usage_events')
op.drop_table('skill_usage_events')

op.execute("DROP INDEX IF EXISTS ix_skills_embedding_hnsw")
op.drop_column('skills', 'embedding')

op.execute("DROP EXTENSION IF EXISTS vector")
Loading