@heytherevibin/skillforge 0.10.1 → 0.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/CONTRIBUTING.md +5 -3
  3. package/README.md +37 -345
  4. package/RELEASING.md +7 -6
  5. package/STRATEGY.md +2 -2
  6. package/bin/cli.js +297 -52
  7. package/ci/test-user-env-profile.cjs +65 -0
  8. package/docs/README.md +14 -0
  9. package/docs/architecture-and-data.md +90 -0
  10. package/docs/cli-reference.md +57 -0
  11. package/docs/environment-and-configuration.md +76 -0
  12. package/docs/getting-started.md +88 -0
  13. package/docs/mcp-integration.md +75 -0
  14. package/docs/troubleshooting.md +50 -0
  15. package/lib/templates/claude-code-skillforge-global.md +3 -3
  16. package/lib/templates/cursor-skillforge-global.md +6 -2
  17. package/lib/user-env-profile.js +141 -0
  18. package/package.json +3 -2
  19. package/python/app/agent_cli.py +334 -0
  20. package/python/app/explain_route.py +170 -0
  21. package/python/app/health_cli.py +13 -0
  22. package/python/app/main.py +131 -48
  23. package/python/app/materialize.py +150 -68
  24. package/python/app/mcp_contract.py +2 -1
  25. package/python/app/mcp_operator.py +252 -0
  26. package/python/app/mcp_server.py +290 -118
  27. package/python/app/npm_pkg_version.py +38 -0
  28. package/python/app/pick_diversify.py +51 -0
  29. package/python/app/replay_cli.py +145 -0
  30. package/python/app/route_cli.py +251 -87
  31. package/python/app/route_cli_pick.py +35 -0
  32. package/python/app/route_policies.py +18 -3
  33. package/python/app/route_quality.py +70 -1
  34. package/python/app/router_llm.py +85 -0
  35. package/python/app/router_mode.py +21 -0
  36. package/python/app/routing_signals.py +7 -1
  37. package/python/app/skill_manifest.py +67 -0
  38. package/python/app/skills_author_cli.py +117 -0
  39. package/python/app/tips_cli.py +37 -0
  40. package/python/app/tools_cli.py +276 -0
  41. package/python/fixtures/route_eval/smoke.json +5 -0
  42. package/python/requirements.txt +1 -0
  43. package/python/tests/test_capabilities_bundle.py +33 -0
  44. package/python/tests/test_materialize_hosts.py +108 -0
  45. package/python/tests/test_mcp_contract.py +1 -1
  46. package/python/tests/test_mcp_initialize_clientinfo.py +26 -0
  47. package/python/tests/test_mcp_operator.py +84 -0
  48. package/python/tests/test_npm_pkg_version.py +21 -0
  49. package/python/tests/test_pick_diversify.py +47 -0
  50. package/python/tests/test_replay_cli.py +31 -0
  51. package/python/tests/test_route_cli_pick.py +25 -0
  52. package/python/tests/test_route_policies.py +29 -0
  53. package/python/tests/test_route_quality.py +72 -0
  54. package/python/tests/test_router_llm.py +63 -0
  55. package/python/tests/test_router_mode_env.py +21 -0
  56. package/python/tests/test_routing_signals.py +20 -0
  57. package/python/tests/test_skill_manifest.py +48 -0
  58. package/python/tests/test_tools_cli.py +69 -0
@@ -0,0 +1,57 @@
1
+ # CLI reference
2
+
3
+ **Entrypoint:** `bin/cli.js` (published as **`skillforge`** on npm).
4
+
5
+ All Python CLIs honour the merged environment (**`buildEnv`** in **`bin/cli.js`**). Prefer **`skillforge …`** rather than invoking **`python -m app.*`** manually unless you are developing core modules.
6
+
7
+ Run **`skillforge --help`** (or **`node bin/cli.js --help`**) for the condensed matrix; **`skillforge <cmd> --help`** forwards submodule help (**`route`**, **`agent`**, **`tools`**, …).
8
+
9
+ ## Launch surface (by concern)
10
+
11
+ ### Core workflows
12
+
13
+ | Command | Description |
14
+ |---------|-------------|
15
+ | **`skillforge mcp`** | Start stdio MCP server (**forces `SKILLFORGE_TRANSPORT=mcp`**). |
16
+ | **`skillforge route …`** | Same routing pipeline as MCP **`route_skills`** (TTY interactive **`-i`**, **`--json`**, **`--explain`**). |
17
+ | **`skillforge tools …`** | Subcommands mirror MCP tool handlers (**`skillforge tools -h`** enumerates verbs). **`--json`** prints raw MCP-shaped envelopes for automation. |
18
+ | **`skillforge agent …`** | Standalone OpenAI-compatible assistant loop invoking MCP-backed tool handlers (**`OPENAI_*`**, **`SKILLFORGE_AGENT_*`**). **`--help`** works before Python **`openai`** import; run **`skillforge install`** after upgrades when deps drift. |
19
+
20
+ ### Workspace + catalog
21
+
22
+ | Command | Description |
23
+ |---------|-------------|
24
+ | **`skillforge index --project-root=…`** | Chunk/embed repository text · writes **`project_chunks`** per project SQLite. |
25
+ | **`skillforge skills …`** | **`list`**, **`add`**, **`remove`**, **`init`**, **`lint`** authoring helpers (**`skills_author_cli`**). |
26
+ | **`skillforge pack …`** | Install/list/update/remove git-hosted skill bundles (**`lib/packs.js`** bridge). |
27
+
28
+ ### Observability / ops
29
+
30
+ | Command | Description |
31
+ |---------|-------------|
32
+ | **`skillforge events …`** | Tail SQLite routing / feedback events (**`--watch`**). |
33
+ | **`skillforge replay …`** | Timeline reconstructor across stored events (**`--session-id`**). |
34
+ | **`skillforge health …`** | Path + catalogue checks (**`--quick`** skips heavyweight embed/router load); JSON via **`--json`**. **`user_env_profile`** row notes whether **`~/.skillforge/env`** exists. |
35
+ | **`skillforge route-eval …`** | Fixture embedding harness (CI consumes **`fixtures/route_eval/*.json`**). |
36
+ | **`skillforge weights export|import …`** | Portable snapshots of **`skill_weights`**. |
37
+
38
+ ### Setup / ergonomics
39
+
40
+ | Command | Description |
41
+ |---------|-------------|
42
+ | **`skillforge install`** | Provision **`~/.skillforge/venv`**, Python deps (**`requirements.txt`**), optional editor hooks (**`hosts init`** artefacts). |
43
+ | **`skillforge config path|init|validate`** | Stable **`~/.skillforge/env`** profile + linter (**`validate`** exit codes documented in env guide). |
44
+ | **`skillforge hosts init` / `skillforge cursor init`** | Write managed slash commands (**`/skillforge`**) — no Python prerequisite. |
45
+ | **`skillforge tips`** | Human-readable MCP + terminal cheat-sheet. |
46
+
47
+ ## Flags worth memorising (**`skillforge route`**)
48
+
49
+ | Flag / env | Behaviour |
50
+ |------------|-----------|
51
+ | **`-i`** or **`SKILLFORGE_ROUTE_INTERACTIVE=1`** | Prompt for ranks after **`host`** shortlists (TTY only). |
52
+ | **`--json`** | Single envelope with **`phase`** markers for scripting (**`route_cli`**). |
53
+ | **`--picked-names=id1,id2`** | Implements second leg of **`host`** finalize. |
54
+
55
+ ## Automation tip
56
+
57
+ Prefer **`skillforge tools <verb> … --json`** when you want identical payloads compared to MCP (great for scripted ops + CI probes).
@@ -0,0 +1,76 @@
1
+ # Environment & configuration
2
+
3
+ ## Configuration layers (in merge order)
4
+
5
+ When you run **`skillforge <anything>`**, **Node `bin/cli.js`** builds a child **`process.env`** as follows:
6
+
7
+ 1. **`~/.skillforge/env`** (optional dotenv-style file) — **`skillforge config path`**, **`init`**, **`validate`**.
8
+ 2. **Current process env** — your shell, CI secrets, MCP host **`server.env`** / **`mcpServers.*.env`**.
9
+ 3. **Bootstrap keys always set last by Skillforge** — at minimum **`PYTHONPATH`**, **`SKILLFORGE_BUNDLED_SKILLS`**, **`SKILLFORGE_USER_SKILLS`**, **`SKILLFORGE_DB_PATH`**, **`PYTHONUNBUFFERED=1`**.
10
+ 4. **`SKILLFORGE_TRANSPORT=mcp`** is appended **only** for **`skillforge mcp`** (Python treats MCP differently from standalone CLI).
11
+
12
+ Higher layers override lower ones for overlapping keys—but built-in SKILLFORGE path keys always win over accidental values in **`~/.skillforge/env`**.
13
+
14
+ ## ~/.skillforge/env (operator profile)
15
+
16
+ | Command | Purpose |
17
+ |---------|---------|
18
+ | **`skillforge config path`** | Print absolute path (`~/.skillforge/env`). |
19
+ | **`skillforge config init [--force]`** | Write commented template (`--force` overwrites). |
20
+ | **`skillforge config validate`** | Lint; **`error`** issues exit **1**; missing file exits **0**. |
21
+
22
+ **Syntax:** one **`KEY=value`** per line, optional **`export`**, **`#`** comments; variable names **`[A-Za-z_][A-Za-z0-9_]*`**; optional quotes. No shell **`${VAR}`** expansion—parser lives in **`lib/user-env-profile.js`**.
23
+
24
+ ## MCP **`entry.env`** vs profile
25
+
26
+ If the IDE merges **`env`** into the **`skillforge mcp`** process, those vars sit in **layer 2** and **override** the same keys from **`~/.skillforge/env`**. Put **shared defaults** in the profile file; put **host-specific overrides** (or secrets the IDE injects exclusively) in **`mcp.json`**.
27
+
28
+ ## SKILLFORGE_TRANSPORT (**MCP** only)
29
+
30
+ **`skillforge mcp`** forces **`SKILLFORGE_TRANSPORT=mcp`**. In that mode router LLM wiring follows **Anthropic** legacy paths for MCP. Standalone CLI (no transport flag) may use **`SKILLFORGE_ROUTER_LLM_BACKEND=openai_compatible`** for OpenAI-compatible routing—see codebase **`app/main.py`** / **`app/router_llm.py`**.
31
+
32
+ ## Direct Python (**advanced**)
33
+
34
+ If you bypass Node and run **`python -m app.mcp_server`**, **`~/.skillforge/env` is not read**. Either use **`skillforge mcp`** or replicate **`buildEnv()`** wiring yourself.
35
+
36
+ ## Environment variable reference (operator table)
37
+
38
+ Below is reference text from the published operator docs. Defaults and parsing logic are implemented primarily in **`python/app/main.py`**, **`python/app/route_policies.py`**, **`python/app/db_paths.py`**, and MCP modules—if this table and code disagree, trust the repository.
39
+
40
+ | Variable | Role |
41
+ |----------|------|
42
+ | `ANTHROPIC_API_KEY` | Omit when **`SKILLFORGE_ROUTER_MODE`** is **`host`** (unset default) unless **`SKILLFORGE_HAIKU_RERANK`** or other modes invoke Haiku (**`skillforge mcp config --with-anthropic`** documents **`auto` + placeholder**). |
43
+ | `SKILLFORGE_ROUTER_MODE` | **`host`** (default when unset) · **`auto`** (legacy Haiku-if-key) · **`embedding`** · **`full`**. Empty string (**`=`** alone) normalises to **`auto`**. |
44
+ | `SKILLFORGE_TRANSPORT` | Set **`mcp`** by **`skillforge mcp`** only. |
45
+ | `SKILLFORGE_ROUTER_LLM_BACKEND` | Standalone/non-MCP: **`openai_compatible`** uses **`OPENAI_API_BASE`**, etc. Ignored under MCP transport. |
46
+ | `OPENAI_API_BASE`, `SKILLFORGE_OPENAI_API_BASE`, `OPENAI_API_KEY`, `SKILLFORGE_OPENAI_ROUTER_MODEL` | OpenAI-compatible router defaults. |
47
+ | `SKILLFORGE_AGENT_MODEL`, `SKILLFORGE_AGENT_API_KEY`, `SKILLFORGE_AGENT_API_BASE` | **`skillforge agent`** (**also **`OPENAI_*`** fallbacks). || `SKILLFORGE_EMBED_MODEL`, `SKILLFORGE_ROUTER_MODEL` | Embedding + Anthropic router model ids. |
48
+ | `SKILLFORGE_TOP_K`, `SKILLFORGE_MAX_ACTIVE` | Shortlist size and simultaneous skills cap. |
49
+ | `SKILLFORGE_REROUTE_THRESHOLD` | Re-route sensitivity (Jaccard distance). |
50
+ | `SKILLFORGE_ROUTER_CONV_MAX_TURNS`, `SKILLFORGE_ROUTER_CONV_MSG_CHARS` | Conversation fused into embedding/hybrid routing query (**0** turns = prompt-only). |
51
+ | `SKILLFORGE_ROUTER_PROMPT_HISTORY_MSGS`, `SKILLFORGE_ROUTER_PROMPT_HISTORY_CHARS` | Conversation shown into router LLM prompts. |
52
+ | `SKILLFORGE_ROUTER_CATALOG_PREVIEW_CHARS` | Truncation in router catalog excerpts. |
53
+ | `SKILLFORGE_ROUTER_HYBRID`, `SKILLFORGE_ROUTER_HYBRID_ALPHA` | Hybrid sparse+dense (**`off`**, **`keyword`**, **`bm25`**). |
54
+ | `SKILLFORGE_HAIKU_RERANK`, `SKILLFORGE_HAIKU_RERANK_MAX`, `SKILLFORGE_HAIKU_RERANK_MODEL` | Optional rerank phase. |
55
+ | `SKILLFORGE_CONTEXT_MODE`, `SKILLFORGE_ROUTE_MAX_CHARS`, `SKILLFORGE_CHUNK_MAX_CHARS`, `SKILLFORGE_CHUNK_OVERLAP` | Chunking (**`skills`** + **`index`** reuse chunk tunables). |
56
+ | `SKILLFORGE_CONTEXT_FUSION`, `SKILLFORGE_CONTEXT_BUDGET_CHARS`, `SKILLFORGE_CONTEXT_MMR_LAMBDA`, `SKILLFORGE_FUSION_POOL_SKILL`, `SKILLFORGE_FUSION_POOL_PROJECT`, `SKILLFORGE_FUSION_FULL_BODY_PREVIEW_CHARS` | MMR fusion. |
57
+ | `SKILLFORGE_PROJECT_RAG_MAX_CHARS`, `SKILLFORGE_PROJECT_RAG_MAX_CHUNKS` | Project chunk retrieval caps. |
58
+ | `SKILLFORGE_PROJECT_NOTES_MAX_CHARS` | Cap **`project_notes`**. |
59
+ | `SKILLFORGE_REDACT_CONTEXT`, `SKILLFORGE_REDACT_HOME_IN_PATHS` | Redaction knobs. |
60
+ | `SKILLFORGE_MCP_USER_ID`, `SKILLFORGE_PROJECT_ROOT` | User scoping + DB/project resolution helpers. |
61
+ | `SKILLFORGE_MATERIALIZE_HOSTS` | Default host resolution when **`materialize_project`** **`hosts`** is **`auto`**. |
62
+ | `SKILLFORGE_ROUTE_POLICIES`, `SKILLFORGE_ROUTE_POLICIES_FILE` | Policies JSON (invalid JSON ⇒ stderr warning + empty rules — see Troubleshooting). |
63
+ | `SKILLFORGE_HOST_PICK_MAX`, `SKILLFORGE_HOST_PICK_LINE_CHARS` | Host-mode shortlist formatting. |
64
+ | `SKILLFORGE_ROUTE_AMBIGUITY_COS_MARGIN`, `SKILLFORGE_ROUTE_AMBIGUITY_ROUTE_MARGIN` | **`route_quality`** ambiguity heuristics. |
65
+ | `SKILLFORGE_ROUTE_AMBIGUITY_DISABLE` | Disable ambiguity tier heuristics. |
66
+ | `SKILLFORGE_PICK_DIVERSIFY`, `SKILLFORGE_PICK_MAX_PER_SOURCE` | Per-source pick thinning before policy merge. |
67
+ | Paths | **`SKILLFORGE_BUNDLED_SKILLS`**, **`SKILLFORGE_USER_SKILLS`**, **`SKILLFORGE_DB_PATH`** (normally set by CLI). |
68
+ | Skill manifests | **`SKILLFORGE_SKILL_MANIFEST_STRICT`** — invalid skills skipped at catalog load (**`skills lint`**). |
69
+ | Project index | **`SKILLFORGE_INDEX_MAX_FILE_BYTES`**, **`SKILLFORGE_INDEX_IGNORE_DIRS`**. |
70
+ | Hot reload | **`SKILLFORGE_SKILL_HOT_RELOAD`**, **`SKILLFORGE_WATCH_SKILLS_INTERVAL`**, **`SKILLFORGE_MCP_LIST_CHANGED`**. |
71
+ | Editor hooks install | **`SKILLFORGE_SKIP_CURSOR_SETUP`**, **`SKILLFORGE_SKIP_CLAUDE_CODE_SETUP`**, **`SKILLFORGE_CURSOR_GLOBAL_COMMAND`**, **`SKILLFORGE_CLAUDE_CODE_GLOBAL_COMMAND`**. |
72
+ | **`SKILLFORGE_MCP_SERVER_VERSION`** | Overrides **`capabilities`** / MCP reported semver (**`published_package_version()`**). |
73
+
74
+ ---
75
+
76
+ **Related:** **[MCP integration](mcp-integration.md)** · **[Troubleshooting](troubleshooting.md)**
@@ -0,0 +1,88 @@
1
+ # Getting started
2
+
3
+ ## 1. What you are installing
4
+
5
+ Skillforge ships as **[@heytherevibin/skillforge](https://www.npmjs.com/package/@heytherevibin/skillforge)**. The **CLI** (`skillforge`) is a **Node.js** shim that installs a **managed Python virtualenv** under **`~/.skillforge/venv`** and runs **`python -m app.…`** with a consistent environment (`PYTHONPATH`, skill dirs, DB path). You do **not** need to activate the venv by hand.
6
+
7
+ **Documentation matches this checkout’s release line:** **0.11.7** (**[`package.json` `version`](../package.json)**). On npm, **`npm view @heytherevibin/skillforge version`** is the registry truth—use **`@latest`** or **`npx -y`** to stay current.
8
+
9
+ ## 2. Prerequisites
10
+
11
+ | Requirement | Notes |
12
+ |-------------|-------|
13
+ | **Node.js ≥ 18** | Required to run **`npx`** / **`skillforge`**. |
14
+ | **Python ≥ 3.10** | Used on first **`skillforge install`** to create **`~/.skillforge/venv`**. |
15
+ | **Internet (first install)** | `pip install` from **`python/requirements.txt`**; embedding model download may occur once. |
16
+
17
+ ## 3. Try without installing globally
18
+
19
+ Run:
20
+
21
+ ```bash
22
+ npx --yes @heytherevibin/skillforge --help
23
+ ```
24
+
25
+ First use may invoke **`skillforge install`** implicitly when you hit a Python-backed command (`route`, `mcp`, …). Use **`skillforge install`** explicitly if you prefer a deliberate bootstrap.
26
+
27
+ ## 4. Global install (optional)
28
+
29
+ ```bash
30
+ npm install -g @heytherevibin/skillforge
31
+ skillforge --help
32
+ ```
33
+
34
+ Verify:
35
+
36
+ ```bash
37
+ skillforge config validate # exits 0 if ~/.skillforge/env is missing (optional file)
38
+ skillforge health --quick
39
+ ```
40
+
41
+ ## 5. Optional operator profile (~/.skillforge/env)
42
+
43
+ Recommended for stable API keys and tunables:
44
+
45
+ ```bash
46
+ skillforge config init # ~/.skillforge/env template (chmod 600 on Unix where supported)
47
+ skillforge config validate # lint after edits
48
+ ```
49
+
50
+ See **[Environment & configuration](environment-and-configuration.md)** for merge order versus MCP **`entry.env`**.
51
+
52
+ ## 6. Wire MCP (Cursor, Claude Desktop, …)
53
+
54
+ Emit a snippet (stdout is JSON):
55
+
56
+ ```bash
57
+ skillforge mcp config
58
+ # Add --local for a git checkout · --with-anthropic · --with-env (see MCP guide)
59
+ ```
60
+
61
+ **Instructions:**
62
+
63
+ 1. Copy the **`mcpServers.skillforge`** object into your host config (example: **`~/.cursor/mcp.json`**).
64
+ 2. **Fully restart** the MCP host application after edits.
65
+ 3. Confirm tools appear — start a session with **`capabilities`** once (bundle lists tool names + **router_snapshot**).
66
+
67
+ **Critical:** MCP uses **stdout** only for JSON-RPC. Skillforge logs to **stderr**.
68
+
69
+ ## 7. Understand default routing (**host**, two-step)
70
+
71
+ If **`SKILLFORGE_ROUTER_MODE`** is unset, Skillforge defaults to **`host`**:
72
+
73
+ 1. First **`route_skills`** → numbered **shortlist** (no **`picked_names`**).
74
+ 2. Second call → same **`prompt`** plus **`picked_names`** (`id1,id2` or enumerated picks from the listing).
75
+
76
+ CLI mirrors this: **`skillforge route`** twice, or **`skillforge route -i`** on a TTY after the shortlist.
77
+
78
+ Details: **[MCP integration](mcp-integration.md)**.
79
+
80
+ ## 8. Operational habits
81
+
82
+ ```bash
83
+ skillforge tips # cheatsheet on stdout
84
+ skillforge events --watch # routing / feedback tail
85
+ skillforge replay --limit=20 [--json]
86
+ ```
87
+
88
+ Further reading:** [CLI reference](cli-reference.md)** · **[Architecture & data](architecture-and-data.md)**.
@@ -0,0 +1,75 @@
1
+ # MCP integration
2
+
3
+ Skillforge speaks **stdio MCP JSON-RPC**. Tool definitions (**name**, **`inputSchema`**, descriptions) ship from **`python/app/mcp_server.py`**.
4
+
5
+ ## stdout discipline
6
+
7
+ Anything that writes stray bytes to **stdout** breaks MCP hosts. **`skillforge mcp`** never logs JSON-RPC payloads to stdout; logs go to **stderr**.
8
+
9
+ ## Default routing (**host**, Skillforge ≥ 0.11.0 — use **≥ 0.11.7** if you rely on **`route-eval`** / full router init stability)
10
+
11
+ Stable **`Router`** embedding + **`_by_name`** initialization ship in **[0.11.7](../CHANGELOG.md)**; stay on **`0.11.7`** or newer for CI-aligned routing.
12
+
13
+ When **`SKILLFORGE_ROUTER_MODE`** is **unset**:
14
+
15
+ 1. First **`route_skills`** call → **numbered shortlist** (no **`picked_names`** yet).
16
+ 2. Second call → same **`prompt`** plus **`picked_names`** (**`id1,id2`** or enumerated picks returned in the listing).
17
+
18
+ **`auto`** (legacy behavior) ⇒ **`SKILLFORGE_ROUTER_MODE=auto`** or empty string (**`=`** alone). **`skillforge mcp config --with-anthropic`** emits **`SKILLFORGE_ROUTER_MODE=auto`** **and** an **`ANTHROPIC_API_KEY`** placeholder together so placeholders are not meaningless under **`host`** default routing.
19
+
20
+ ### Mode reference
21
+
22
+ | **`SKILLFORGE_ROUTER_MODE`** | Behaviour (high level) |
23
+ |------------------------------|------------------------|
24
+ | **unset → treats as `host`** | Host-driven two-step routing. |
25
+ | **`host`** | Explicit two-step **shortlist → picked_names**. |
26
+ | **`auto`** | Embedding-first without key; in-process Haiku when **`ANTHROPIC_API_KEY`** set. |
27
+ | **`embedding`** | Embedding-only picks / overrides via **`picked_names`**. |
28
+ | **`full`** | Haiku-heavy paths (falls back gracefully if key absent). |
29
+
30
+ ## Where to declare **`env`**
31
+
32
+ | Host | Typical manifest |
33
+ |------|------------------|
34
+ | **Cursor / VS Code MCP** | **`~/.cursor/mcp.json`** (workspace overrides possible) |
35
+ | **Claude Desktop** | **`claude_desktop_config.json`** |
36
+
37
+ Restart the MCP host whenever **`env`** or package version changes.
38
+
39
+ ## **`skillforge mcp config`**
40
+
41
+ ```bash
42
+ skillforge mcp config
43
+ skillforge mcp config --local
44
+ skillforge mcp config --with-anthropic # SKILLFORGE_ROUTER_MODE=auto + key placeholder env
45
+ skillforge mcp config --with-env # SKILLFORGE_ROUTER_MODE=host scaffold in entry.env
46
+ ```
47
+
48
+ **Note:** Passing **`--with-anthropic`** **replaces** the emitted **`env`** object (**`--with-env`** loses); merge manually if you need both patterns.
49
+
50
+ ## MCP tools (quick map)
51
+
52
+ Schemas + parameter docs: **`python/app/mcp_server.py`**.
53
+
54
+ | Tool | Responsibility |
55
+ |------|----------------|
56
+ | **`route_skills`** | Embed → shortlist → policies → context assembly; honours **`picked_names`**. |
57
+ | **`search_skills`** | Embedding shortlist for arbitrary query text. |
58
+ | **`explain_route`** | Routing diagnostics (shortlist audit) without heavyweight session effects. |
59
+ | **`get_skill`**, **`list_skills`** | Deterministic SKILL.md retrieval + catalog summaries. |
60
+ | **`skill_feedback`**, **`skill_referenced`**, **`disable_skill`** | Learning loop inputs. |
61
+ | **`materialize_project`** | Opinionated scaffolding for **`cursor`**, **`claude_code`**, or **`both`** with **`hosts`**: **`auto`**, **`both`**, **`cursor`**, **`claude_code`**. |
62
+ | **`skillforge_bootstrap`** | Composite route + scaffold helper (mind **`host`** shortlist caveat). |
63
+ | **`capabilities`** | Session bootstrap bundle (**semver**, MCP schema marker, **`mcp_tools`**, **`user_env_profile`** commands, **`router_snapshot`**). |
64
+ | **`get_router_status`** | Diagnostics snapshot (modes, hybrids, rerank hints). |
65
+ | **`project_index_status`** | Project **`project_chunks`** stats + index metadata (**`project_root`**). |
66
+ | **`weights_snapshot`** | Portable learned weights excerpt (parity with **`weights export`** CLI). |
67
+ | **`events_recent`** | Recent SQLite **`events`** rows (**bounded** **`limit`**). |
68
+
69
+ ## Response **`_meta`**
70
+
71
+ Structured metadata is anchored by **`MCP_RESPONSE_SCHEMA_VERSION`** in **`app/mcp_contract.py`**. Highlights include **`route_quality`**, **`routing_overlay`**, **`feedback_effect`**, budgeting / fusion stats, **`host_pick_*`** artefacts when **`host`** mode emits shortlists.
72
+
73
+ ---
74
+
75
+ **Related:** **[Environment & configuration](environment-and-configuration.md)** · **[CLI reference](cli-reference.md)**
@@ -0,0 +1,50 @@
1
+ # Troubleshooting
2
+
3
+ ## MCP tools never appear / connection dies immediately
4
+
5
+ **Check:**
6
+
7
+ 1. **Restart** Cursor / Claude / VS Code after editing **`mcp.json`** (**hot reload seldom enough**).
8
+ 2. Confirm **nothing else** wraps stdout (debug prints, rogue **`console.log`** forks); **`skillforge mcp`** forbids chatter on stdout.
9
+ 3. Bump package version (**`npm view @heytherevibin/skillforge version`** vs local git tag) — mismatched semver often means stale binary.
10
+ 4. Run **`skillforge health --quick`** locally to prove venv + skill tree viability.
11
+
12
+ ## **`skillforge agent` Missing `openai`**
13
+
14
+ Upgrade managed deps:
15
+
16
+ ```bash
17
+ skillforge install
18
+ ```
19
+
20
+ (or manually **`pip install -r`** against **`~/.skillforge/venv`**).
21
+
22
+ Validate env profile too:
23
+
24
+ ```bash
25
+ skillforge config validate
26
+ ```
27
+
28
+ ## Route policies silently empty
29
+
30
+ Malformed **`SKILLFORGE_ROUTE_POLICIES`** JSON or unreadable **`SKILLFORGE_ROUTE_POLICIES_FILE`** now prints **`[skillforge] …`** on **stderr** and falls back to **no regex rules**.
31
+
32
+ **Instruction:** **`skillforge explain_route`** / inspect logs · fix JSON · re-run.
33
+
34
+ ## NPM publish / **`EOTP`** in GitHub Actions
35
+
36
+ Release workflow requires a **Granular NPM token with Bypass 2FA** — see **[RELEASING.md](../RELEASING.md)**.
37
+
38
+ Re-run **`Skillforge release`** after rotating **`NPM_TOKEN`**.
39
+
40
+ ## Tag mismatches (**`release.yml`** guard)
41
+
42
+ **`vX.Y.Z` tag must equal `package.json`** **`version`**. Bump semver, push **`main`**, then re-tag (**`git tag -d`** / **`git push :refs/tags/...`** if you need to re-cut).
43
+
44
+ ---
45
+
46
+ Need more?
47
+
48
+ - Diagnostics bundle: MCP **`capabilities`**, **`get_router_status`**.
49
+ - Security expectations: **[SECURITY.md](../SECURITY.md)**.
50
+ - Broader changelog context: **[CHANGELOG.md](../CHANGELOG.md)**.
@@ -2,8 +2,6 @@
2
2
  description: Run Skillforge MCP (route_skills) to load routed SKILL.md context. Use when the user invokes /skillforge, asks for Skillforge, or needs catalog skills for the repo.
3
3
  ---
4
4
 
5
- <!-- skillforge-managed vPACKAGE_VERSION — remove this line to stop auto-updates from `skillforge install` -->
6
-
7
5
  # Skillforge — route SKILL.md context (MCP)
8
6
 
9
7
  Global **`/skillforge`** for **Claude Code** (same mechanism as `.claude/commands/*.md`). Configure the **skillforge** MCP server (see **`skillforge mcp config`**, project **`.mcp.json`**, or `claude mcp`). Optional: **`skillforge health`**, **`skillforge route-eval`**, **`skillforge weights export|import`**. MCP **`_meta.feedback_effect`** explains learned-ranking bias for picked skills when present.
@@ -12,8 +10,10 @@ Global **`/skillforge`** for **Claude Code** (same mechanism as `.claude/command
12
10
 
13
11
  1. **`route_skills`** (MCP): pass **`project_root`** as the **current project root** (absolute path) so SQLite lives in **`<project>/.skillforge/`**. Pass the **user's task** as **`prompt`**. Reuse **`session_id`** across turns when the MCP returns it.
14
12
 
15
- - **`SKILLFORGE_ROUTER_MODE=host`**: first call **without** **`picked_names`** (shortlist only); second call **with** **`picked_names`**.
13
+ - **`host`** routing (default when **`SKILLFORGE_ROUTER_MODE`** is unset): first call **without** **`picked_names`** (shortlist only); second call **with** **`picked_names`**.
16
14
 
17
15
  2. **Use the returned skill text** in your answer.
18
16
 
19
17
  3. **Per-repo list:** run **`materialize_project`** after **`route_skills`** to refresh **`.claude/commands/skillforge.md`** and **`CLAUDE.md`**.
18
+
19
+ <!-- skillforge-managed vPACKAGE_VERSION — remove this line to stop auto-updates from `skillforge install` -->
@@ -1,4 +1,6 @@
1
- <!-- skillforge-managed vPACKAGE_VERSION — remove this line to stop auto-updates from `skillforge install` -->
1
+ ---
2
+ description: Route SKILL.md context via Skillforge MCP (/skillforge). Configure the skillforge server in ~/.cursor/mcp.json — run skillforge mcp config for JSON.
3
+ ---
2
4
 
3
5
  # Skillforge — route SKILL.md context (MCP)
4
6
 
@@ -8,9 +10,11 @@ Global **`/skillforge`** command. Use the **skillforge** MCP server (configure i
8
10
 
9
11
  1. **`route_skills`** (MCP): pass **`project_root`** as the **current workspace root** (absolute path) so SQLite lives in **`<workspace>/.skillforge/`**. Pass the **user's task** as **`prompt`**. Reuse **`session_id`** across turns when the tool returns one.
10
12
 
11
- - **`SKILLFORGE_ROUTER_MODE=host`**: call once **without** **`picked_names`** (shortlist only); then call again with **`picked_names`** (exact catalog ids) to load skill context.
13
+ - **`host`** routing (default when **`SKILLFORGE_ROUTER_MODE`** is unset): call once **without** **`picked_names`** (shortlist only); then call again with **`picked_names`** (exact catalog ids) to load skill context.
12
14
  - Optional: **`conversation`** when recent turns should influence routing.
13
15
 
14
16
  2. **Use the returned skill text** in your answer.
15
17
 
16
18
  3. **Project-specific lists:** run **`materialize_project`** in a repo (after **`route_skills`**) to write **`.cursor/commands/skillforge.md`**, **`.cursor/rules/skillforge.mdc`**, and **`docs/SKILLFORGE-PRD.md`** with the latest **`skill_names`**.
19
+
20
+ <!-- skillforge-managed vPACKAGE_VERSION — remove this line to stop auto-updates from `skillforge install` -->
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Parses Skillforge ~/.skillforge/env (dotenv-style) for merge + validation.
3
+ * Shell-free: no ${VAR} expansion.
4
+ */
5
+ 'use strict';
6
+
7
+ const fs = require('fs');
8
+
9
+ /**
10
+ * @typedef {{ level: 'error'|'warning', line: number, message: string }} UserEnvIssue
11
+ */
12
+ /**
13
+ * @param {string} raw
14
+ * @returns {{ vars: Record<string, string>, issues: UserEnvIssue[] }}
15
+ */
16
+ function parseUserEnvProfileText(raw) {
17
+ /** @type {Record<string, string>} */
18
+ const vars = {};
19
+ /** @type {UserEnvIssue[]} */
20
+ const issues = [];
21
+
22
+ /** @type {Set<string>} */
23
+ const declared = new Set();
24
+
25
+ const lines = raw.split(/\r?\n/);
26
+ const keyOk = /^[A-Za-z_][A-Za-z0-9_]*$/;
27
+
28
+ for (let i = 0; i < lines.length; i++) {
29
+ const lineNum = i + 1;
30
+ const rawLine = lines[i];
31
+ const t = rawLine.trim();
32
+ if (!t || t.startsWith('#')) {
33
+ continue;
34
+ }
35
+
36
+ let s = t;
37
+ if (s.startsWith('export ')) {
38
+ s = s.slice(7).trim();
39
+ }
40
+
41
+ const ix = s.indexOf('=');
42
+ if (ix < 0) {
43
+ issues.push({
44
+ level: 'warning',
45
+ line: lineNum,
46
+ message:
47
+ 'line has no "=" — expected KEY=value (dotenv syntax); skipped when loading',
48
+ });
49
+ continue;
50
+ }
51
+
52
+ if (ix === 0) {
53
+ issues.push({
54
+ level: 'error',
55
+ line: lineNum,
56
+ message: 'missing variable name before "="',
57
+ });
58
+ continue;
59
+ }
60
+
61
+ const k = s.slice(0, ix).trim();
62
+ let v = s.slice(ix + 1).trim();
63
+ if (v.startsWith('"')) {
64
+ if (v.length < 2 || !v.endsWith('"')) {
65
+ issues.push({
66
+ level: 'warning',
67
+ line: lineNum,
68
+ message: 'value starts with " but closing quote missing or malformed',
69
+ });
70
+ }
71
+ } else if (v.startsWith("'")) {
72
+ if (v.length < 2 || !v.endsWith("'")) {
73
+ issues.push({
74
+ level: 'warning',
75
+ line: lineNum,
76
+ message: "value starts with ' but closing quote missing or malformed",
77
+ });
78
+ }
79
+ }
80
+
81
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
82
+ v = v.slice(1, -1);
83
+ }
84
+
85
+ if (!keyOk.test(k)) {
86
+ issues.push({
87
+ level: 'error',
88
+ line: lineNum,
89
+ message:
90
+ `invalid KEY "${k}" — use ASCII letters, digits, underscore (must match [A-Za-z_][A-Za-z0-9_]*)`,
91
+ });
92
+ continue;
93
+ }
94
+
95
+ if (declared.has(k)) {
96
+ issues.push({
97
+ level: 'warning',
98
+ line: lineNum,
99
+ message: `duplicate "${k}" — last assignment wins`,
100
+ });
101
+ }
102
+ declared.add(k);
103
+ vars[k] = v;
104
+ }
105
+
106
+ return { vars, issues };
107
+ }
108
+
109
+ /**
110
+ * Read and parse ~/.skillforge/env. Missing file ⇒ empty vars & issues only if caller treats missing.
111
+ * @param {string} absPath
112
+ * @returns {{
113
+ * vars: Record<string, string>,
114
+ * issues: UserEnvIssue[],
115
+ * missingFile?: boolean,
116
+ * readErr?: boolean,
117
+ * }}
118
+ */
119
+ function readUserEnvProfileFromFile(absPath) {
120
+ if (!fs.existsSync(absPath)) {
121
+ return { vars: {}, issues: [], missingFile: true };
122
+ }
123
+ let raw;
124
+ try {
125
+ raw = fs.readFileSync(absPath, 'utf8');
126
+ } catch (e) {
127
+ const msg = /** @type {Error} */ (e).message;
128
+ return {
129
+ vars: {},
130
+ issues: [{ level: 'error', line: 0, message: `cannot read ${absPath}: ${msg}` }],
131
+ readErr: true,
132
+ };
133
+ }
134
+ const { vars, issues } = parseUserEnvProfileText(raw);
135
+ return { vars, issues };
136
+ }
137
+
138
+ module.exports = {
139
+ parseUserEnvProfileText,
140
+ readUserEnvProfileFromFile,
141
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heytherevibin/skillforge",
3
- "version": "0.10.1",
3
+ "version": "0.11.7",
4
4
  "description": "SKILL.md orchestration for AI agents: MCP-first routing with local embeddings, optional LLM stages, project RAG, policy overlays, portable learning state, and auditable telemetry.",
5
5
  "keywords": [
6
6
  "claude",
@@ -22,6 +22,7 @@
22
22
  "python/",
23
23
  "skills/",
24
24
  "README.md",
25
+ "docs/",
25
26
  "CHANGELOG.md",
26
27
  "STRATEGY.md",
27
28
  "LICENSE",
@@ -34,6 +35,6 @@
34
35
  "node": ">=18.0.0"
35
36
  },
36
37
  "scripts": {
37
- "test": "node bin/cli.js --help"
38
+ "test": "node --test ci/test-user-env-profile.cjs && node bin/cli.js --help && node bin/cli.js config validate"
38
39
  }
39
40
  }