@heytherevibin/skillforge 0.2.1 → 0.7.0
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/CHANGELOG.md +32 -0
- package/README.md +44 -53
- package/RELEASING.md +1 -1
- package/SECURITY.md +2 -2
- package/STRATEGY.md +1 -3
- package/bin/cli.js +32 -138
- package/package.json +2 -2
- package/python/app/chunking.py +116 -0
- package/python/app/context_fusion.py +77 -0
- package/python/app/events_cli.py +1 -1
- package/python/app/index_cli.py +89 -0
- package/python/app/main.py +380 -214
- package/python/app/mcp_contract.py +121 -0
- package/python/app/mcp_server.py +80 -28
- package/python/app/project_index.py +600 -0
- package/python/app/redaction.py +128 -0
- package/python/app/route_cli.py +42 -19
- package/python/requirements.txt +0 -4
- package/python/tests/test_chunking.py +34 -0
- package/python/tests/test_context_fusion.py +45 -0
- package/python/tests/test_mcp_contract.py +137 -0
- package/python/tests/test_project_index.py +76 -0
- package/python/tests/test_redaction.py +51 -0
- package/python/app/auth.py +0 -63
- package/python/app/cli.py +0 -78
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- **Breaking:** Removed the optional **HTTP API** (`skillforge start`), **`skillforge chat`** harness, and **`skillforge auth`** (bearer tokens were only used by HTTP). MCP (`skillforge mcp`), **`skillforge route`**, **`skillforge events`**, and **`skillforge index`** are unchanged.
|
|
6
|
+
- **Migration:** The CLI deletes a leftover **`~/.skillforge/auth.json`** on **every** invocation (including **`skillforge --help`**), once the file is gone the message stops.
|
|
7
|
+
- Dropped **FastAPI**, **uvicorn**, and direct **pydantic** / **httpx** dependencies from `python/requirements.txt` (routing still uses libraries that may bundle their own deps).
|
|
8
|
+
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
- **Phase 4 context safety (MCP meta 1.4)**: Default **secret / credential pattern redaction** and optional **home-directory stripping** on injected chunk text, relative **`path`** fields, stored route **`prompt`** snippet, **`reasoning`**, and **`orchestrator_db`** in **`_meta`**. Disable with **`SKILLFORGE_REDACT_CONTEXT=0`**; path scrub with **`SKILLFORGE_REDACT_HOME_IN_PATHS=0`**. New **[`app/redaction.py`](python/app/redaction.py)**; route events include **`context_redaction`** hit counts.
|
|
12
|
+
|
|
13
|
+
## 0.5.0
|
|
14
|
+
|
|
15
|
+
- **Phase 3 context fusion (MCP meta 1.3)**: When **`include_project_rag`** is on and the project index is non-empty, skill + project chunk **pools** are merged with **greedy MMR** under a single **`SKILLFORGE_CONTEXT_BUDGET_CHARS`** (default: route max + project RAG max). Disable with **`SKILLFORGE_CONTEXT_FUSION=0`** to keep append-only behavior.
|
|
16
|
+
- **Telemetry**: route events and **`_meta.fusion`** include MMR trace; each context item may carry **`mmr_rank`**, **`mmr_score`**, **`retrieval_relevance`**, **`max_sim_to_prior`**.
|
|
17
|
+
- New **[`app/context_fusion.py`](python/app/context_fusion.py)**; **`load_project_fusion_pool`** in **`project_index`**.
|
|
18
|
+
|
|
19
|
+
## 0.4.0
|
|
20
|
+
|
|
21
|
+
- **Phase 2 project RAG (MCP 1.2)**: **`skillforge index --project-root=…`** walks the repo (bounded file sizes, ignore dirs), chunks text files, and stores **`project_chunks`** + embeddings in **`<project>/.skillforge/orchestrator.db`** (same DB as sessions/weights).
|
|
22
|
+
- **MCP / CLI / HTTP**: optional **`include_project_rag`** (MCP + **`skillforge route --include-project-rag`**) appends top matching file chunks under **`SKILLFORGE_PROJECT_RAG_MAX_CHARS`**. **`_meta`**: schema **1.2**; **`sources`** may include **`kind: file`**; **`budget.chars_project_chunks`** / **`chars_context_items_total`**.
|
|
23
|
+
|
|
24
|
+
## 0.3.0
|
|
25
|
+
|
|
26
|
+
- **Phase 1 skill RAG (MCP 1.1)**: Default **`SKILLFORGE_CONTEXT_MODE=chunks`** — line-bounded chunks from each picked **`SKILL.md`** body, scored by query similarity, injected up to **`SKILLFORGE_ROUTE_MAX_CHARS`**. Set **`full_body`** for legacy whole-document injection per skill.
|
|
27
|
+
- **`_meta`**: schema **1.1**; **`sources`** lists chunk-level rows with **`line_start` / `line_end`** and **`score`**; **`context_items_count`**.
|
|
28
|
+
- New **[`app/chunking.py`](python/app/chunking.py)**; **`Router.build_context_items`**, **`format_context_items_markdown`**.
|
|
29
|
+
|
|
30
|
+
## 0.2.2
|
|
31
|
+
|
|
32
|
+
- **MCP Phase 0 contract**: **`route_skills`** success responses include versioned **`_meta`** (`schema_version` **1.0**, **`sources`**, **`budget`**, **`candidates_preview`**). Empty prompt returns **`isError`** + **`_meta.error`**. Shared builder in **`app.mcp_contract`**; **`skillforge route --json-meta`** matches.
|
|
33
|
+
- **Docs**: README “MCP response contract” section.
|
|
34
|
+
|
|
3
35
|
## 0.2.1
|
|
4
36
|
|
|
5
37
|
- Same code as **0.2.0**. **npm never allows reusing a version** after it has been published once—even if you **unpublish** it and only **0.1.0** remains visible. The registry still blocks **`0.2.0`**; ship **`0.2.1`** (or higher) instead.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
**Primary interface:** **stdio MCP** (`skillforge mcp`) — add it to Claude Desktop, Cursor, or Claude Code.
|
|
12
12
|
|
|
13
|
-
**
|
|
13
|
+
**Observability:** run **`skillforge events --watch`** in a terminal (top skills, active sessions, and live **route** / **feedback** lines from SQLite).
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -23,11 +23,10 @@
|
|
|
23
23
|
- [Usage](#usage)
|
|
24
24
|
- [Run modes](#run-modes)
|
|
25
25
|
- [Model Context Protocol (MCP)](#model-context-protocol-mcp)
|
|
26
|
-
|
|
26
|
+
- [MCP response contract](#mcp-response-contract)
|
|
27
27
|
- [Skills and packs](#skills-and-packs)
|
|
28
28
|
- [Routing pipeline](#routing-pipeline)
|
|
29
29
|
- [Configuration](#configuration)
|
|
30
|
-
- [HTTP API](#http-api)
|
|
31
30
|
- [Local data and operations](#local-data-and-operations)
|
|
32
31
|
- [Security considerations](#security-considerations)
|
|
33
32
|
- [Contributing and governance](#contributing-and-governance)
|
|
@@ -47,7 +46,7 @@
|
|
|
47
46
|
| **Observability** | **`skillforge events`**: snapshots of **usage** + **active sessions**, **`--watch`** for realtime; **`--verbose`** for route detail. No browser UI. |
|
|
48
47
|
| **Project bootstrap** | MCP tools **`materialize_project`** and **`skillforge_bootstrap`** write `.cursor/rules`, **`docs/SKILLFORGE-PRD.md`**, and a **`CLAUDE.md`** section (map **`/skillforge`** in rules to MCP tools). |
|
|
49
48
|
| **Extensibility** | Custom skills, git-based **packs**, and overrides under a single user config directory. |
|
|
50
|
-
| **Deployment flexibility** | **MCP stdio**
|
|
49
|
+
| **Deployment flexibility** | **MCP stdio** as the default integration; **CLI** helpers (`route`, `events`, `index`). |
|
|
51
50
|
|
|
52
51
|
Bundled content includes **200+** curated skills (coding, security, research, frontend/backend patterns, and more). Exact counts are validated in CI.
|
|
53
52
|
|
|
@@ -58,8 +57,8 @@ Bundled content includes **200+** curated skills (coding, security, research, fr
|
|
|
58
57
|
| Dependency | Version | Notes |
|
|
59
58
|
|------------|---------|--------|
|
|
60
59
|
| **Node.js** | **>= 18** | Required for the CLI bootstrapper. Continuous integration runs on **Node 22**. |
|
|
61
|
-
| **Python** | **>= 3.10** | Used on the host PATH for embeddings and the
|
|
62
|
-
| **Anthropic API** | — | **`ANTHROPIC_API_KEY`**
|
|
60
|
+
| **Python** | **>= 3.10** | Used on the host PATH for embeddings and routing (via the CLI-spawned **venv**). |
|
|
61
|
+
| **Anthropic API** | — | **`ANTHROPIC_API_KEY`** enables the **full** (Haiku) router when you want it. **MCP** can run **without** it using **embedding-only** routing (default when the key is omitted; see [MCP](#model-context-protocol-mcp)). |
|
|
63
62
|
|
|
64
63
|
**First run:** The CLI creates **`~/.skillforge/`**, a dedicated **Python venv**, installs Python dependencies, and caches the default embedding model (typically on the order of one to two minutes once; subsequent starts are fast).
|
|
65
64
|
|
|
@@ -73,12 +72,6 @@ npx --yes @heytherevibin/skillforge --help
|
|
|
73
72
|
|
|
74
73
|
Add Skillforge to your MCP config (see [MCP](#model-context-protocol-mcp)). No `ANTHROPIC_API_KEY` is required for **embedding-only** routing.
|
|
75
74
|
|
|
76
|
-
Optional HTTP API (e.g. for `skillforge chat`): set **`ANTHROPIC_API_KEY`**, then:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
skillforge start
|
|
80
|
-
```
|
|
81
|
-
|
|
82
75
|
Live log (usage + routes): **`skillforge events --watch`**.
|
|
83
76
|
|
|
84
77
|
---
|
|
@@ -111,11 +104,9 @@ Source and issues: [github.com/heytherevibin/skillforge](https://github.com/heyt
|
|
|
111
104
|
|---------|---------|
|
|
112
105
|
| `skillforge --help` | Recommended first step; **MCP** is the main integration. |
|
|
113
106
|
| `skillforge mcp` | **stdio** MCP server (Claude, Cursor, …). |
|
|
114
|
-
| `skillforge start [--port=8000]` | Optional **HTTP API** (no HTML or WebSocket UI). |
|
|
115
107
|
| `skillforge events [--watch]` | **Terminal** log: usage snapshot + routes; see **`skillforge events --help`**. |
|
|
116
108
|
| `skillforge route […]` | **Terminal** routing — same pipeline as MCP **`route_skills`** (loads embed model); see **`skillforge route --help`**. |
|
|
117
109
|
| `skillforge mcp config [--local] [--with-anthropic]` | **stdout**: JSON snippet for **`mcp.json`** (merge manually). |
|
|
118
|
-
| `skillforge chat` | Dev harness: HTTP client to **`POST /chat`** (needs **`start`** + API key). |
|
|
119
110
|
|
|
120
111
|
### Model Context Protocol (MCP)
|
|
121
112
|
|
|
@@ -163,29 +154,31 @@ With **Haiku** routing (uses your Anthropic key in the MCP process):
|
|
|
163
154
|
|
|
164
155
|
| Tool | Purpose |
|
|
165
156
|
|------|---------|
|
|
166
|
-
| `route_skills` | Returns routed **`SKILL.md`**
|
|
157
|
+
| `route_skills` | Returns routed **`SKILL.md`** context (chunks or full body). Pass **`project_root`** for per-repo SQLite under **`.skillforge/orchestrator.db`**. Optional **`include_project_rag`** (after **`skillforge index --project-root=…`**), **`session_id`**, **`user_id`** / **`SKILLFORGE_MCP_USER_ID`**, or env **`SKILLFORGE_PROJECT_ROOT`**. |
|
|
167
158
|
| `list_skills` | Catalog overview; optional **`user_id`** scopes usage stats. |
|
|
168
|
-
| `skill_feedback` | Feedback for the learning loop; optional **`user_id`**, **`session_id`** (
|
|
159
|
+
| `skill_feedback` | Feedback for the learning loop; optional **`user_id`**, **`session_id`** (stored with events). |
|
|
169
160
|
| `skill_referenced` | Mark a routed skill as **used** in the reply (increments **`referenced`** + weight; optional **`user_id`**). |
|
|
170
161
|
| `disable_skill` | Toggle skills; optional **`user_id`**. |
|
|
171
162
|
| `materialize_project` | Writes **`.cursor/rules/skillforge.mdc`**, **`docs/SKILLFORGE-PRD.md`**, updates **`CLAUDE.md`** (Skillforge block). Args: **`project_root`**, **`skill_names`** from **`route_skills`**. |
|
|
172
163
|
| `skillforge_bootstrap` | **`route_skills`** + **`materialize_project`** in one call (needs **`project_root`**). |
|
|
173
164
|
|
|
174
|
-
|
|
165
|
+
### MCP response contract
|
|
175
166
|
|
|
176
|
-
|
|
167
|
+
Structured diagnostics for tool **`route_skills`** live in **`result._meta`** (hosts may ignore or log them):
|
|
177
168
|
|
|
178
|
-
|
|
169
|
+
- **`schema_version`**: **`1.4`** — same as **1.3** plus optional **`context_redaction`** (`enabled`, `secret_hits`, `path_hits`) on **`route_skills`** success.
|
|
170
|
+
- **`sources`**: citations; each item has **`kind`** (`skill` or **`file`**), **`ref`**, **`line_start`** / **`line_end`**, **`score`**, optional **`mmr_rank`** after fusion.
|
|
171
|
+
- **`fusion`**: present when MMR fusion ran (**`enabled`: true**): **`lambda`**, **`budget_chars`**, **`pool_skill`**, **`pool_project`**, **`mmr_trace`**, …
|
|
172
|
+
- **`context_redaction`**: optional; when scrubbing is enabled, reports **`enabled`**, **`secret_hits`**, **`path_hits`** for exported context.
|
|
173
|
+
- **`budget`**: **`chars_skill_bodies`**, **`chars_project_chunks`**, **`chars_context_items_total`**, **`chars_response_total`**, **`est_tokens_approx`** (rough `chars/4`).
|
|
174
|
+
- **`candidates_preview`**: up to 15 shortlist entries **`{ name, score }`** for debugging.
|
|
175
|
+
- **`picked`**, **`reasoning`**, **`session_id`**, **`user_id`**, **`rerouted`**, **`change_pct`**, **`route_ms`**, **`orchestrator_db`**.
|
|
179
176
|
|
|
180
|
-
|
|
177
|
+
On validation errors (e.g. empty **`prompt`**), the tool returns **`isError`: true** and **`_meta.error`** (e.g. **`empty_prompt`**), still with **`schema_version`** and **`sources`: `[]`**.
|
|
181
178
|
|
|
182
|
-
|
|
183
|
-
skillforge auth add <user-id>
|
|
184
|
-
skillforge auth list
|
|
185
|
-
skillforge auth remove <user-id>
|
|
186
|
-
```
|
|
179
|
+
`/skillforge` is not registered by npm installs; add a **Cursor rule** or **CLAUDE.md** instruction so the agent calls these tools when the user asks.
|
|
187
180
|
|
|
188
|
-
|
|
181
|
+
Route events go to **`~/.skillforge/data/orchestrator.db`** (or per-repo **`.skillforge/orchestrator.db`** when **`project_root`** is set); use **`skillforge events`** to inspect them.
|
|
189
182
|
|
|
190
183
|
---
|
|
191
184
|
|
|
@@ -233,7 +226,7 @@ User prompt
|
|
|
233
226
|
→ Usage signals update weights (optional)
|
|
234
227
|
```
|
|
235
228
|
|
|
236
|
-
Re-route: when overlap between successive active sets falls below a configurable threshold, the pipeline selects a new set for the next turn. Events are stored in SQLite; stream them with **`skillforge events --watch
|
|
229
|
+
Re-route: when overlap between successive active sets falls below a configurable threshold, the pipeline selects a new set for the next turn. Events are stored in SQLite; stream them with **`skillforge events --watch`**.
|
|
237
230
|
|
|
238
231
|
---
|
|
239
232
|
|
|
@@ -243,16 +236,30 @@ Environment variables (see also inline help and server defaults):
|
|
|
243
236
|
|
|
244
237
|
| Variable | Default | Role |
|
|
245
238
|
|----------|---------|------|
|
|
246
|
-
| `ANTHROPIC_API_KEY` | — | **
|
|
247
|
-
| `SKILLFORGE_ROUTER_MODE` | *(auto)* | `full` = always use Haiku for final pick (
|
|
248
|
-
| `SKILLFORGE_PORT` | `8000` | HTTP listen port. |
|
|
239
|
+
| `ANTHROPIC_API_KEY` | — | **Optional** for MCP: omit for embedding-only routing (default when unset); set for **full** (Haiku) routing. |
|
|
240
|
+
| `SKILLFORGE_ROUTER_MODE` | *(auto)* | `full` = always use Haiku for final pick (requires key). `embedding` = skip Haiku; top `SKILLFORGE_MAX_ACTIVE` from shortlist. Unset = **auto**: embedding-only when `ANTHROPIC_API_KEY` is absent, else full. |
|
|
249
241
|
| `SKILLFORGE_EMBED_MODEL` | `all-MiniLM-L6-v2` | Embedding model id. |
|
|
250
|
-
| `SKILLFORGE_ROUTER_MODEL` | `claude-haiku-4-5-20251001` | Routing model. |
|
|
251
|
-
| `SKILLFORGE_ANSWER_MODEL` | `claude-opus-4-7` | Main response model. |
|
|
242
|
+
| `SKILLFORGE_ROUTER_MODEL` | `claude-haiku-4-5-20251001` | Routing model (Haiku). |
|
|
252
243
|
| `SKILLFORGE_TOP_K` | `15` | Embedding shortlist size. |
|
|
253
244
|
| `SKILLFORGE_MAX_ACTIVE` | `7` | Maximum skills injected per turn. |
|
|
254
245
|
| `SKILLFORGE_REROUTE_THRESHOLD` | `0.4` | Re-route sensitivity (Jaccard distance). |
|
|
255
|
-
| `
|
|
246
|
+
| `SKILLFORGE_CONTEXT_MODE` | `chunks` | `chunks` = embed **line-bounded chunks** from each picked skill body (RAG) up to **`SKILLFORGE_ROUTE_MAX_CHARS`**. `full_body` = inject entire **SKILL.md** per pick (legacy). |
|
|
247
|
+
| `SKILLFORGE_CHUNK_MAX_CHARS` | `1200` | Max characters per chunk (before overlap split). |
|
|
248
|
+
| `SKILLFORGE_CHUNK_OVERLAP` | `200` | Character overlap when hard-splitting an oversized section. |
|
|
249
|
+
| `SKILLFORGE_ROUTE_MAX_CHARS` | `60000` | Skill chunk char cap when **`SKILLFORGE_CONTEXT_FUSION`** is off (append path); also part of default unified budget sum when fusion is on. |
|
|
250
|
+
| `SKILLFORGE_PROJECT_RAG_MAX_CHARS` | `24000` | Project chunk char cap when fusion is off (append path); part of default unified budget when fusion is on. |
|
|
251
|
+
| `SKILLFORGE_CONTEXT_BUDGET_CHARS` | *(route + project RAG defaults)* | Single cap for **MMR-fused** skill + project context. |
|
|
252
|
+
| `SKILLFORGE_CONTEXT_FUSION` | `1` | **`0`** / **`false`**: disable MMR fusion; append project chunks after skills. |
|
|
253
|
+
| `SKILLFORGE_CONTEXT_MMR_LAMBDA` | `0.7` | MMR tradeoff: higher ⇒ query relevance; lower ⇒ diversity vs. already-selected chunks. |
|
|
254
|
+
| `SKILLFORGE_FUSION_POOL_SKILL` | `96` | Max skill chunks in the fusion candidate pool. |
|
|
255
|
+
| `SKILLFORGE_FUSION_POOL_PROJECT` | `96` | Max project chunks in the fusion candidate pool. |
|
|
256
|
+
| `SKILLFORGE_FUSION_FULL_BODY_PREVIEW_CHARS` | `4000` | **`SKILL.md`** prefix length for embedding full-body / fallback fusion rows. |
|
|
257
|
+
| `SKILLFORGE_PROJECT_RAG_MAX_CHUNKS` | `20000` | Max **project_chunks** rows loaded for one retrieval. |
|
|
258
|
+
| `SKILLFORGE_INDEX_MAX_FILE_BYTES` | `524288` | Skip indexing files larger than this (bytes). |
|
|
259
|
+
| `SKILLFORGE_INDEX_IGNORE_DIRS` | `""` | Extra comma-separated directory **basename** ignores (e.g. `out,tmp`). |
|
|
260
|
+
| `SKILLFORGE_REDACT_CONTEXT` | `1` | When **`1`** (default), scrub common secret shapes and (with home path scrub) exported context before MCP/CLI output and route events. |
|
|
261
|
+
| `SKILLFORGE_REDACT_HOME_IN_PATHS` | `1` | Replace resolved home-directory prefixes in chunk paths / DB path hints with **`[HOME]`**. |
|
|
262
|
+
| `SKILLFORGE_MCP_USER_ID` | `""` | Default logical **user id** for MCP tool calls when arguments omit `user_id` (weights, sessions, events). |
|
|
256
263
|
| `SKILLFORGE_PROJECT_ROOT` | `""` | Default workspace root when MCP **`project_root`** is omitted: events/weights/sessions live in **`<root>/.skillforge/orchestrator.db`**. Prefer passing **`project_root`** on each tool call from the host. |
|
|
257
264
|
| `SKILLFORGE_SKILL_HOT_RELOAD` | `1` | When **`0`** / **`false`**, disable **SKILL.md** hot-reload; restart the MCP process to refresh the catalog. |
|
|
258
265
|
| `SKILLFORGE_WATCH_SKILLS_INTERVAL` | `30` | Seconds between background catalog checks when hot reload is on. **`0`**: no background polling and no MCP **`tools.listChanged`**; **`tools/list`** and **`tools/call`** still reload when files change. |
|
|
@@ -260,29 +267,13 @@ Environment variables (see also inline help and server defaults):
|
|
|
260
267
|
|
|
261
268
|
---
|
|
262
269
|
|
|
263
|
-
## HTTP API
|
|
264
|
-
|
|
265
|
-
| Method | Path | Description |
|
|
266
|
-
|--------|------|-------------|
|
|
267
|
-
| `POST` | `/chat` | Primary chat; SSE stream. |
|
|
268
|
-
| `POST` | `/feedback` | Skill feedback for learning. |
|
|
269
|
-
| `POST` | `/skills/disable` | Enable/disable a skill flag. |
|
|
270
|
-
| `GET` | `/skills` | Catalog with stats and weights. |
|
|
271
|
-
| `GET` | `/events` | Recent routing events (`?limit=`). |
|
|
272
|
-
| `GET` | `/` | JSON service hint (use **`skillforge events --watch`** for a live terminal log). |
|
|
273
|
-
| `GET` | `/healthz` | Health metadata (`skills_loaded`, **`live_log`** hint). |
|
|
274
|
-
|
|
275
|
-
Authenticated mode applies **`Bearer`** tokens as described above. Do not expose unauthenticated instances beyond trusted networks.
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
270
|
## Local data and operations
|
|
280
271
|
|
|
281
272
|
Optional **per-project** state (when **`project_root`** or **`SKILLFORGE_PROJECT_ROOT`** is set, or MCP passes **`project_root`** on tools):
|
|
282
273
|
|
|
283
274
|
```
|
|
284
275
|
<workspace>/.skillforge/
|
|
285
|
-
├── orchestrator.db # SQLite
|
|
276
|
+
├── orchestrator.db # SQLite: sessions, weights, events, **project_chunks** (after `skillforge index`)
|
|
286
277
|
└── last_route.json # Last route_skills snapshot (after a routed call)
|
|
287
278
|
```
|
|
288
279
|
|
|
@@ -295,12 +286,12 @@ Global default when no project root:
|
|
|
295
286
|
├── skills/ # User-added skills
|
|
296
287
|
├── packs/<hash>/ # Cloned pack repositories
|
|
297
288
|
├── packs.json # Pack registry
|
|
298
|
-
└── auth.json # Tokens (POSIX mode 0600 when used)
|
|
299
289
|
```
|
|
300
290
|
|
|
301
291
|
| Command | Effect |
|
|
302
292
|
|---------|--------|
|
|
303
293
|
| `skillforge events` | Prints a **usage** snapshot and recent **`route`** / **`feedback`** rows; **`--watch`**, **`--project-root`** (per-repo DB), **`--user`**, **`--verbose`** (see **`--help`**). |
|
|
294
|
+
| `skillforge index` | Chunk/embed text files under **`--project-root`** into **`project_chunks`**. **`--reset`**, **`--stats-only`**, **`--quiet`** (see **`--help`**). |
|
|
304
295
|
| `skillforge reset` | Clears learning state and event history in the database. |
|
|
305
296
|
| `skillforge install` | Re-runs bootstrap (venv and dependencies). |
|
|
306
297
|
| `rm -rf ~/.skillforge` | Full removal of local state and venv. |
|
|
@@ -309,8 +300,8 @@ Global default when no project root:
|
|
|
309
300
|
|
|
310
301
|
## Security considerations
|
|
311
302
|
|
|
312
|
-
-
|
|
313
|
-
-
|
|
303
|
+
- **Redaction is best-effort regex scrubbing**, not a guarantee. Do not paste production secrets into prompts; treat routed context like **untrusted text** until reviewed.
|
|
304
|
+
- Treat **`ANTHROPIC_API_KEY`** as a **secret** when you set it (e.g. for Haiku routing in MCP). Prefer environment injection or secret stores, not committed files.
|
|
314
305
|
- Vulnerability disclosure: see **[SECURITY.md](SECURITY.md)**.
|
|
315
306
|
|
|
316
307
|
---
|
package/RELEASING.md
CHANGED
|
@@ -77,7 +77,7 @@ npm test
|
|
|
77
77
|
Python (syntax only):
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
for f in python/app/main.py python/app/
|
|
80
|
+
for f in python/app/main.py python/app/mcp_server.py python/app/events_cli.py python/app/materialize.py python/app/db_paths.py python/app/route_cli.py python/app/mcp_contract.py python/app/chunking.py python/app/project_index.py python/app/index_cli.py python/app/context_fusion.py python/app/redaction.py; do python3 -m py_compile "$f"; done
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
## Troubleshooting: `EOTP` / one-time password in CI
|
package/SECURITY.md
CHANGED
|
@@ -26,6 +26,6 @@ We aim to acknowledge valid reports within a few business days.
|
|
|
26
26
|
- Keep **2FA** enabled on the npm account that owns the `@heytherevibin` scope.
|
|
27
27
|
- Prefer pinning action versions or reviewing Dependabot PRs before merge.
|
|
28
28
|
|
|
29
|
-
## Runtime
|
|
29
|
+
## Runtime security
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Skillforge’s published surface is **local**: **stdio MCP** and **CLI** commands. Keep your **MCP host** and **`ANTHROPIC_API_KEY`** (if used) within a trusted environment. Do not commit secrets.
|
package/STRATEGY.md
CHANGED
|
@@ -7,8 +7,7 @@ Skillforge is an **npm-packaged skill orchestrator**: it routes tasks to a small
|
|
|
7
7
|
## Surfaces (today)
|
|
8
8
|
|
|
9
9
|
- **MCP** (`skillforge mcp`): primary — `route_skills`, `list_skills`, feedback tools, `materialize_project`, `skillforge_bootstrap`.
|
|
10
|
-
- **Terminal**: `skillforge events --watch` with optional **`--project-root
|
|
11
|
-
- **HTTP** (`skillforge start`): optional; still uses the global DB in **app_state** (not per-request project root unless extended later).
|
|
10
|
+
- **Terminal**: `skillforge events --watch` with optional **`--project-root`**; `skillforge route`, `skillforge index`.
|
|
12
11
|
|
|
13
12
|
## Cursor reality
|
|
14
13
|
|
|
@@ -17,7 +16,6 @@ Native **`/skillforge`** in editor chat is **not** registered by this npm packag
|
|
|
17
16
|
## Near-term backlog
|
|
18
17
|
|
|
19
18
|
- Shared **`orchestrate()`** API for MCP + CLI parity.
|
|
20
|
-
- HTTP: optional **`project_root`** header or body for `/chat` if needed.
|
|
21
19
|
- Tests: MCP handshake + `resolve_orchestrator_db` behavior.
|
|
22
20
|
|
|
23
21
|
## Non-goals (v1)
|
package/bin/cli.js
CHANGED
|
@@ -5,17 +5,15 @@
|
|
|
5
5
|
* Usage:
|
|
6
6
|
* skillforge, skillforge --help Show help (primary path: MCP, not a web app)
|
|
7
7
|
* skillforge mcp MCP stdio server (Claude / Cursor / …)
|
|
8
|
-
* skillforge start [--port=8000] Optional headless HTTP API (no browser UI)
|
|
9
8
|
* skillforge events [--watch] [--limit=N] Print SQLite routing events
|
|
10
9
|
* skillforge route [words…] [--prompt=…] Same routing as MCP route_skills (terminal)
|
|
11
|
-
* skillforge
|
|
10
|
+
* skillforge index --project-root=… Chunk/embed repo files for project RAG
|
|
12
11
|
* skillforge install One-time Python venv + deps
|
|
13
|
-
* skillforge skills … / pack … /
|
|
12
|
+
* skillforge skills … / pack … / reset
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
15
|
const path = require('path');
|
|
17
16
|
const fs = require('fs');
|
|
18
|
-
const crypto = require('crypto');
|
|
19
17
|
const { spawn, spawnSync } = require('child_process');
|
|
20
18
|
const os = require('os');
|
|
21
19
|
const packs = require('../lib/packs');
|
|
@@ -26,8 +24,8 @@ const CONFIG_DIR = path.join(os.homedir(), '.skillforge');
|
|
|
26
24
|
const VENV_DIR = path.join(CONFIG_DIR, 'venv');
|
|
27
25
|
const DATA_DIR = path.join(CONFIG_DIR, 'data');
|
|
28
26
|
const USER_SKILLS_DIR = path.join(CONFIG_DIR, 'skills');
|
|
29
|
-
|
|
30
|
-
const
|
|
27
|
+
/** Bearer-token file for the removed HTTP API (<=0.6.x); deleted on first CLI use. */
|
|
28
|
+
const LEGACY_AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
|
|
31
29
|
const SETUP_MARKER = path.join(CONFIG_DIR, '.setup-complete');
|
|
32
30
|
|
|
33
31
|
const args = process.argv.slice(2);
|
|
@@ -87,6 +85,18 @@ function ensureDirs() {
|
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
|
|
88
|
+
/** v0.7.0 removed HTTP + `skillforge auth`; leftover tokens file is misleading — remove once. */
|
|
89
|
+
function dropLegacyAuthJsonIfPresent() {
|
|
90
|
+
try {
|
|
91
|
+
if (fs.existsSync(LEGACY_AUTH_FILE)) {
|
|
92
|
+
fs.rmSync(LEGACY_AUTH_FILE);
|
|
93
|
+
info('Removed legacy ~/.skillforge/auth.json (HTTP API was removed in v0.7).');
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
err(`Could not remove legacy auth.json: ${e.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
function runSetup() {
|
|
91
101
|
info('First-time setup — this happens once and takes ~2 minutes');
|
|
92
102
|
ensureDirs();
|
|
@@ -145,77 +155,7 @@ function setupIfNeeded() {
|
|
|
145
155
|
}
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
// ---- API key check ----
|
|
149
|
-
function checkApiKey() {
|
|
150
|
-
if (!process.env.ANTHROPIC_API_KEY) {
|
|
151
|
-
err('ANTHROPIC_API_KEY environment variable is not set.');
|
|
152
|
-
log(c.dim(' Get a key at https://console.anthropic.com/'));
|
|
153
|
-
log(c.dim(' Then set it:'));
|
|
154
|
-
log(c.dim(' export ANTHROPIC_API_KEY=sk-ant-...'));
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ---- auth management ----
|
|
160
|
-
function loadAuth() {
|
|
161
|
-
if (!fs.existsSync(AUTH_FILE)) return {};
|
|
162
|
-
try { return JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')); } catch { return {}; }
|
|
163
|
-
}
|
|
164
|
-
function saveAuth(map) {
|
|
165
|
-
ensureDirs();
|
|
166
|
-
fs.writeFileSync(AUTH_FILE, JSON.stringify(map, null, 2), { mode: 0o600 });
|
|
167
|
-
}
|
|
168
|
-
function authToEnvVar(map) {
|
|
169
|
-
// map is { token: userId }. Convert and inject as JSON env var.
|
|
170
|
-
return JSON.stringify(map);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function authAdd(user) {
|
|
174
|
-
if (!user) { err('Usage: skillforge auth add <user-id>'); process.exit(1); }
|
|
175
|
-
const map = loadAuth();
|
|
176
|
-
// Generate a token
|
|
177
|
-
const token = 'sf_' + crypto.randomBytes(24).toString('base64url');
|
|
178
|
-
map[token] = user;
|
|
179
|
-
saveAuth(map);
|
|
180
|
-
ok(`Created token for user "${user}":`);
|
|
181
|
-
log('');
|
|
182
|
-
log(' ' + c.bold(token));
|
|
183
|
-
log('');
|
|
184
|
-
log(c.dim('Use this token in the Authorization header:'));
|
|
185
|
-
log(c.dim(` Authorization: Bearer ${token}`));
|
|
186
|
-
log(c.dim('Restart the server for the token to take effect.'));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function authList() {
|
|
190
|
-
const map = loadAuth();
|
|
191
|
-
const tokens = Object.entries(map);
|
|
192
|
-
if (tokens.length === 0) {
|
|
193
|
-
info('No auth tokens. Server runs in single-user mode.');
|
|
194
|
-
log(c.dim(' Add one with: skillforge auth add <user-id>'));
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
log(c.bold('Auth tokens:'));
|
|
198
|
-
for (const [token, user] of tokens) {
|
|
199
|
-
log(` ${c.dim(token.slice(0, 16) + '...')} → ${user}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function authRemove(user) {
|
|
204
|
-
if (!user) { err('Usage: skillforge auth remove <user-id>'); process.exit(1); }
|
|
205
|
-
const map = loadAuth();
|
|
206
|
-
const before = Object.keys(map).length;
|
|
207
|
-
for (const [t, u] of Object.entries(map)) {
|
|
208
|
-
if (u === user) delete map[t];
|
|
209
|
-
}
|
|
210
|
-
const removed = before - Object.keys(map).length;
|
|
211
|
-
saveAuth(map);
|
|
212
|
-
if (removed > 0) ok(`Revoked ${removed} token(s) for "${user}"`);
|
|
213
|
-
else info(`No tokens for "${user}"`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ---- server lifecycle ----
|
|
217
158
|
function buildEnv(extra = {}) {
|
|
218
|
-
const authMap = loadAuth();
|
|
219
159
|
return {
|
|
220
160
|
...process.env,
|
|
221
161
|
SKILLFORGE_BUNDLED_SKILLS: path.join(PKG_ROOT, 'skills'),
|
|
@@ -223,36 +163,10 @@ function buildEnv(extra = {}) {
|
|
|
223
163
|
SKILLFORGE_DB_PATH: path.join(DATA_DIR, 'orchestrator.db'),
|
|
224
164
|
PYTHONPATH: path.join(PKG_ROOT, 'python'),
|
|
225
165
|
PYTHONUNBUFFERED: '1',
|
|
226
|
-
...(Object.keys(authMap).length > 0 ? { SKILLFORGE_AUTH_TOKENS: authToEnvVar(authMap) } : {}),
|
|
227
166
|
...extra,
|
|
228
167
|
};
|
|
229
168
|
}
|
|
230
169
|
|
|
231
|
-
function startServer({ port = 8000 } = {}) {
|
|
232
|
-
setupIfNeeded();
|
|
233
|
-
checkApiKey();
|
|
234
|
-
|
|
235
|
-
const env = buildEnv({ SKILLFORGE_PORT: String(port) });
|
|
236
|
-
const authEnabled = Object.keys(loadAuth()).length > 0;
|
|
237
|
-
|
|
238
|
-
info(`Starting HTTP API on http://localhost:${port}`);
|
|
239
|
-
log(c.dim(' Live log: skillforge events --watch'));
|
|
240
|
-
log(c.dim(` Skills dir: ${USER_SKILLS_DIR} (drop folders here to add)`));
|
|
241
|
-
log(c.dim(` Data dir: ${DATA_DIR}`));
|
|
242
|
-
log(c.dim(` Auth: ${authEnabled ? 'enabled (bearer token required)' : 'disabled (single-user)'}`));
|
|
243
|
-
log('');
|
|
244
|
-
|
|
245
|
-
const proc = spawn(
|
|
246
|
-
venvPython(),
|
|
247
|
-
['-m', 'uvicorn', 'app.main:app', '--host', '0.0.0.0', '--port', String(port)],
|
|
248
|
-
{ stdio: 'inherit', env }
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
proc.on('exit', (code) => process.exit(code || 0));
|
|
252
|
-
process.on('SIGINT', () => proc.kill('SIGINT'));
|
|
253
|
-
process.on('SIGTERM', () => proc.kill('SIGTERM'));
|
|
254
|
-
}
|
|
255
|
-
|
|
256
170
|
function printMcpConfig() {
|
|
257
171
|
setupIfNeeded();
|
|
258
172
|
const useLocal = args.includes('--local');
|
|
@@ -313,12 +227,14 @@ function runRouteCmd() {
|
|
|
313
227
|
proc.on('exit', (code) => process.exit(code ?? 0));
|
|
314
228
|
}
|
|
315
229
|
|
|
316
|
-
function
|
|
230
|
+
function runIndexCmd() {
|
|
317
231
|
setupIfNeeded();
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
232
|
+
const sub = args.slice(1);
|
|
233
|
+
const proc = spawn(venvPython(), ['-m', 'app.index_cli', ...sub], {
|
|
234
|
+
stdio: 'inherit',
|
|
235
|
+
env: buildEnv(),
|
|
236
|
+
});
|
|
237
|
+
proc.on('exit', (code) => process.exit(code ?? 0));
|
|
322
238
|
}
|
|
323
239
|
|
|
324
240
|
// ---- skill management ----
|
|
@@ -341,7 +257,7 @@ function skillsAdd(srcPath) {
|
|
|
341
257
|
const dest = path.join(USER_SKILLS_DIR, name);
|
|
342
258
|
fs.cpSync(src, dest, { recursive: true });
|
|
343
259
|
ok(`Added skill "${name}" → ${dest}`);
|
|
344
|
-
log(c.dim(' Restart
|
|
260
|
+
log(c.dim(' Restart skillforge mcp (or trigger catalog reload) to pick up the new skill.'));
|
|
345
261
|
}
|
|
346
262
|
|
|
347
263
|
function skillsList() {
|
|
@@ -373,7 +289,7 @@ function skillsRemove(name) {
|
|
|
373
289
|
}
|
|
374
290
|
const target = path.join(USER_SKILLS_DIR, name);
|
|
375
291
|
if (!fs.existsSync(target)) {
|
|
376
|
-
err(`No user skill named "${name}". Bundled skills cannot be removed (use disable_skill via MCP
|
|
292
|
+
err(`No user skill named "${name}". Bundled skills cannot be removed (use disable_skill via MCP).`);
|
|
377
293
|
process.exit(1);
|
|
378
294
|
}
|
|
379
295
|
fs.rmSync(target, { recursive: true, force: true });
|
|
@@ -398,10 +314,9 @@ ${c.bold('Run modes:')}
|
|
|
398
314
|
skillforge --help This message (recommended first step)
|
|
399
315
|
skillforge mcp MCP stdio — primary integration for Claude / Cursor
|
|
400
316
|
skillforge mcp config [--local] [--with-anthropic] Print JSON for MCP host (merge into mcp.json)
|
|
401
|
-
skillforge start [--port=8000] Optional HTTP API (no web dashboard)
|
|
402
317
|
skillforge events [--watch] [--limit=N] [--verbose] [--user=…] Live routing log + usage (see --help)
|
|
403
|
-
skillforge route [words…] [--project-root=…] [--
|
|
404
|
-
skillforge
|
|
318
|
+
skillforge route [words…] [--project-root=…] [--include-project-rag] Route a prompt (see skillforge route --help)
|
|
319
|
+
skillforge index --project-root=… [--reset] [--stats-only] Index repo text for include_project_rag
|
|
405
320
|
|
|
406
321
|
${c.bold('Skills:')}
|
|
407
322
|
skillforge skills list List bundled and user skills
|
|
@@ -414,11 +329,6 @@ ${c.bold('Skill packs (install from git):')}
|
|
|
414
329
|
skillforge pack update <name> Update a pack
|
|
415
330
|
skillforge pack remove <name> Uninstall a pack
|
|
416
331
|
|
|
417
|
-
${c.bold('Auth (multi-user mode):')}
|
|
418
|
-
skillforge auth add <user> Create a bearer token for a user
|
|
419
|
-
skillforge auth list List users with tokens
|
|
420
|
-
skillforge auth remove <user> Revoke all tokens for a user
|
|
421
|
-
|
|
422
332
|
${c.bold('Maintenance:')}
|
|
423
333
|
skillforge reset Wipe learned state and event log
|
|
424
334
|
skillforge install Re-run setup (auto-runs on first launch)
|
|
@@ -436,29 +346,25 @@ ${c.bold('MCP integration:')}
|
|
|
436
346
|
|
|
437
347
|
// ---- main ----
|
|
438
348
|
async function main() {
|
|
349
|
+
dropLegacyAuthJsonIfPresent();
|
|
350
|
+
|
|
439
351
|
if (args.includes('--help') || args.includes('-h') || cmd === 'help') {
|
|
440
352
|
showHelp();
|
|
441
353
|
return;
|
|
442
354
|
}
|
|
443
355
|
|
|
444
|
-
const portArg = args.find((a) => a.startsWith('--port='));
|
|
445
|
-
const port = portArg ? parseInt(portArg.split('=')[1], 10) : 8000;
|
|
446
|
-
|
|
447
356
|
switch (cmd) {
|
|
448
357
|
case undefined:
|
|
449
358
|
showHelp();
|
|
450
359
|
break;
|
|
451
|
-
case 'start':
|
|
452
|
-
startServer({ port });
|
|
453
|
-
break;
|
|
454
360
|
case 'events':
|
|
455
361
|
runEventsCmd();
|
|
456
362
|
break;
|
|
457
363
|
case 'route':
|
|
458
364
|
runRouteCmd();
|
|
459
365
|
break;
|
|
460
|
-
case '
|
|
461
|
-
|
|
366
|
+
case 'index':
|
|
367
|
+
runIndexCmd();
|
|
462
368
|
break;
|
|
463
369
|
case 'mcp':
|
|
464
370
|
if (args[1] === 'config') {
|
|
@@ -492,7 +398,7 @@ async function main() {
|
|
|
492
398
|
const result = packs.installPack(args[2]);
|
|
493
399
|
ok(`Installed pack "${result.name}" (${result.version}) with ${result.skills.length} skill(s):`);
|
|
494
400
|
result.skills.forEach(s => log(' ' + c.dim('•'), s));
|
|
495
|
-
log(c.dim(' Restart
|
|
401
|
+
log(c.dim(' Restart skillforge mcp (or trigger catalog reload) to pick up new skills.'));
|
|
496
402
|
} else if (sub === 'list') {
|
|
497
403
|
const list = packs.listPacks();
|
|
498
404
|
if (list.length === 0) {
|
|
@@ -522,18 +428,6 @@ async function main() {
|
|
|
522
428
|
}
|
|
523
429
|
break;
|
|
524
430
|
}
|
|
525
|
-
case 'auth': {
|
|
526
|
-
const sub = args[1];
|
|
527
|
-
if (sub === 'add') authAdd(args[2]);
|
|
528
|
-
else if (sub === 'list') authList();
|
|
529
|
-
else if (sub === 'remove' || sub === 'rm') authRemove(args[2]);
|
|
530
|
-
else {
|
|
531
|
-
err(`Unknown auth subcommand: ${sub}`);
|
|
532
|
-
log(c.dim(' Try: add, list, remove'));
|
|
533
|
-
process.exit(1);
|
|
534
|
-
}
|
|
535
|
-
break;
|
|
536
|
-
}
|
|
537
431
|
default:
|
|
538
432
|
err(`Unknown command: ${cmd}`);
|
|
539
433
|
showHelp();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heytherevibin/skillforge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Skill orchestration for Claude: hybrid embedding and router-based routing, MCP
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Skill orchestration for Claude: hybrid embedding and router-based routing, MCP stdio server, per-user learning, and a large bundled SKILL.md catalog.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"skills",
|