skissue 0.1.11 → 0.1.15

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 +383 -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,437 @@
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://github.com/midyan/skill-issue/actions/workflows/ci.yml"><img src="https://github.com/midyan/skill-issue/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
19
+ <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>
20
+ <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>
21
+ <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>
22
+ <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>
23
+ <img src="https://img.shields.io/badge/node-%3E%3D24-brightgreen?style=flat-square&logo=nodedotjs" alt="Node 24+">
24
+ <img src="https://img.shields.io/badge/TypeScript-5.7-blue?style=flat-square&logo=typescript" alt="TypeScript">
25
+ </p>
26
+
27
+ <p align="center">
28
+ <a href="#quick-start">Quick Start</a> ·
29
+ <a href="#commands">Commands</a> ·
30
+ <a href="#create-your-own-registry">Create a Registry</a> ·
31
+ <a href="#ci--automation">CI & Automation</a> ·
32
+ <a href="#contributing">Contributing</a>
33
+ </p>
34
+
35
+ ---
36
+
37
+ ## What is this?
38
+
39
+ **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
40
 
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).
41
+ ```
42
+ your-project/
43
+ ├── .agents/skills/ ← skills land here
44
+ │ ├── validate-commits/
45
+ │ │ ├── SKILL.md ← agent reads this
46
+ │ │ └── hard/
47
+ │ │ └── index.ts ← CI runs this
48
+ │ ├── code-review/
49
+ │ │ └── SKILL.md
50
+ │ └── ...
51
+ ├── .skill-issue/
52
+ │ ├── config.yaml ← registry pointer
53
+ │ └── lock.json ← pinned versions
54
+ └── your code...
55
+ ```
4
56
 
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.
57
+ Your agents get superpowers. Your CI gets guardrails. You get coffee.
6
58
 
7
- ## Structure
59
+ ---
8
60
 
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 |
61
+ ## Quick Start
17
62
 
18
- ## Requirements
63
+ ### 1. Install
19
64
 
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.
65
+ ```bash
66
+ # Just run it — no global install needed
67
+ npx skissue
68
+
69
+ # ...or install globally if you're the committed type
70
+ npm install -g skissue
71
+ ```
23
72
 
24
- ## Install the CLI
73
+ ### 2. Initialize
74
+
75
+ Run `skissue` with no arguments. If no config exists, it walks you through setup interactively:
25
76
 
26
77
  ```bash
27
- npx skissue --help
78
+ cd your-project
79
+ npx skissue
28
80
  ```
29
81
 
30
- Or install globally:
82
+ ```
83
+ ▗▄▄▖▗▖ ▗▖▗▄▄▄▖▗▖ ▗▖
84
+ ▐▌ ▐▌▗▞▘ █ ▐▌ ▐▌
85
+ ▝▀▚▖ ▐▛▚▖ █ ▐▌ ▐▌
86
+ ▗▄▄▞▘▐▌ ▐▌▗▄█▄▖▐▙▄▄▖▐▙▄▄▖
87
+ ▗▄▄▄▖▗▄▄▖▗▄▄▖▗▖ ▗▖▗▄▄▄▖
88
+ █ ▐▌ ▐▌ ▐▌ ▐▌▐▌
89
+ █ ▝▀▚▖ ▝▀▚▖▐▌ ▐▌▐▛▀▀▘
90
+ ▗▄█▄▖▗▄▄▞▘▗▄▄▞▘▝▚▄▞▘▐▙▄▄▖
91
+
92
+ ◆ Where does the skill registry live?
93
+ │ ● GitHub — clone registry from owner/repo
94
+ │ ○ Local directory
95
+
96
+ ```
97
+
98
+ Pick GitHub or local, point it at your registry, and you're done. Config lands in `.skill-issue/config.yaml`.
99
+
100
+ ### 3. Install skills
31
101
 
32
102
  ```bash
33
- npm install -g skissue
34
- skissue --help
103
+ # Interactive browse, toggle, install
104
+ npx skissue manage
105
+
106
+ # Direct — you know what you want
107
+ npx skissue install validate-commits
108
+ npx skissue install code-review
35
109
  ```
36
110
 
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.
111
+ ### 4. There is no step 4
112
+
113
+ Skills are in `.agents/skills/`. Your agents can find them. Commit the folder. Ship it.
114
+
115
+ ---
116
+
117
+ ## Commands
118
+
119
+ | Command | What it does |
120
+ | ------------------------ | ------------------------------------------------------------ |
121
+ | `skissue` | Smart default — runs `init` if needed, then opens `manage` |
122
+ | `skissue init` | Interactive config setup (`.skill-issue/config.yaml`) |
123
+ | `skissue init-registry` | Scaffold a minimal registry (`registry.json` + sample skill) |
124
+ | `skissue manage` | TUI to browse, install, and uninstall skills |
125
+ | `skissue install <id>` | Install a specific skill from the registry |
126
+ | `skissue uninstall <id>` | Remove a skill and its lock entry |
127
+ | `skissue list` | Show installed skills with lock metadata |
128
+ | `skissue outdated` | Check which installed skills have newer versions |
129
+ | `skissue update [id]` | Re-fetch skill(s) from the registry |
130
+ | `skissue doctor` | Health check — Node version, config, registry connectivity |
131
+
132
+ > `manage` is also aliased as `skissue browse`.
133
+
134
+ ---
135
+
136
+ ## Create Your Own Registry
137
+
138
+ A skill registry is just a Git repo with a specific layout. Here's how to build one from scratch.
139
+
140
+ **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.
141
+
142
+ ### Registry structure
143
+
144
+ ```
145
+ your-skill-registry/
146
+ ├── registry.json ← manifest of all skills
147
+ └── registry/
148
+ ├── my-first-skill/
149
+ │ ├── SKILL.md ← required: what the skill does
150
+ │ └── hard/ ← optional: executable checks
151
+ │ ├── SKILL.md
152
+ │ └── index.ts
153
+ ├── another-skill/
154
+ │ └── SKILL.md
155
+ └── ...
156
+ ```
38
157
 
39
- ### From source
158
+ ### Step 1: Create the repo
40
159
 
41
160
  ```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
161
+ mkdir my-skill-registry && cd my-skill-registry
162
+ git init
163
+ mkdir -p registry
47
164
  ```
48
165
 
49
- **Automated publish (CI):**
166
+ ### Step 2: Write `registry.json`
50
167
 
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.
168
+ This is the manifest. It maps skill IDs to their paths:
169
+
170
+ ```json
171
+ {
172
+ "skills": {
173
+ "my-first-skill": "registry/my-first-skill",
174
+ "another-skill": "registry/another-skill"
175
+ }
176
+ }
177
+ ```
54
178
 
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`).
179
+ > If `registry.json` is missing or doesn't list a skill, the convention `registry/<id>/` is used as a fallback.
56
180
 
57
- ## Quick start
181
+ ### Step 3: Create a skill
58
182
 
59
- ### Consumer project + GitHub registry
183
+ Every skill needs at minimum a `SKILL.md` at its root:
60
184
 
61
185
  ```bash
62
- cd your-consumer-repo
63
- skissue init # choose "GitHub" and enter owner/repo
64
- skissue install skill-issue
186
+ mkdir -p registry/my-first-skill
187
+ ```
188
+
189
+ ```markdown
190
+ <!-- registry/my-first-skill/SKILL.md -->
191
+
192
+ # My First Skill
193
+
194
+ Teaches the agent how to do something awesome.
195
+
196
+ ## When to use
197
+
198
+ Use this skill when the user asks to do the awesome thing.
199
+
200
+ ## Instructions
201
+
202
+ 1. Do the awesome thing
203
+ 2. Do it well
204
+ 3. Profit
65
205
  ```
66
206
 
67
- ### Local skill-registry checkout
207
+ ### Step 4: (Optional) Add a hard check
68
208
 
69
- Point **`registry.path`** at a clone of **skill-registry** (or another repo whose root has **`registry.json`** and **`registry/`**):
209
+ Hard skills are executable TypeScript that can validate, lint, or enforce rules:
70
210
 
71
211
  ```bash
72
- cd /path/to/your-consumer
73
- npm run dev -- init # choose "Local directory", path ../skill-registry
74
- skissue install skill-issue
212
+ mkdir -p registry/my-first-skill/hard
75
213
  ```
76
214
 
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.
215
+ ```typescript
216
+ // registry/my-first-skill/hard/index.ts
217
+ const errors: string[] = [];
78
218
 
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`**.
219
+ // Your validation logic here
220
+ if (!someCondition) {
221
+ errors.push("Something is wrong");
222
+ }
80
223
 
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.
224
+ if (errors.length > 0) {
225
+ console.error(errors.join("\n"));
226
+ process.exit(1);
227
+ }
228
+ console.log("All good!");
229
+ ```
82
230
 
83
- ## Environment
231
+ ### Step 5: Push and connect
84
232
 
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 |
233
+ ```bash
234
+ git add -A && git commit -m "feat: initial skill registry"
235
+ git remote add origin git@github.com:you/my-skill-registry.git
236
+ git push -u origin main
237
+ ```
89
238
 
90
- Do not put tokens in `config.yaml`; keep them in the environment or credential helper.
239
+ Now in any consumer project:
91
240
 
92
- ## Registry layout (in the registry repo)
241
+ ```bash
242
+ npx skissue init # choose GitHub, enter you/my-skill-registry
243
+ npx skissue manage # browse and install your skills
244
+ ```
93
245
 
94
- - **`registry.json`** at the **repository root** maps skill ids to paths (typically under **`registry/`**):
246
+ That's it. You're running a skill registry.
95
247
 
96
- ```json
97
- {
98
- "skills": {
99
- "my-skill": "registry/my-skill"
100
- }
101
- }
248
+ ---
249
+
250
+ ## Local Registry (monorepo friendly)
251
+
252
+ If your registry lives on disk (monorepo, or a local clone), skip GitHub entirely:
253
+
254
+ ```bash
255
+ npx skissue init
256
+ # → choose "Local directory"
257
+ # → path: ../my-skill-registry (relative to your project)
102
258
  ```
103
259
 
104
- - If `registry.json` is missing or does not list an id, the convention **`registry/<id>/`** is used.
260
+ The local registry must be a **git repository** (so `HEAD` can be stored in the lockfile). No network auth needed.
105
261
 
106
- ## Commands
262
+ ---
263
+
264
+ ## How It Works
265
+
266
+ ```
267
+ ┌──────────────────────────────────────────────────────┐
268
+ │ your project │
269
+ │ │
270
+ │ .skill-issue/config.yaml ──→ points to registry │
271
+ │ .skill-issue/lock.json ──→ pinned commits │
272
+ │ │
273
+ │ .agents/skills/<id>/ ←── installed skills │
274
+ │ ├── SKILL.md (agents read these) │
275
+ │ └── hard/index.ts (CI runs these) │
276
+ └───────────────────────┬──────────────────────────────┘
277
+
278
+ │ git clone / local path
279
+
280
+ ┌───────────────────────▼──────────────────────────────┐
281
+ │ skill registry repo │
282
+ │ │
283
+ │ registry.json maps skill-id → path │
284
+ │ registry/<id>/SKILL.md │
285
+ │ registry/<id>/hard/ (optional executable checks) │
286
+ └──────────────────────────────────────────────────────┘
287
+ ```
288
+
289
+ 1. **`skissue init`** writes `.skill-issue/config.yaml` — either a GitHub `owner/repo` or a local path.
290
+ 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.
291
+ 3. **`skissue outdated`** compares locked commit hashes against the current registry HEAD.
292
+ 4. **`skissue update`** re-fetches and overwrites.
293
+
294
+ ---
295
+
296
+ ## Auth & Transport
297
+
298
+ | Scenario | What happens |
299
+ | ----------------------------------- | ------------------------------------------- |
300
+ | `GITHUB_TOKEN` or `GH_TOKEN` is set | HTTPS with token (typical CI) |
301
+ | No token in env | SSH via `git@github.com:…` (typical laptop) |
302
+ | `registry.useSsh: true` | Force SSH |
303
+ | `registry.useSsh: false` | Force HTTPS |
304
+ | Local registry | No auth needed |
305
+
306
+ > Never put tokens in `config.yaml`. Use environment variables or a credential helper.
307
+
308
+ ---
309
+
310
+ ## CI & Automation
311
+
312
+ 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`:
313
+
314
+ ```bash
315
+ # In your CI workflow
316
+ npx tsx .agents/skills/*/hard/index.ts
317
+ ```
318
+
319
+ Or use the built-in harness runner if you've adopted the full skissue harness pattern:
320
+
321
+ ```bash
322
+ npm run check:all
323
+ ```
324
+
325
+ ### Automated publishing (this repo)
107
326
 
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` |
327
+ This repo uses a three-workflow CI/CD pipeline:
118
328
 
119
- `manage` is also available as `skissue browse`.
329
+ 1. **PR & push** — [`ci.yml`](.github/workflows/ci.yml) runs `npm run verify` (TypeScript, ESLint, Prettier, tests, harness, harness score, build)
330
+ 2. **Green main** — [`version-and-release.yml`](.github/workflows/version-and-release.yml) bumps the patch version, tags, and creates a GitHub Release
331
+ 3. **Tag push** — [`publish.yml`](.github/workflows/publish.yml) runs `npm publish --access public`
120
332
 
121
- ### Interactive
333
+ ---
122
334
 
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.
335
+ ## Config Reference
336
+
337
+ `.skill-issue/config.yaml`:
338
+
339
+ ```yaml
340
+ # GitHub registry
341
+ registry:
342
+ owner: midyan
343
+ repo: skill-registry
344
+ branch: main
345
+ # useSsh: true | false | omit for auto-detection
346
+ skillsRoot: .agents/skills
347
+
348
+ # — OR —
349
+
350
+ # Local registry
351
+ registry:
352
+ path: ../skill-registry # relative to project root
353
+ branch: main
354
+ skillsRoot: .agents/skills
355
+ ```
356
+
357
+ `.skill-issue/lock.json` is managed by the CLI. Commit it — it pins your skill versions.
358
+
359
+ ---
360
+
361
+ ## Requirements
362
+
363
+ - **Node** 24+ (see `.nvmrc`)
364
+ - **git** on your `PATH`
365
+ - For **remote** registries: `GITHUB_TOKEN` / `GH_TOKEN`, or SSH keys / `gh auth`
366
+ - For **local** registries: just a git checkout on disk
367
+
368
+ ---
124
369
 
125
370
  ## Development
126
371
 
127
372
  ```bash
128
- npm install # installs deps and runs `prepare` — enables Husky git hooks
129
- npm run verify
373
+ git clone https://github.com/midyan/skill-issue.git
374
+ cd skill-issue
375
+ npm install # also enables Husky git hooks via prepare
376
+ npm run verify # typecheck + lint + format + test + harness + harness score + build
130
377
  ```
131
378
 
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).
379
+ | Script | What it does |
380
+ | ----------------------- | ----------------------------------------------------------------------------- |
381
+ | `npm run dev -- <args>` | Run CLI from source via tsx |
382
+ | `npm run verify` | Full pipeline: tsc, eslint, prettier, vitest, check:all, harness score, build |
383
+ | `npm run build` | Production build via esbuild |
384
+ | `npm test` | Run vitest |
385
+ | `npm run check:all` | Harness runner (hard skill checks) |
386
+ | `npm run repo-verify` | Same as verify, with explicit skill discovery output |
387
+
388
+ ### Project structure
389
+
390
+ ```
391
+ src/
392
+ ├── entry.ts ← CLI bootstrap (commander)
393
+ ├── config.ts ← config loading & validation
394
+ ├── lockfile.ts ← lock.json I/O
395
+ ├── paths.ts ← path resolution
396
+ ├── io.ts ← filesystem copy helpers
397
+ ├── commands/ ← subcommands (init, install, manage, ...)
398
+ │ └── banner.ts ← the beautiful TUI banner
399
+ ├── git/ ← git invocation & registry checkout
400
+ └── registry/ ← skill resolution from checkout
401
+ ```
402
+
403
+ ---
404
+
405
+ ## Contributing
406
+
407
+ 1. Fork it
408
+ 2. Create your branch (`git checkout -b feat/amazing-thing`)
409
+ 3. Make your changes
410
+ 4. Run `npm run verify` — everything must pass
411
+ 5. Commit with [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, ...)
412
+ 6. Open a PR
413
+
414
+ ---
415
+
416
+ ## License
417
+
418
+ [MIT](LICENSE) — go wild.
419
+
420
+ ---
421
+
422
+ <p align="center">
423
+ <sub>Built with obsessive attention to developer experience.</sub><br>
424
+ <sub>If your agents have a skill issue, now there's a fix for that.</sub>
425
+ </p>
426
+
427
+ <p align="center">
428
+ <sub>
429
+ <pre>
430
+ ╭────────────────────────────────╮
431
+ │ │
432
+ │ no more skill issues. ever. │
433
+ │ │
434
+ ╰────────────────────────────────╯
435
+ </pre>
436
+ </sub>
437
+ </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.15",
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)\""