skissue 0.1.11 → 0.1.16

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +382 -78
  3. package/dist/entry.js +170 -14
  4. package/package.json +2 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 skill-issue contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,132 +1,436 @@
1
- # skissue
1
+ <pre align="center">
2
+ ▗▄▄▖▗▖ ▗▖▗▄▄▄▖▗▖ ▗▖
3
+ ▐▌ ▐▌▗▞▘ █ ▐▌ ▐▌
4
+ ▝▀▚▖ ▐▛▚▖ █ ▐▌ ▐▌
5
+ ▗▄▄▞▘▐▌ ▐▌▗▄█▄▖▐▙▄▄▖▐▙▄▄▖
6
+ ▗▄▄▄▖▗▄▄▖▗▄▄▖▗▖ ▗▖▗▄▄▄▖
7
+ █ ▐▌ ▐▌ ▐▌ ▐▌▐▌
8
+ █ ▝▀▚▖ ▝▀▚▖▐▌ ▐▌▐▛▀▀▘
9
+ ▗▄█▄▖▗▄▄▞▘▗▄▄▞▘▝▚▄▞▘▐▙▄▄▖
10
+ </pre>
11
+
12
+ <h3 align="center">
13
+ A package manager for AI agent skills.<br>
14
+ <sub>Install. Sync. Ship. No skill issues here.</sub>
15
+ </h3>
16
+
17
+ <p align="center">
18
+ <a href="https://www.npmjs.com/package/skissue"><img src="https://img.shields.io/npm/v/skissue?style=flat-square&logo=npm&color=CB3837" alt="npm version"></a>
19
+ <a href="https://www.npmjs.com/package/skissue"><img src="https://img.shields.io/npm/dm/skissue?style=flat-square&logo=npm&color=CB3837" alt="npm downloads"></a>
20
+ <a href="https://github.com/midyan/skill-issue/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow?style=flat-square" alt="License MIT"></a>
21
+ <a href="https://github.com/midyan/skill-issue"><img src="https://img.shields.io/github/stars/midyan/skill-issue?style=flat-square&logo=github&color=8B5CF6" alt="GitHub stars"></a>
22
+ <img src="https://img.shields.io/badge/node-%3E%3D24-brightgreen?style=flat-square&logo=nodedotjs" alt="Node 24+">
23
+ <img src="https://img.shields.io/badge/TypeScript-5.7-blue?style=flat-square&logo=typescript" alt="TypeScript">
24
+ </p>
25
+
26
+ <p align="center">
27
+ <a href="#quick-start">Quick Start</a> ·
28
+ <a href="#commands">Commands</a> ·
29
+ <a href="#create-your-own-registry">Create a Registry</a> ·
30
+ <a href="#ci--automation">CI & Automation</a> ·
31
+ <a href="#contributing">Contributing</a>
32
+ </p>
33
+
34
+ ---
35
+
36
+ ## What is this?
37
+
38
+ **skissue** is a CLI that installs and syncs **agent skills** from a Git-based registry into your project. Think of it as npm, but for AI agent capabilities — soft docs (`SKILL.md`) and optional hard checks (`hard/index.ts`) that your coding agents can discover and use automatically.
2
39
 
3
- CLI to **install and sync agent skills** from a **GitHub registry** or a **local directory** (e.g. a monorepo whose root contains **`registry/`** and **`registry.json`**). On the **consumer**, skills are installed under **`skillsRoot`** (default **`.agents/skills/<skill-id>/`**, each with **`SKILL.md`**). Configuration and lock state live under **`.skill-issue/`** (committable; no secrets).
40
+ ```
41
+ your-project/
42
+ ├── .agents/skills/ ← skills land here
43
+ │ ├── validate-commits/
44
+ │ │ ├── SKILL.md ← agent reads this
45
+ │ │ └── hard/
46
+ │ │ └── index.ts ← CI runs this
47
+ │ ├── code-review/
48
+ │ │ └── SKILL.md
49
+ │ └── ...
50
+ ├── .skill-issue/
51
+ │ ├── config.yaml ← registry pointer
52
+ │ └── lock.json ← pinned versions
53
+ └── your code...
54
+ ```
4
55
 
5
- **This repository** contains the **CLI** (`src/`) and a **harness** (`harness/`) — TypeScript checks for this repo's docs, indexes, and `src/` layout. The **hosted skill catalog** (`registry.json` + `registry/<skill-id>/`) lives in the separate **skill-registry** repository. **`.agents/`** holds **agent rules** and mirrored skill docs for this repo only — it is **not** the installable registry.
56
+ Your agents get superpowers. Your CI gets guardrails. You get coffee.
6
57
 
7
- ## Structure
58
+ ---
8
59
 
9
- | Path | Role |
10
- | ---------------------- | ---------------------------------------------------------------------------------------------- |
11
- | [AGENTS.md](AGENTS.md) | Agent entry point |
12
- | [harness/](harness/) | CI checks (`harness/runner.ts`, `npm run check:all`); see [harness/INDEX.md](harness/INDEX.md) |
13
- | [.agents/](.agents/) | Rules for maintaining this repo (not the skill-registry payload) |
14
- | [docs/](docs/) | Harness (`HARNESS.md`), architecture (`ARCHITECTURE.md`), INDEX format |
15
- | [src/](src/) | skissue CLI source |
16
- | [scripts/](scripts/) | `install.sh` for global install from a clone |
60
+ ## Quick Start
17
61
 
18
- ## Requirements
62
+ ### 1. Install
19
63
 
20
- - **Node** 24+ (see `.nvmrc`)
21
- - **git** on your `PATH`
22
- - For **remote** registries: access via **`GITHUB_TOKEN`** / **`GH_TOKEN`**, or **git credentials** / **`gh auth`**. **Local** registries need no network auth.
64
+ ```bash
65
+ # Just run it — no global install needed
66
+ npx skissue
67
+
68
+ # ...or install globally if you're the committed type
69
+ npm install -g skissue
70
+ ```
23
71
 
24
- ## Install the CLI
72
+ ### 2. Initialize
73
+
74
+ Run `skissue` with no arguments. If no config exists, it walks you through setup interactively:
25
75
 
26
76
  ```bash
27
- npx skissue --help
77
+ cd your-project
78
+ npx skissue
28
79
  ```
29
80
 
30
- Or install globally:
81
+ ```
82
+ ▗▄▄▖▗▖ ▗▖▗▄▄▄▖▗▖ ▗▖
83
+ ▐▌ ▐▌▗▞▘ █ ▐▌ ▐▌
84
+ ▝▀▚▖ ▐▛▚▖ █ ▐▌ ▐▌
85
+ ▗▄▄▞▘▐▌ ▐▌▗▄█▄▖▐▙▄▄▖▐▙▄▄▖
86
+ ▗▄▄▄▖▗▄▄▖▗▄▄▖▗▖ ▗▖▗▄▄▄▖
87
+ █ ▐▌ ▐▌ ▐▌ ▐▌▐▌
88
+ █ ▝▀▚▖ ▝▀▚▖▐▌ ▐▌▐▛▀▀▘
89
+ ▗▄█▄▖▗▄▄▞▘▗▄▄▞▘▝▚▄▞▘▐▙▄▄▖
90
+
91
+ ◆ Where does the skill registry live?
92
+ │ ● GitHub — clone registry from owner/repo
93
+ │ ○ Local directory
94
+
95
+ ```
96
+
97
+ Pick GitHub or local, point it at your registry, and you're done. Config lands in `.skill-issue/config.yaml`.
98
+
99
+ ### 3. Install skills
31
100
 
32
101
  ```bash
33
- npm install -g skissue
34
- skissue --help
102
+ # Interactive browse, toggle, install
103
+ npx skissue manage
104
+
105
+ # Direct — you know what you want
106
+ npx skissue install validate-commits
107
+ npx skissue install code-review
35
108
  ```
36
109
 
37
- With no arguments, **`npx skissue`** runs interactive **setup** (`.skill-issue/config.yaml`) if needed, then opens the **manage** menu to install or remove skills.
110
+ ### 4. There is no step 4
111
+
112
+ Skills are in `.agents/skills/`. Your agents can find them. Commit the folder. Ship it.
113
+
114
+ ---
115
+
116
+ ## Commands
117
+
118
+ | Command | What it does |
119
+ | ------------------------ | ------------------------------------------------------------ |
120
+ | `skissue` | Smart default — runs `init` if needed, then opens `manage` |
121
+ | `skissue init` | Interactive config setup (`.skill-issue/config.yaml`) |
122
+ | `skissue init-registry` | Scaffold a minimal registry (`registry.json` + sample skill) |
123
+ | `skissue manage` | TUI to browse, install, and uninstall skills |
124
+ | `skissue install <id>` | Install a specific skill from the registry |
125
+ | `skissue uninstall <id>` | Remove a skill and its lock entry |
126
+ | `skissue list` | Show installed skills with lock metadata |
127
+ | `skissue outdated` | Check which installed skills have newer versions |
128
+ | `skissue update [id]` | Re-fetch skill(s) from the registry |
129
+ | `skissue doctor` | Health check — Node version, config, registry connectivity |
130
+
131
+ > `manage` is also aliased as `skissue browse`.
132
+
133
+ ---
134
+
135
+ ## Create Your Own Registry
136
+
137
+ A skill registry is just a Git repo with a specific layout. Here's how to build one from scratch.
138
+
139
+ **Shortcut:** run `npx skissue init-registry` to create `registry.json`, `registry/<sample-skill>/SKILL.md`, and optionally `git init` in the current directory or a path you choose.
140
+
141
+ ### Registry structure
142
+
143
+ ```
144
+ your-skill-registry/
145
+ ├── registry.json ← manifest of all skills
146
+ └── registry/
147
+ ├── my-first-skill/
148
+ │ ├── SKILL.md ← required: what the skill does
149
+ │ └── hard/ ← optional: executable checks
150
+ │ ├── SKILL.md
151
+ │ └── index.ts
152
+ ├── another-skill/
153
+ │ └── SKILL.md
154
+ └── ...
155
+ ```
38
156
 
39
- ### From source
157
+ ### Step 1: Create the repo
40
158
 
41
159
  ```bash
42
- git clone https://github.com/midyan/skill-issue.git
43
- cd skill-issue
44
- npm install
45
- npm run build
46
- node dist/entry.js --help
160
+ mkdir my-skill-registry && cd my-skill-registry
161
+ git init
162
+ mkdir -p registry
47
163
  ```
48
164
 
49
- **Automated publish (CI):**
165
+ ### Step 2: Write `registry.json`
50
166
 
51
- 1. **Pull requests:** [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs **`npm run verify`** (TypeScript, ESLint, Prettier, tests, harness, build).
52
- 2. **Push to `main`:** Same **verify** runs unless the commit message contains **`[skip ci]`** (used by the automated version bump so the full suite does not run twice for that commit).
53
- 3. When **CI succeeds** after a push to **`main`**, [`.github/workflows/version-and-release.yml`](.github/workflows/version-and-release.yml) bumps the **patch** version, commits to **`main`** with **`[skip ci]`**, then creates a **GitHub Release** and tag **`v<version>`**. That tag triggers [`.github/workflows/publish.yml`](.github/workflows/publish.yml) to **`npm publish --provenance --access public`** to the public npm registry.
167
+ This is the manifest. It maps skill IDs to their paths:
168
+
169
+ ```json
170
+ {
171
+ "skills": {
172
+ "my-first-skill": "registry/my-first-skill",
173
+ "another-skill": "registry/another-skill"
174
+ }
175
+ }
176
+ ```
54
177
 
55
- **Branch protection:** `GITHUB_TOKEN` must be allowed to **push to `main`** and create releases (or use a PAT with `contents: write` stored as a secret). If publish fails after the tag exists, **re-run** the publish workflow or use **workflow dispatch** with the tag (e.g. `v0.1.0`).
178
+ > If `registry.json` is missing or doesn't list a skill, the convention `registry/<id>/` is used as a fallback.
56
179
 
57
- ## Quick start
180
+ ### Step 3: Create a skill
58
181
 
59
- ### Consumer project + GitHub registry
182
+ Every skill needs at minimum a `SKILL.md` at its root:
60
183
 
61
184
  ```bash
62
- cd your-consumer-repo
63
- skissue init # choose "GitHub" and enter owner/repo
64
- skissue install skill-issue
185
+ mkdir -p registry/my-first-skill
186
+ ```
187
+
188
+ ```markdown
189
+ <!-- registry/my-first-skill/SKILL.md -->
190
+
191
+ # My First Skill
192
+
193
+ Teaches the agent how to do something awesome.
194
+
195
+ ## When to use
196
+
197
+ Use this skill when the user asks to do the awesome thing.
198
+
199
+ ## Instructions
200
+
201
+ 1. Do the awesome thing
202
+ 2. Do it well
203
+ 3. Profit
65
204
  ```
66
205
 
67
- ### Local skill-registry checkout
206
+ ### Step 4: (Optional) Add a hard check
68
207
 
69
- Point **`registry.path`** at a clone of **skill-registry** (or another repo whose root has **`registry.json`** and **`registry/`**):
208
+ Hard skills are executable TypeScript that can validate, lint, or enforce rules:
70
209
 
71
210
  ```bash
72
- cd /path/to/your-consumer
73
- npm run dev -- init # choose "Local directory", path ../skill-registry
74
- skissue install skill-issue
211
+ mkdir -p registry/my-first-skill/hard
75
212
  ```
76
213
 
77
- `registry.path` is **relative to the consumer project root** (where `.skill-issue/` lives). The local registry must be a **git** checkout so `HEAD` can be stored in the lockfile.
214
+ ```typescript
215
+ // registry/my-first-skill/hard/index.ts
216
+ const errors: string[] = [];
78
217
 
79
- `init` writes `.skill-issue/config.yaml` (either **`registry.path`** or **`registry.owner` / `registry.repo`**, optional **`registry.useSsh`**, branch, and skills root). `install` resolves the path from **`registry.json`** (or **`registry/<id>`** by convention), copies into **`skillsRoot/<id>/`**, and updates **`.skill-issue/lock.json`**.
218
+ // Your validation logic here
219
+ if (!someCondition) {
220
+ errors.push("Something is wrong");
221
+ }
80
222
 
81
- **Remote transport (auto):** if **`registry.useSsh`** is omitted, the CLI uses **SSH** (`git@github.com:…`) when **`GITHUB_TOKEN` / `GH_TOKEN` are unset** (typical laptop with GitHub SSH keys), and **HTTPS with a token** when a token is set (CI). Set **`registry.useSsh: true`** or **`false`** to force SSH or HTTPS.
223
+ if (errors.length > 0) {
224
+ console.error(errors.join("\n"));
225
+ process.exit(1);
226
+ }
227
+ console.log("All good!");
228
+ ```
82
229
 
83
- ## Environment
230
+ ### Step 5: Push and connect
84
231
 
85
- | Variable | Purpose |
86
- | -------------- | ----------------------------------------------------------------------------------- |
87
- | `GITHUB_TOKEN` | HTTPS auth when the resolved transport is HTTPS (e.g. CI, or auto when this is set) |
88
- | `GH_TOKEN` | Same as above if `GITHUB_TOKEN` is unset |
232
+ ```bash
233
+ git add -A && git commit -m "feat: initial skill registry"
234
+ git remote add origin git@github.com:you/my-skill-registry.git
235
+ git push -u origin main
236
+ ```
89
237
 
90
- Do not put tokens in `config.yaml`; keep them in the environment or credential helper.
238
+ Now in any consumer project:
91
239
 
92
- ## Registry layout (in the registry repo)
240
+ ```bash
241
+ npx skissue init # choose GitHub, enter you/my-skill-registry
242
+ npx skissue manage # browse and install your skills
243
+ ```
93
244
 
94
- - **`registry.json`** at the **repository root** maps skill ids to paths (typically under **`registry/`**):
245
+ That's it. You're running a skill registry.
95
246
 
96
- ```json
97
- {
98
- "skills": {
99
- "my-skill": "registry/my-skill"
100
- }
101
- }
247
+ ---
248
+
249
+ ## Local Registry (monorepo friendly)
250
+
251
+ If your registry lives on disk (monorepo, or a local clone), skip GitHub entirely:
252
+
253
+ ```bash
254
+ npx skissue init
255
+ # → choose "Local directory"
256
+ # → path: ../my-skill-registry (relative to your project)
102
257
  ```
103
258
 
104
- - If `registry.json` is missing or does not list an id, the convention **`registry/<id>/`** is used.
259
+ The local registry must be a **git repository** (so `HEAD` can be stored in the lockfile). No network auth needed.
105
260
 
106
- ## Commands
261
+ ---
262
+
263
+ ## How It Works
264
+
265
+ ```
266
+ ┌──────────────────────────────────────────────────────┐
267
+ │ your project │
268
+ │ │
269
+ │ .skill-issue/config.yaml ──→ points to registry │
270
+ │ .skill-issue/lock.json ──→ pinned commits │
271
+ │ │
272
+ │ .agents/skills/<id>/ ←── installed skills │
273
+ │ ├── SKILL.md (agents read these) │
274
+ │ └── hard/index.ts (CI runs these) │
275
+ └───────────────────────┬──────────────────────────────┘
276
+
277
+ │ git clone / local path
278
+
279
+ ┌───────────────────────▼──────────────────────────────┐
280
+ │ skill registry repo │
281
+ │ │
282
+ │ registry.json maps skill-id → path │
283
+ │ registry/<id>/SKILL.md │
284
+ │ registry/<id>/hard/ (optional executable checks) │
285
+ └──────────────────────────────────────────────────────┘
286
+ ```
287
+
288
+ 1. **`skissue init`** writes `.skill-issue/config.yaml` — either a GitHub `owner/repo` or a local path.
289
+ 2. **`skissue install <id>`** clones (or reads) the registry, resolves the skill path from `registry.json`, copies the skill tree into `.agents/skills/<id>/`, and writes a lockfile entry with the commit hash.
290
+ 3. **`skissue outdated`** compares locked commit hashes against the current registry HEAD.
291
+ 4. **`skissue update`** re-fetches and overwrites.
292
+
293
+ ---
294
+
295
+ ## Auth & Transport
296
+
297
+ | Scenario | What happens |
298
+ | ----------------------------------- | ------------------------------------------- |
299
+ | `GITHUB_TOKEN` or `GH_TOKEN` is set | HTTPS with token (typical CI) |
300
+ | No token in env | SSH via `git@github.com:…` (typical laptop) |
301
+ | `registry.useSsh: true` | Force SSH |
302
+ | `registry.useSsh: false` | Force HTTPS |
303
+ | Local registry | No auth needed |
304
+
305
+ > Never put tokens in `config.yaml`. Use environment variables or a credential helper.
306
+
307
+ ---
308
+
309
+ ## CI & Automation
310
+
311
+ Skills with `hard/index.ts` can be wired into your CI pipeline. The harness runner executes every hard skill that isn't marked with `hard/.no-auto-run`:
312
+
313
+ ```bash
314
+ # In your CI workflow
315
+ npx tsx .agents/skills/*/hard/index.ts
316
+ ```
317
+
318
+ Or use the built-in harness runner if you've adopted the full skissue harness pattern:
319
+
320
+ ```bash
321
+ npm run check:all
322
+ ```
323
+
324
+ ### Automated publishing (this repo)
107
325
 
108
- | Command | Description |
109
- | ------------------------ | ------------------------------------------------- |
110
- | `skissue init` | Create config interactively |
111
- | `skissue install <id>` | Install skill at registry branch tip; update lock |
112
- | `skissue uninstall <id>` | Remove install dir and lock entry |
113
- | `skissue list` | Show installed ids and lock metadata |
114
- | `skissue manage` | Interactive menu to install or uninstall skills |
115
- | `skissue outdated` | Compare locked commit vs current branch path diff |
116
- | `skissue update [id]` | Re-install from registry |
117
- | `skissue doctor` | Check Node, config, `git ls-remote` |
326
+ This repo uses a three-workflow CI/CD pipeline:
118
327
 
119
- `manage` is also available as `skissue browse`.
328
+ 1. **PR & push** — [`ci.yml`](.github/workflows/ci.yml) runs `npm run verify` (TypeScript, ESLint, Prettier, tests, harness, harness score, build)
329
+ 2. **Green main** — [`version-and-release.yml`](.github/workflows/version-and-release.yml) bumps the patch version, tags, and creates a GitHub Release
330
+ 3. **Tag push** — [`publish.yml`](.github/workflows/publish.yml) runs `npm publish --access public`
120
331
 
121
- ### Interactive
332
+ ---
122
333
 
123
- After `init`, run `skissue manage` (or `browse`) to see registry skills vs installed skills and batch install or uninstall with prompts. Non-interactive `install` / `uninstall` remain for scripts.
334
+ ## Config Reference
335
+
336
+ `.skill-issue/config.yaml`:
337
+
338
+ ```yaml
339
+ # GitHub registry
340
+ registry:
341
+ owner: midyan
342
+ repo: skill-registry
343
+ branch: main
344
+ # useSsh: true | false | omit for auto-detection
345
+ skillsRoot: .agents/skills
346
+
347
+ # — OR —
348
+
349
+ # Local registry
350
+ registry:
351
+ path: ../skill-registry # relative to project root
352
+ branch: main
353
+ skillsRoot: .agents/skills
354
+ ```
355
+
356
+ `.skill-issue/lock.json` is managed by the CLI. Commit it — it pins your skill versions.
357
+
358
+ ---
359
+
360
+ ## Requirements
361
+
362
+ - **Node** 24+ (see `.nvmrc`)
363
+ - **git** on your `PATH`
364
+ - For **remote** registries: `GITHUB_TOKEN` / `GH_TOKEN`, or SSH keys / `gh auth`
365
+ - For **local** registries: just a git checkout on disk
366
+
367
+ ---
124
368
 
125
369
  ## Development
126
370
 
127
371
  ```bash
128
- npm install # installs deps and runs `prepare` — enables Husky git hooks
129
- npm run verify
372
+ git clone https://github.com/midyan/skill-issue.git
373
+ cd skill-issue
374
+ npm install # also enables Husky git hooks via prepare
375
+ npm run verify # typecheck + lint + format + test + harness + harness score + build
130
376
  ```
131
377
 
132
- **`npm run verify`** runs TypeScript (`tsc --noEmit`), ESLint, Prettier check, Vitest, **`npm run check:all`** ( **`harness/runner.ts`** — each **`harness/<id>/hard/index.ts`** without **`hard/.no-auto-run`** ), then **`npm run build`**. The same pipeline runs on **`git commit`** via **`.husky/pre-commit`** (skip with **`git commit --no-verify`** if needed). See [harness/SKILL.md](harness/SKILL.md), [harness/repo-verify/SKILL.md](harness/repo-verify/SKILL.md), and [docs/HARNESS.md](docs/HARNESS.md).
378
+ | Script | What it does |
379
+ | ----------------------- | ----------------------------------------------------------------------------- |
380
+ | `npm run dev -- <args>` | Run CLI from source via tsx |
381
+ | `npm run verify` | Full pipeline: tsc, eslint, prettier, vitest, check:all, harness score, build |
382
+ | `npm run build` | Production build via esbuild |
383
+ | `npm test` | Run vitest |
384
+ | `npm run check:all` | Harness runner (hard skill checks) |
385
+ | `npm run repo-verify` | Same as verify, with explicit skill discovery output |
386
+
387
+ ### Project structure
388
+
389
+ ```
390
+ src/
391
+ ├── entry.ts ← CLI bootstrap (commander)
392
+ ├── config.ts ← config loading & validation
393
+ ├── lockfile.ts ← lock.json I/O
394
+ ├── paths.ts ← path resolution
395
+ ├── io.ts ← filesystem copy helpers
396
+ ├── commands/ ← subcommands (init, install, manage, ...)
397
+ │ └── banner.ts ← the beautiful TUI banner
398
+ ├── git/ ← git invocation & registry checkout
399
+ └── registry/ ← skill resolution from checkout
400
+ ```
401
+
402
+ ---
403
+
404
+ ## Contributing
405
+
406
+ 1. Fork it
407
+ 2. Create your branch (`git checkout -b feat/amazing-thing`)
408
+ 3. Make your changes
409
+ 4. Run `npm run verify` — everything must pass
410
+ 5. Commit with [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, ...)
411
+ 6. Open a PR
412
+
413
+ ---
414
+
415
+ ## License
416
+
417
+ [MIT](LICENSE) — go wild.
418
+
419
+ ---
420
+
421
+ <p align="center">
422
+ <sub>Built with obsessive attention to developer experience.</sub><br>
423
+ <sub>If your agents have a skill issue, now there's a fix for that.</sub>
424
+ </p>
425
+
426
+ <p align="center">
427
+ <sub>
428
+ <pre>
429
+ ╭────────────────────────────────╮
430
+ │ │
431
+ │ no more skill issues. ever. │
432
+ │ │
433
+ ╰────────────────────────────────╯
434
+ </pre>
435
+ </sub>
436
+ </p>
package/dist/entry.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // src/entry.ts
5
5
  import { Command } from "commander";
6
6
  import { createRequire } from "node:module";
7
- import { resolve as resolve2 } from "node:path";
7
+ import { resolve as resolve3 } from "node:path";
8
8
 
9
9
  // src/commands/init.ts
10
10
  import * as p from "@clack/prompts";
@@ -126,7 +126,7 @@ import { join as join2, resolve } from "node:path";
126
126
  // src/git/exec.ts
127
127
  import { spawn } from "node:child_process";
128
128
  function execGit(args, options = {}) {
129
- return new Promise((resolve3) => {
129
+ return new Promise((resolve4) => {
130
130
  const env = { ...process.env, ...options.env };
131
131
  if (env.GIT_TERMINAL_PROMPT === void 0) {
132
132
  env.GIT_TERMINAL_PROMPT = "0";
@@ -151,7 +151,7 @@ function execGit(args, options = {}) {
151
151
  child.on("close", (code, signal) => {
152
152
  if (timeout !== void 0) clearTimeout(timeout);
153
153
  if (signal === "SIGTERM") {
154
- resolve3({
154
+ resolve4({
155
155
  code: 124,
156
156
  stdout,
157
157
  stderr: `${stderr}
@@ -159,11 +159,11 @@ skissue: git timed out after ${timeoutMs}ms`.trim()
159
159
  });
160
160
  return;
161
161
  }
162
- resolve3({ code: code ?? 1, stdout, stderr });
162
+ resolve4({ code: code ?? 1, stdout, stderr });
163
163
  });
164
164
  child.on("error", (err) => {
165
165
  if (timeout !== void 0) clearTimeout(timeout);
166
- resolve3({ code: 1, stdout, stderr: String(err) });
166
+ resolve4({ code: 1, stdout, stderr: String(err) });
167
167
  });
168
168
  });
169
169
  }
@@ -330,8 +330,8 @@ async function ensureCommit(repoPath, sha) {
330
330
  async function diffPath(repoPath, fromCommit, toCommit, pathInRepo) {
331
331
  await ensureCommit(repoPath, fromCommit);
332
332
  await ensureCommit(repoPath, toCommit);
333
- const p3 = pathInRepo.replace(/^\.\/+/, "").replace(/\/+$/, "");
334
- const diff = await execGit(["diff", fromCommit, toCommit, "--", p3], { cwd: repoPath });
333
+ const p4 = pathInRepo.replace(/^\.\/+/, "").replace(/\/+$/, "");
334
+ const diff = await execGit(["diff", fromCommit, toCommit, "--", p4], { cwd: repoPath });
335
335
  if (diff.code !== 0) {
336
336
  throw new Error(`git diff failed: ${diff.stderr}`);
337
337
  }
@@ -470,11 +470,11 @@ async function runInit(cwd) {
470
470
  process.exit(0);
471
471
  }
472
472
  cfg = ConfigSchema.parse({ ...cfg, skillsRoot: String(skillsRoot).trim() });
473
- const confirm3 = await p.confirm({
473
+ const confirm4 = await p.confirm({
474
474
  message: hasExistingConfig ? `Overwrite ${chalk.cyan(".skill-issue/config.yaml")} and use ${chalk.cyan(cfg.skillsRoot)} for installs?` : `Create ${chalk.cyan(".skill-issue/config.yaml")} and use ${chalk.cyan(cfg.skillsRoot)} for installs?`,
475
475
  initialValue: true
476
476
  });
477
- if (p.isCancel(confirm3) || !confirm3) {
477
+ if (p.isCancel(confirm4) || !confirm4) {
478
478
  p.cancel("Aborted.");
479
479
  process.exit(0);
480
480
  }
@@ -567,8 +567,8 @@ async function resolveSkillPath(registryRepoRoot, skillId) {
567
567
  }
568
568
  return { skillPath: join3("registry", skillId).replace(/\\/g, "/"), source: "convention" };
569
569
  }
570
- function normalizeRelPath(p3) {
571
- return p3.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
570
+ function normalizeRelPath(p4) {
571
+ return p4.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
572
572
  }
573
573
 
574
574
  // src/io.ts
@@ -576,9 +576,9 @@ import { access as access2, cp, rm as rm2 } from "node:fs/promises";
576
576
  import { constants } from "node:fs";
577
577
  import { join as join4 } from "node:path";
578
578
  async function assertSkillMdPresent(skillSourceDir) {
579
- const p3 = join4(skillSourceDir, "SKILL.md");
579
+ const p4 = join4(skillSourceDir, "SKILL.md");
580
580
  try {
581
- await access2(p3, constants.R_OK);
581
+ await access2(p4, constants.R_OK);
582
582
  } catch {
583
583
  throw new Error(`Expected SKILL.md in skill path: ${skillSourceDir}`);
584
584
  }
@@ -1043,11 +1043,167 @@ async function runDoctor(cwd) {
1043
1043
  }
1044
1044
  }
1045
1045
 
1046
+ // src/commands/init-registry.ts
1047
+ import * as p3 from "@clack/prompts";
1048
+ import chalk10 from "chalk";
1049
+ import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
1050
+ import { mkdir as mkdir5, writeFile as writeFile3 } from "node:fs/promises";
1051
+ import { join as join7, resolve as resolve2 } from "node:path";
1052
+ function validateSkillId(raw) {
1053
+ const id = raw.trim();
1054
+ if (!id) return "Skill id is required";
1055
+ if (id.includes("/") || id.includes("\\")) return "Use a single segment (no path separators)";
1056
+ if (id === "." || id === ".." || id.includes("..")) return "Invalid skill id";
1057
+ return void 0;
1058
+ }
1059
+ function registryLayoutExists(root) {
1060
+ const jsonPath = join7(root, "registry.json");
1061
+ const regDir = join7(root, "registry");
1062
+ if (existsSync4(jsonPath)) return true;
1063
+ if (!existsSync4(regDir)) return false;
1064
+ try {
1065
+ return statSync2(regDir).isDirectory();
1066
+ } catch {
1067
+ return false;
1068
+ }
1069
+ }
1070
+ function skillMarkdown(skillId) {
1071
+ const title = skillId.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1072
+ return `---
1073
+ name: ${skillId}
1074
+ description: Sample skill scaffolded by skissue init-registry. Replace this description.
1075
+ ---
1076
+
1077
+ # ${title}
1078
+
1079
+ A minimal starter skill for your registry.
1080
+
1081
+ ## When to use
1082
+
1083
+ Use when the user works with this sample capability (customize this section).
1084
+
1085
+ ## Instructions
1086
+
1087
+ 1. Edit this file to describe what the agent should do.
1088
+ 2. Optionally add \`hard/\` with executable checks.
1089
+ `;
1090
+ }
1091
+ async function scaffoldMinimalRegistry(params) {
1092
+ const { root, skillId, runGitInit } = params;
1093
+ const regPath = `registry/${skillId}`;
1094
+ const manifest = { skills: { [skillId]: regPath } };
1095
+ await mkdir5(join7(root, regPath), { recursive: true });
1096
+ await writeFile3(join7(root, "registry.json"), `${JSON.stringify(manifest, null, 2)}
1097
+ `, "utf8");
1098
+ await writeFile3(join7(root, regPath, "SKILL.md"), skillMarkdown(skillId), "utf8");
1099
+ let gitInitRan = false;
1100
+ if (runGitInit) {
1101
+ const r = await execGit(["init"], { cwd: root });
1102
+ if (r.code === 0) {
1103
+ gitInitRan = true;
1104
+ } else {
1105
+ throw new Error(`git init failed: ${r.stderr || r.stdout || `exit ${r.code}`}`);
1106
+ }
1107
+ }
1108
+ return { root, skillId, gitInitRan };
1109
+ }
1110
+ async function runInitRegistry(cwd) {
1111
+ p3.intro(chalk10.bold("skissue init-registry"));
1112
+ const where = await p3.select({
1113
+ message: "Where should the skill registry live?",
1114
+ options: [
1115
+ { value: "here", label: "Current directory \u2014 create registry files here" },
1116
+ { value: "other", label: "Another directory \u2014 I will enter the path" }
1117
+ ],
1118
+ initialValue: "here"
1119
+ });
1120
+ if (p3.isCancel(where)) {
1121
+ p3.cancel("Aborted.");
1122
+ process.exit(0);
1123
+ }
1124
+ let root;
1125
+ if (where === "here") {
1126
+ root = resolve2(cwd);
1127
+ } else {
1128
+ const raw = await p3.text({
1129
+ message: "Path to registry root (absolute or relative to current directory)",
1130
+ placeholder: "../my-skill-registry",
1131
+ validate: (v) => v?.trim() ? void 0 : "Required"
1132
+ });
1133
+ if (p3.isCancel(raw)) {
1134
+ p3.cancel("Aborted.");
1135
+ process.exit(0);
1136
+ }
1137
+ root = resolve2(cwd, String(raw).trim());
1138
+ }
1139
+ const exists = registryLayoutExists(root);
1140
+ if (exists) {
1141
+ const ok = await p3.confirm({
1142
+ message: `${chalk10.yellow("registry.json and/or registry/ already exist here.")} Replace with a minimal single-skill layout?`,
1143
+ initialValue: false
1144
+ });
1145
+ if (p3.isCancel(ok) || !ok) {
1146
+ p3.cancel("Aborted. No files were changed.");
1147
+ process.exit(0);
1148
+ }
1149
+ }
1150
+ const idRaw = await p3.text({
1151
+ message: "Sample skill id (folder name under registry/)",
1152
+ initialValue: "sample-skill",
1153
+ validate: (v) => validateSkillId(String(v ?? ""))
1154
+ });
1155
+ if (p3.isCancel(idRaw)) {
1156
+ p3.cancel("Aborted.");
1157
+ process.exit(0);
1158
+ }
1159
+ const skillId = String(idRaw).trim();
1160
+ const gitDir = join7(root, ".git");
1161
+ const hadGit = existsSync4(gitDir);
1162
+ let runGitInit = false;
1163
+ if (!hadGit) {
1164
+ const initGit = await p3.confirm({
1165
+ message: "No git repository here yet. Run `git init`? (recommended \u2014 skissue local registry mode needs a git repo)",
1166
+ initialValue: true
1167
+ });
1168
+ if (p3.isCancel(initGit)) {
1169
+ p3.cancel("Aborted.");
1170
+ process.exit(0);
1171
+ }
1172
+ runGitInit = Boolean(initGit);
1173
+ }
1174
+ try {
1175
+ const result = await scaffoldMinimalRegistry({ root, skillId, runGitInit });
1176
+ if (!hadGit && !result.gitInitRan) {
1177
+ p3.note(
1178
+ "This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
1179
+ chalk10.yellow("Heads up")
1180
+ );
1181
+ }
1182
+ p3.outro(
1183
+ chalk10.green(
1184
+ `Wrote ${chalk10.cyan("registry.json")} and ${chalk10.cyan(`registry/${skillId}/SKILL.md`)} under ${chalk10.cyan(result.root)}`
1185
+ )
1186
+ );
1187
+ } catch (e) {
1188
+ p3.cancel(e instanceof Error ? e.message : String(e));
1189
+ process.exitCode = 1;
1190
+ }
1191
+ }
1192
+
1046
1193
  // src/entry.ts
1047
1194
  var require2 = createRequire(import.meta.url);
1048
1195
  var pkg = require2("../package.json");
1049
1196
  var program = new Command();
1050
1197
  program.name("skissue").description("Install and sync agent skills from a GitHub registry or a local registry path").version(pkg.version);
1198
+ program.command("init-registry").description("Scaffold a minimal git-backed skill registry (registry.json + sample skill)").action(async () => {
1199
+ const cwd = process.cwd();
1200
+ try {
1201
+ await runInitRegistry(cwd);
1202
+ } catch (e) {
1203
+ console.error(e);
1204
+ process.exitCode = 1;
1205
+ }
1206
+ });
1051
1207
  program.command("init").description("Create .skill-issue/config and confirm skills install path").action(async () => {
1052
1208
  const cwd = process.cwd();
1053
1209
  try {
@@ -1106,7 +1262,7 @@ program.command("update").description("Re-fetch and overwrite installed skill(s)
1106
1262
  }
1107
1263
  });
1108
1264
  program.command("doctor").description("Check Node, config, and sync registry checkout").option("-C, --cwd <path>", "Project root", process.cwd()).action(async (opts) => {
1109
- const cwd = resolve2(opts.cwd ?? process.cwd());
1265
+ const cwd = resolve3(opts.cwd ?? process.cwd());
1110
1266
  try {
1111
1267
  await runDoctor(cwd);
1112
1268
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skissue",
3
- "version": "0.1.11",
3
+ "version": "0.1.16",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,7 +41,7 @@
41
41
  "check:registry": "tsx harness/validate-registry/hard/index.ts",
42
42
  "check:all": "tsx harness/runner.ts",
43
43
  "check:harness-score": "tsx harness/report-harness-score/hard/index.ts",
44
- "verify": "tsc --noEmit && npm run lint && npm run format:check && npm test && npm run check:all && npm run build",
44
+ "verify": "tsc --noEmit && npm run lint && npm run format:check && npm test && npm run check:all && npm run check:harness-score && npm run build",
45
45
  "repo-verify": "tsx harness/repo-verify/hard/index.ts",
46
46
  "prepublishOnly": "npm run build",
47
47
  "prepare": "husky || node -e \"process.exit(0)\""