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.
- package/LICENSE +21 -0
- package/README.md +382 -78
- package/dist/entry.js +170 -14
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
+
Your agents get superpowers. Your CI gets guardrails. You get coffee.
|
|
6
57
|
|
|
7
|
-
|
|
58
|
+
---
|
|
8
59
|
|
|
9
|
-
|
|
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
|
-
|
|
62
|
+
### 1. Install
|
|
19
63
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
cd your-project
|
|
78
|
+
npx skissue
|
|
28
79
|
```
|
|
29
80
|
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
skissue
|
|
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
|
-
|
|
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
|
-
###
|
|
157
|
+
### Step 1: Create the repo
|
|
40
158
|
|
|
41
159
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
165
|
+
### Step 2: Write `registry.json`
|
|
50
166
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
178
|
+
> If `registry.json` is missing or doesn't list a skill, the convention `registry/<id>/` is used as a fallback.
|
|
56
179
|
|
|
57
|
-
|
|
180
|
+
### Step 3: Create a skill
|
|
58
181
|
|
|
59
|
-
|
|
182
|
+
Every skill needs at minimum a `SKILL.md` at its root:
|
|
60
183
|
|
|
61
184
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
###
|
|
206
|
+
### Step 4: (Optional) Add a hard check
|
|
68
207
|
|
|
69
|
-
|
|
208
|
+
Hard skills are executable TypeScript that can validate, lint, or enforce rules:
|
|
70
209
|
|
|
71
210
|
```bash
|
|
72
|
-
|
|
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
|
-
|
|
214
|
+
```typescript
|
|
215
|
+
// registry/my-first-skill/hard/index.ts
|
|
216
|
+
const errors: string[] = [];
|
|
78
217
|
|
|
79
|
-
|
|
218
|
+
// Your validation logic here
|
|
219
|
+
if (!someCondition) {
|
|
220
|
+
errors.push("Something is wrong");
|
|
221
|
+
}
|
|
80
222
|
|
|
81
|
-
|
|
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
|
-
|
|
230
|
+
### Step 5: Push and connect
|
|
84
231
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
238
|
+
Now in any consumer project:
|
|
91
239
|
|
|
92
|
-
|
|
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
|
-
|
|
245
|
+
That's it. You're running a skill registry.
|
|
95
246
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
259
|
+
The local registry must be a **git repository** (so `HEAD` can be stored in the lockfile). No network auth needed.
|
|
105
260
|
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
332
|
+
---
|
|
122
333
|
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
162
|
+
resolve4({ code: code ?? 1, stdout, stderr });
|
|
163
163
|
});
|
|
164
164
|
child.on("error", (err) => {
|
|
165
165
|
if (timeout !== void 0) clearTimeout(timeout);
|
|
166
|
-
|
|
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
|
|
334
|
-
const diff = await execGit(["diff", fromCommit, toCommit, "--",
|
|
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
|
|
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(
|
|
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(
|
|
571
|
-
return
|
|
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
|
|
579
|
+
const p4 = join4(skillSourceDir, "SKILL.md");
|
|
580
580
|
try {
|
|
581
|
-
await access2(
|
|
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 =
|
|
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.
|
|
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)\""
|