@heytherevibin/skillforge 0.8.0 → 0.10.1

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/RELEASING.md CHANGED
@@ -5,7 +5,7 @@
5
5
  | Workflow | File | When it runs |
6
6
  |----------|------|----------------|
7
7
  | **CI** | [.github/workflows/ci.yml](.github/workflows/ci.yml) | Every **push** and **pull request** to `main`; also **`workflow_dispatch`** (run manually from the Actions tab) |
8
- | **Skillforge release** | [.github/workflows/release.yml](.github/workflows/release.yml) | When a **`v*`** tag is **pushed** to the repository (e.g. `v0.2.1`) |
8
+ | **Skillforge release** | [.github/workflows/release.yml](.github/workflows/release.yml) | When a **`v*`** tag is **pushed** to the repository (e.g. `v0.10.1`) |
9
9
 
10
10
  In the GitHub UI, open **Actions** and look for **CI** and **Skillforge release** (not the older “publish to npm” name).
11
11
 
@@ -28,14 +28,14 @@ You need the **`NPM_TOKEN`** repository secret.
28
28
 
29
29
  Create one at [npm → Access Tokens](https://www.npmjs.com/settings/~/tokens) (**Generate New Token** → **Granular Access Token**). Optionally explore [**trusted publishing** (OIDC)](https://docs.npmjs.com/trusted-publishers/) later to avoid long-lived tokens.
30
30
 
31
- 1. On `main`, set **`version`** in `package.json` to the version you are releasing (must be **unused** on npm — e.g. `0.2.1`).
32
- 2. Match **MCP** `serverInfo.version` in `python/app/mcp_server.py` and add a **`CHANGELOG.md`** section for that version.
31
+ 1. On `main`, set **`version`** in `package.json` to the version you are releasing (must be **unused** on npm — e.g. `0.10.1`).
32
+ 2. Match **MCP** `serverInfo.version` in `python/app/mcp_server.py` (must equal `package.json`) and add a **`CHANGELOG.md`** section for that version.
33
33
  3. Commit and **`git push origin main`**. Wait for **CI** to pass.
34
34
  4. Create a tag whose name is **`v` + that exact version**:
35
- `git tag v0.2.1 && git push origin v0.2.1`
35
+ `git tag v0.10.1 && git push origin v0.10.1`
36
36
  5. Open **Actions → Skillforge release**. The job will **fail the version check** if the tag does not match `package.json`.
37
37
  6. Confirm on npm: `npm view @heytherevibin/skillforge version`
38
- Confirm the **GitHub Release** exists with title **`Skillforge <tag>`** (e.g. **`Skillforge v0.2.1`**) and the `.tgz` asset.
38
+ Confirm the **GitHub Release** exists with title **`Skillforge <tag>`** (e.g. **`Skillforge v0.10.1`**) and the `.tgz` asset.
39
39
 
40
40
  Scoped packages require a **public** publish; the workflow already runs `npm publish --access public`.
41
41
 
@@ -67,6 +67,10 @@ Typical causes: the tag was created before **Skillforge release** existed on `ma
67
67
  1. Bump `version` in `package.json`, commit, push `main`.
68
68
  2. Push a **new** tag: `v` + new version.
69
69
 
70
+ ## Bundled skills gate
71
+
72
+ The **minimum** number of **`skills/**/**/SKILL.md`** files required in CI is **`ci/bundle-gate.json`** → **`minSkillMdFiles`**. Change that value when your distribution policy for the vendored catalog changes (do not edit the number inline in **`ci.yml`**).
73
+
70
74
  ## Local sanity checks (before push)
71
75
 
72
76
  ```bash
@@ -74,10 +78,18 @@ node --check bin/cli.js && node --check lib/packs.js
74
78
  npm test
75
79
  ```
76
80
 
77
- Python (syntax only):
81
+ Python (syntax only)—the **authoritative** module list is in **`.github/workflows/ci.yml`** (step “Check Python syntax”). Example:
78
82
 
79
83
  ```bash
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 python/app/route_policies.py python/app/routing_signals.py; do python3 -m py_compile "$f"; done
84
+ for f in \
85
+ python/app/main.py python/app/mcp_server.py python/app/events_cli.py python/app/materialize.py \
86
+ python/app/db_paths.py python/app/route_cli.py python/app/mcp_contract.py python/app/chunking.py \
87
+ python/app/project_index.py python/app/index_cli.py python/app/context_fusion.py python/app/redaction.py \
88
+ python/app/route_policies.py python/app/routing_signals.py python/app/route_quality.py \
89
+ python/app/route_eval_harness.py python/app/eval_cli.py python/app/health_cli.py \
90
+ python/app/feedback_meta.py python/app/weights_cli.py; do
91
+ python3 -m py_compile "$f"
92
+ done
81
93
  ```
82
94
 
83
95
  ## Troubleshooting: `EOTP` / one-time password in CI
package/SECURITY.md CHANGED
@@ -2,30 +2,78 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
- Security fixes are applied to the **latest** published minor release on the `main` branch. Older tags may not receive backports; upgrade to the current release when possible.
5
+ Security fixes are applied to the **latest published release** on the default branch (**`main`**). Older release lines may not receive backports; upgrade to the current release when advisories apply.
6
6
 
7
7
  ## Reporting a vulnerability
8
8
 
9
- Please **do not** open a public GitHub issue for undisclosed security vulnerabilities.
9
+ Please **do not** open a **public** GitHub issue for undisclosed security vulnerabilities.
10
10
 
11
11
  **Preferred:** use [GitHub Security Advisories](https://github.com/heytherevibin/skillforge/security/advisories/new) for this repository (if you have access).
12
12
 
13
- **Alternative:** open a **private** report via GitHub **Security → Report a vulnerability** on the repository, or contact the maintainer through a channel they publish on their GitHub profile.
13
+ **Alternative:** **Security → Report a vulnerability** on the repository, or contact maintainers through channels published on their GitHub profiles.
14
14
 
15
15
  Include:
16
16
 
17
- - Description of the issue and impact
18
- - Steps to reproduce (if known)
19
- - Affected version or commit (if known)
17
+ - Description of the issue and assessed impact
18
+ - Steps to reproduce (if known)
19
+ - Affected **version**, **tag**, or **commit** (if known)
20
20
 
21
- We aim to acknowledge valid reports within a few business days.
21
+ We aim to **acknowledge** valid reports within a few **business days**. Resolution timelines depend on severity and validation effort.
22
22
 
23
- ## npm and supply chain
23
+ ---
24
24
 
25
- - For **`NPM_TOKEN`** in GitHub Actions, use a [**granular access token**](https://docs.npmjs.com/about-access-tokens) with **read and write** to your scope and **Bypass 2FA** enabled, so **`npm publish`** does not return **`EOTP`**. Legacy automation/classic tokens are no longer available on npm as of 2025.
26
- - Keep **2FA** enabled on the npm account that owns the `@heytherevibin` scope.
27
- - Prefer pinning action versions or reviewing Dependabot PRs before merge.
25
+ ## Threat model (summary)
28
26
 
29
- ## Runtime security
27
+ | Component | Exposure |
28
+ |-----------|-----------|
29
+ | **MCP stdio server** | Runs locally; accepts JSON-RPC from the parent **MCP host** process. Treat the host as part of your trust boundary. |
30
+ | **CLI** | Spawns Python modules with environment inherited from the shell; avoid untrusted **`PATH`** or wrapper scripts. |
31
+ | **SQLite databases** | Contain prompts (redacted where configured), events, embeddings metadata, and learning weights. Protect filesystem permissions and backups. |
32
+ | **Optional Anthropic API** | Outbound HTTPS when **`ANTHROPIC_API_KEY`** is set; prompts and router context may leave the machine per Anthropic’s policies. |
30
33
 
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.
34
+ Skillforge does **not** implement network listening for the published OSS package (no HTTP API in current releases).
35
+
36
+ ---
37
+
38
+ ## Data handling & privacy
39
+
40
+ - **Redaction** (`SKILLFORGE_REDACT_*`) is **pattern-based** and **best-effort**—not a substitute for data-classification or DLP controls.
41
+ - **Project routing notes** (`project_notes` / aliases in policies) are **prefixed** to the internal routing query only when **`project_root`** is set—reducing accidental global application from org-wide env JSON.
42
+ - Operators should assume **route events** and **`_meta`** may contain **sensitive path fragments** unless redaction is enabled and effective for their workload.
43
+
44
+ ---
45
+
46
+ ## Secrets & configuration
47
+
48
+ - **Never commit** `ANTHROPIC_API_KEY`, npm **`NPM_TOKEN`**, or workspace tokens.
49
+ - Prefer **OS secret stores**, **CI secrets**, or MCP host **env injection**.
50
+ - Review **`policies.json`** and **`SKILLFORGE_ROUTE_POLICIES`** for **prompt injection** risk: notes influence embeddings for the declared project only, but can still steer retrieval.
51
+
52
+ ---
53
+
54
+ ## Supply chain & distribution
55
+
56
+ ### npm
57
+
58
+ - Use **granular access tokens** with the minimum scope required; enable **Bypass 2FA** only on dedicated automation tokens when CI publishing requires it (see [RELEASING.md](RELEASING.md)).
59
+ - Keep **2FA** enabled on maintainer accounts that own the **`@heytherevibin`** scope.
60
+ - Review **Dependabot** / dependency updates before merge for typosquatting and breaking changes.
61
+
62
+ ### CI
63
+
64
+ - Workflows run with **`contents: read`** on the default **CI** job; verify **`release.yml`** permissions when extending publish steps.
65
+ - Prefer **pinned** major versions of third-party Actions or follow your org’s pinning policy.
66
+
67
+ ---
68
+
69
+ ## Runtime hardening (recommended)
70
+
71
+ - Run Skillforge on **managed workstations** or **locked-down CI agents** appropriate to your data classification.
72
+ - Separate **production** and **development** orchestrator databases if weights or events must not mix.
73
+ - Periodically run **`skillforge health`** and **`skillforge route-eval`** after upgrades to validate embeddings and bundled catalog integrity in your environment.
74
+
75
+ ---
76
+
77
+ ## Coordinated disclosure
78
+
79
+ If you are preparing **public** research or a conference talk that names Skillforge, please contact maintainers **before** the embargo date so fixes or guidance can ship alongside disclosures when appropriate.
package/STRATEGY.md CHANGED
@@ -1,24 +1,50 @@
1
- # Skillforge strategy (Cursor-first)
1
+ # Skillforge — product strategy
2
2
 
3
- ## Product intent
3
+ ## Mission
4
4
 
5
- Skillforge is an **npm-packaged skill orchestrator**: it routes tasks to a small set of **SKILL.md** documents (embeddings + optional Haiku) and returns their bodies for agent context. **Per-project** learning and telemetry live under **`<workspace>/.skillforge/`** when **`project_root`** (or **`SKILLFORGE_PROJECT_ROOT`**) is set; otherwise the global DB under **`~/.skillforge/data/`** is used.
5
+ Skillforge is a **local, MCP-first orchestration layer** that selects a **small, relevant** subset of **`SKILL.md`** (and optional indexed project) context for each user task—so agent hosts can stay **grounded** without loading entire catalogs.
6
6
 
7
- ## Surfaces (today)
7
+ ## Principles
8
8
 
9
- - **MCP** (`skillforge mcp`): primary — `route_skills`, `list_skills`, feedback tools, `materialize_project`, `skillforge_bootstrap`.
10
- - **Terminal**: `skillforge events --watch` with optional **`--project-root`**; `skillforge route`, `skillforge index`.
9
+ | Principle | Implication |
10
+ |-----------|-------------|
11
+ | **Local-first** | Default data plane is **SQLite** and **stdio MCP** on the operator’s machine. |
12
+ | **Explicit trust** | Optional **LLM** stages run only when keys and modes allow; **host** mode delegates final skill choice to the MCP client. |
13
+ | **Observable** | Versioned **`_meta`**, route **events**, and operator CLIs (**`events`**, **`health`**, **`route-eval`**) support enterprise debugging and CI gates. |
14
+ | **Portable learning** | Weights and feedback are **exportable**—suitable for backup, migration, and controlled restore. |
15
+ | **Governable** | **Regex policies** and **project overlays** (exclude, boost, notes) let platform teams steer routing without forking the catalog. |
11
16
 
12
- ## Cursor reality
17
+ ## Primary surfaces
13
18
 
14
- Native **`/skillforge`** in editor chat is **not** registered by this npm package. The practical pattern is **Cursor rules** (e.g. materialized **`skillforge.mdc`**) instructing the agent to call MCP tools; users may *say* `/skillforge` as shorthand.
19
+ | Surface | Role |
20
+ |---------|------|
21
+ | **MCP (`skillforge mcp`)** | Integration for Claude, Cursor, Claude Code, and other JSON-RPC MCP hosts. |
22
+ | **CLI** | Parity routing, indexing, observability, preflight, eval harness, weights I/O. |
15
23
 
16
- ## Near-term backlog
24
+ ## Integration matrix
17
25
 
18
- - Shared **`orchestrate()`** API for MCP + CLI parity.
19
- - Tests: MCP handshake + `resolve_orchestrator_db` behavior.
26
+ | Host capability | Skillforge pattern |
27
+ |-----------------|---------------------|
28
+ | **Project workspace** | Pass **`project_root`** so state and optional RAG live under **`.skillforge/`**. |
29
+ | **Strict context budgets** | Tune **`SKILLFORGE_CONTEXT_*`**, **`SKILLFORGE_ROUTE_MAX_CHARS`**, and project RAG caps. |
30
+ | **No LLM keys on router** | Use **`SKILLFORGE_ROUTER_MODE=embedding`** (or omit **`ANTHROPIC_API_KEY`** in auto mode). |
31
+ | **Human-in-the-loop picks** | **`SKILLFORGE_ROUTER_MODE=host`**: shortlist first, then **`picked_names`**. |
32
+ | **Org policy** | Central **`SKILLFORGE_ROUTE_POLICIES`** / file + per-repo **`policies.json`** with overlay keys. |
20
33
 
21
- ## Non-goals (v1)
34
+ ## Near-term themes
22
35
 
23
- - Auto-scanning every AI agent installed on the host.
24
- - VS Code extension (unless added as a separate track).
36
+ - Continue tightening **parity** between MCP tools and CLI (**routing**, **meta**, **policies**).
37
+ - Expand **fixture library** for **`route-eval`** as catalog and hybrid modes evolve.
38
+ - Optional: richer **enterprise** packaging (e.g. signed SBOM, pinned base images) as downstream consumers require—tracked outside this doc when scope is agreed.
39
+
40
+ ## Non-goals
41
+
42
+ - Replacing the host model or owning end-user billing for conversations.
43
+ - Implicit network egress except **optional** model APIs (Anthropic) and **pack** git clones invoked explicitly by operators.
44
+ - A hosted multi-tenant SaaS as part of the core OSS package.
45
+
46
+ ## References
47
+
48
+ - **Operator documentation:** [README.md](README.md)
49
+ - **Security posture:** [SECURITY.md](SECURITY.md)
50
+ - **Shipping checklist:** [RELEASING.md](RELEASING.md)
package/bin/cli.js CHANGED
@@ -8,7 +8,12 @@
8
8
  * skillforge events [--watch] [--limit=N] Print SQLite routing events
9
9
  * skillforge route [words…] [--prompt=…] Same routing as MCP route_skills (terminal)
10
10
  * skillforge index --project-root=… Chunk/embed repo files for project RAG
11
- * skillforge install One-time Python venv + deps
11
+ * skillforge health [--quick] [--json] Preflight: paths, catalog, optional router load
12
+ * skillforge route-eval --fixture=… Embedding-only regression cases (CI-friendly)
13
+ * skillforge weights export|import Snapshot learned weights (JSON)
14
+ * skillforge install One-time Python venv + deps (+ Cursor /skillforge when detected)
15
+ * skillforge hosts init [--force] Install global /skillforge for Cursor + Claude Code (no Python setup)
16
+ * skillforge cursor init [--force] Same as hosts init (alias)
12
17
  * skillforge skills … / pack … / reset
13
18
  */
14
19
 
@@ -19,7 +24,9 @@ const os = require('os');
19
24
  const packs = require('../lib/packs');
20
25
 
21
26
  const PKG_ROOT = path.resolve(__dirname, '..');
22
- const NPM_PKG_NAME = require(path.join(PKG_ROOT, 'package.json')).name;
27
+ const PKG = require(path.join(PKG_ROOT, 'package.json'));
28
+ const NPM_PKG_NAME = PKG.name;
29
+ const PKG_VERSION = PKG.version || '0.0.0';
23
30
  const CONFIG_DIR = path.join(os.homedir(), '.skillforge');
24
31
  const VENV_DIR = path.join(CONFIG_DIR, 'venv');
25
32
  const DATA_DIR = path.join(CONFIG_DIR, 'data');
@@ -143,7 +150,23 @@ function runSetup() {
143
150
  }
144
151
  ok('Python dependencies installed');
145
152
 
146
- // 4. Mark setup complete
153
+ // 4. Cursor / MCP host hooks (no Python required)
154
+ try {
155
+ const hostSetup = require('../lib/host-setup');
156
+ hostSetup.reportHostsAndInstallAgentCommands({
157
+ force: process.argv.includes('--force-cursor'),
158
+ pkgRoot: PKG_ROOT,
159
+ pkgVersion: PKG_VERSION,
160
+ log,
161
+ ok,
162
+ err,
163
+ dim: s => log(c.dim(s)),
164
+ });
165
+ } catch (e) {
166
+ err(`Host integration failed: ${/** @type {Error} */ (e).message}`);
167
+ }
168
+
169
+ // 5. Mark setup complete
147
170
  fs.writeFileSync(SETUP_MARKER, new Date().toISOString());
148
171
  ok('Setup complete\n');
149
172
  }
@@ -237,6 +260,41 @@ function runIndexCmd() {
237
260
  proc.on('exit', (code) => process.exit(code ?? 0));
238
261
  }
239
262
 
263
+ function runHealthCmd() {
264
+ setupIfNeeded();
265
+ const sub = args.slice(1);
266
+ const proc = spawn(venvPython(), ['-m', 'app.health_cli', ...sub], {
267
+ stdio: 'inherit',
268
+ env: buildEnv(),
269
+ });
270
+ proc.on('exit', (code) => process.exit(code ?? 0));
271
+ }
272
+
273
+ function runRouteEvalCmd() {
274
+ setupIfNeeded();
275
+ const sub = args.slice(1);
276
+ const proc = spawn(venvPython(), ['-m', 'app.eval_cli', ...sub], {
277
+ stdio: 'inherit',
278
+ env: buildEnv(),
279
+ });
280
+ proc.on('exit', (code) => process.exit(code ?? 0));
281
+ }
282
+
283
+ function runWeightsCmd() {
284
+ setupIfNeeded();
285
+ const sub = args.slice(1);
286
+ if (sub.length === 0 || sub[0] === '--help' || sub[0] === '-h') {
287
+ log(c.dim('Usage: skillforge weights export [-o file] [--user-id=] [--project-root=]'));
288
+ log(c.dim(' skillforge weights import <file.json> [--user-id=] [--replace-user] [--project-root=]'));
289
+ process.exit(sub.length === 0 ? 1 : 0);
290
+ }
291
+ const proc = spawn(venvPython(), ['-m', 'app.weights_cli', ...sub], {
292
+ stdio: 'inherit',
293
+ env: buildEnv(),
294
+ });
295
+ proc.on('exit', (code) => process.exit(code ?? 0));
296
+ }
297
+
240
298
  // ---- skill management ----
241
299
  function skillsAdd(srcPath) {
242
300
  if (!srcPath) {
@@ -306,6 +364,19 @@ function reset() {
306
364
  }
307
365
  }
308
366
 
367
+ function runHostsInit() {
368
+ const hostSetup = require('../lib/host-setup');
369
+ hostSetup.reportHostsAndInstallAgentCommands({
370
+ force: args.includes('--force') || args.includes('--force-cursor'),
371
+ pkgRoot: PKG_ROOT,
372
+ pkgVersion: PKG_VERSION,
373
+ log,
374
+ ok,
375
+ err,
376
+ dim: s => log(c.dim(s)),
377
+ });
378
+ }
379
+
309
380
  function showHelp() {
310
381
  log(`
311
382
  ${c.bold('skillforge')} — skill orchestrator co-tool for Claude (MCP-first)
@@ -317,6 +388,8 @@ ${c.bold('Run modes:')}
317
388
  skillforge events [--watch] [--limit=N] [--verbose] [--user=…] Live routing log + usage (see --help)
318
389
  skillforge route [words…] [--project-root=…] [--include-project-rag] Route a prompt (see skillforge route --help)
319
390
  skillforge index --project-root=… [--reset] [--stats-only] Index repo text for include_project_rag
391
+ skillforge health [--quick] [--json] [--project-root=…] Paths + SKILL.md counts; omit --quick to load the embedder
392
+ skillforge route-eval --fixture=path/to/cases.json [--router-mode=embedding] Run routing eval cases
320
393
 
321
394
  ${c.bold('Skills:')}
322
395
  skillforge skills list List bundled and user skills
@@ -331,10 +404,15 @@ ${c.bold('Skill packs (install from git):')}
331
404
 
332
405
  ${c.bold('Maintenance:')}
333
406
  skillforge reset Wipe learned state and event log
334
- skillforge install Re-run setup (auto-runs on first launch)
407
+ skillforge weights export Dump learned weights JSON (see skillforge weights export --help)
408
+ skillforge weights import Restore weights snapshot (see skillforge weights import --help)
409
+ skillforge install Re-run setup (auto-runs on first launch; installs editor /skillforge when detected)
410
+ skillforge install --force-cursor Replace managed host command files even if present
411
+ skillforge hosts init [--force] Write ~/.cursor/commands + ~/.claude/commands /skillforge (no Python)
412
+ skillforge cursor init [--force] Alias for hosts init
335
413
  skillforge --help This message
336
414
 
337
- ${c.bold('First run:')} ${c.cyan('skillforge install')} (auto on first command). Primary use: add MCP config (below). ${c.cyan('skillforge mcp')} needs no API key for embedding-only routing.
415
+ ${c.bold('First run:')} ${c.cyan('skillforge install')} (auto on first command) or ${c.cyan('npx -y')} ${NPM_PKG_NAME} ${c.cyan('install')}. Detects ${c.cyan('Cursor')} and ${c.cyan('Claude Code')} and installs managed **/skillforge** under ${c.cyan('~/.cursor/commands')} and ${c.cyan('~/.claude/commands')} (skip: ${c.dim('SKILLFORGE_SKIP_CURSOR_SETUP')}, ${c.dim('SKILLFORGE_SKIP_CLAUDE_CODE_SETUP')}; force paths: ${c.dim('SKILLFORGE_CURSOR_GLOBAL_COMMAND')} / ${c.dim('SKILLFORGE_CLAUDE_CODE_GLOBAL_COMMAND')}). ${c.cyan('skillforge mcp')} needs no API key for embedding-only routing.
338
416
  ${c.bold('Config dir:')} ${CONFIG_DIR}
339
417
 
340
418
  ${c.bold('MCP integration:')}
@@ -366,6 +444,15 @@ async function main() {
366
444
  case 'index':
367
445
  runIndexCmd();
368
446
  break;
447
+ case 'health':
448
+ runHealthCmd();
449
+ break;
450
+ case 'route-eval':
451
+ runRouteEvalCmd();
452
+ break;
453
+ case 'weights':
454
+ runWeightsCmd();
455
+ break;
369
456
  case 'mcp':
370
457
  if (args[1] === 'config') {
371
458
  printMcpConfig();
@@ -376,6 +463,26 @@ async function main() {
376
463
  case 'install':
377
464
  runSetup();
378
465
  break;
466
+ case 'hosts': {
467
+ const sub = args[1];
468
+ if (sub === 'init') {
469
+ runHostsInit();
470
+ break;
471
+ }
472
+ err('Unknown hosts subcommand.');
473
+ log(c.dim(' Try: skillforge hosts init [--force]'));
474
+ process.exit(1);
475
+ }
476
+ case 'cursor': {
477
+ const sub = args[1];
478
+ if (sub === 'init') {
479
+ runHostsInit();
480
+ break;
481
+ }
482
+ err('Unknown cursor subcommand.');
483
+ log(c.dim(' Try: skillforge cursor init [--force] (alias: hosts init)'));
484
+ process.exit(1);
485
+ }
379
486
  case 'reset':
380
487
  reset();
381
488
  break;
@@ -0,0 +1,4 @@
1
+ {
2
+ "description": "CI gate for the vendored skills tree: at least this many SKILL.md files must be present under skills/. Bump minSkillMdFiles only when intentionally changing bundle size policy.",
3
+ "minSkillMdFiles": 200
4
+ }