@static-var/keystone 0.1.0
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/.agents/plugins/marketplace.json +24 -0
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +12 -0
- package/.codex-plugin/plugin.json +12 -0
- package/.pi/extensions/keystone.ts +172 -0
- package/HOW_IT_WORKS.md +424 -0
- package/Makefile +19 -0
- package/README.md +253 -0
- package/package.json +86 -0
- package/packaging.allowlist +32 -0
- package/scripts/build-metadata.py +99 -0
- package/scripts/package-keystone.sh +59 -0
- package/scripts/validate-keystone.py +261 -0
- package/scripts/validate-package.py +140 -0
- package/skills/keystone/SKILL.md +69 -0
- package/skills/keystone/modules/breakdown.md +239 -0
- package/skills/keystone/modules/build.md +284 -0
- package/skills/keystone/modules/debug.md +198 -0
- package/skills/keystone/modules/gates/isolation.md +56 -0
- package/skills/keystone/modules/gates/proof.md +54 -0
- package/skills/keystone/modules/gates/red.md +59 -0
- package/skills/keystone/modules/gates/review.md +56 -0
- package/skills/keystone/modules/gates/ship.md +57 -0
- package/skills/keystone/modules/health.md +124 -0
- package/skills/keystone/modules/helpers/subagents.md +134 -0
- package/skills/keystone/modules/research.md +86 -0
- package/skills/keystone/modules/review.md +270 -0
- package/skills/keystone/modules/router.md +36 -0
- package/skills/keystone/modules/shape.md +125 -0
- package/skills/keystone/modules/ship.md +130 -0
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Keystone
|
|
2
|
+
|
|
3
|
+
> One public `/keystone` doorway for disciplined AI workflows.
|
|
4
|
+
|
|
5
|
+
Keystone is a hybrid workflow/skill system for coding agents. It keeps the public surface small, routes each request to one internal module, and uses gates to prevent the common failure modes: editing too early, planning without proof, reviewing while mutating, or shipping unverified work.
|
|
6
|
+
|
|
7
|
+
Keystone borrows the best parts of Waza-style routing/review/release discipline and Superpowers-style planning, TDD, debugging, and verification habits.
|
|
8
|
+
|
|
9
|
+
## What Keystone gives you
|
|
10
|
+
|
|
11
|
+
- **One public entrypoint:** `/keystone`
|
|
12
|
+
- **Private internal modules:** router, research, shape, breakdown, build, debug, review, ship, and health
|
|
13
|
+
- **A renamed planner:** `breakdown`, not `plan`, to avoid `/plan` collisions
|
|
14
|
+
- **Safety gates:** isolation, red, proof, review, and ship
|
|
15
|
+
- **Subagent guidance:** host capability matrix plus recommended reasoning level per module
|
|
16
|
+
- **Package tooling:** generated platform manifests, allowlisted archive builds, validators, and routing tests
|
|
17
|
+
- **Multi-host packaging:** Pi extension + skill package metadata plus Claude/Codex/agents plugin manifests
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Validate source, package, routing fixtures, and Python scripts
|
|
23
|
+
make test
|
|
24
|
+
|
|
25
|
+
# Regenerate host metadata
|
|
26
|
+
make regenerate
|
|
27
|
+
|
|
28
|
+
# Build the distributable archive
|
|
29
|
+
make package
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The package archive is written to:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
dist/keystone.zip
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`dist/` is generated and ignored by git.
|
|
39
|
+
|
|
40
|
+
Install in Pi from npm after release:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pi install npm:@static-var/keystone
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or install directly from GitHub:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pi install git:github.com/static-var/Keystone
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Then invoke:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
/keystone <task>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Pi package gallery listing: once `@static-var/keystone` is published to npm with the `pi-package` keyword, it appears on `https://pi.dev/packages`.
|
|
59
|
+
|
|
60
|
+
## How it works in one picture
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
User request
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
/keystone public skill
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
Router chooses exactly one primary module
|
|
70
|
+
│
|
|
71
|
+
├─ router / research / shape / breakdown
|
|
72
|
+
└─ build / debug / review / ship / health
|
|
73
|
+
│
|
|
74
|
+
▼
|
|
75
|
+
Module contract decides what is allowed
|
|
76
|
+
│
|
|
77
|
+
▼
|
|
78
|
+
Gate checks when required
|
|
79
|
+
│
|
|
80
|
+
├─ isolation: safe before mutation
|
|
81
|
+
├─ red: failing check before implementation when practical
|
|
82
|
+
├─ proof: evidence before success claims
|
|
83
|
+
├─ review: no blocking review findings
|
|
84
|
+
└─ ship: verified, reviewed, ready to hand off
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Core rule
|
|
88
|
+
|
|
89
|
+
Keystone exposes **one public skill**:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
skills/keystone/SKILL.md
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Everything else under `skills/keystone/modules/` is internal. Internal modules are not public slash commands.
|
|
96
|
+
|
|
97
|
+
## Routing map
|
|
98
|
+
|
|
99
|
+
| User wants to... | Keystone routes to... |
|
|
100
|
+
|---|---|
|
|
101
|
+
| classify an ambiguous request | `router` |
|
|
102
|
+
| inspect, summarize, research, or compare sources | `research` |
|
|
103
|
+
| draft prose, shape product direction, design UI, or make architecture/scope tradeoffs | `shape` |
|
|
104
|
+
| turn approved direction into tasks | `breakdown` |
|
|
105
|
+
| edit files or implement work | `build` |
|
|
106
|
+
| diagnose bugs or failures | `debug` |
|
|
107
|
+
| review work without changing it | `review` |
|
|
108
|
+
| finalize completed work | `ship` |
|
|
109
|
+
| assess project/tooling health | `health` |
|
|
110
|
+
|
|
111
|
+
## Repository-only maintainer notes
|
|
112
|
+
|
|
113
|
+
Keystone maintainer guidance can live in the repository without shipping as product. Current maintainer-only notes are in:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
maintainers/skill-engineering.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This file is not included in `dist/keystone.zip` and is not part of `/keystone` routing.
|
|
120
|
+
|
|
121
|
+
## Repository layout
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
.
|
|
125
|
+
├── skills/keystone/ # canonical skill source
|
|
126
|
+
│ ├── SKILL.md # public /keystone entrypoint
|
|
127
|
+
│ └── modules/ # internal modules, helpers, and gates
|
|
128
|
+
├── scripts/ # metadata, validation, packaging
|
|
129
|
+
├── tests/routing/cases.yaml # routing fixture cases
|
|
130
|
+
├── tests/test_routing.py # stdlib routing test runner
|
|
131
|
+
├── maintainers/ # repo-only, not shipped in package
|
|
132
|
+
├── .claude-plugin/ # generated Claude plugin metadata
|
|
133
|
+
├── .codex-plugin/ # generated Codex plugin metadata
|
|
134
|
+
├── .agents/plugins/ # generated agents marketplace metadata
|
|
135
|
+
├── packaging.allowlist # default-deny package contents
|
|
136
|
+
├── Makefile # test/package/regenerate targets
|
|
137
|
+
└── HOW_IT_WORKS.md # human-readable architecture guide
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Subagent and reasoning support
|
|
141
|
+
|
|
142
|
+
Keystone documents host-specific subagent support in:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
skills/keystone/modules/helpers/subagents.md
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Current summary:
|
|
149
|
+
|
|
150
|
+
| Harness | Subagents | Per-subagent reasoning |
|
|
151
|
+
|---|---:|---:|
|
|
152
|
+
| Pi with `pi-subagents` | yes | yes: `thinking`, `model`, `profile` |
|
|
153
|
+
| Claude Code | yes | partial: model/detail controls, no general custom-agent reasoning knob |
|
|
154
|
+
| Codex CLI/app | host-dependent | partial/global: use prompt advisory unless host exposes per-agent effort |
|
|
155
|
+
| T3 Code | unconfirmed | unconfirmed |
|
|
156
|
+
| OpenCode | yes | partial/provider-dependent: `model` and provider `variant`; no universal reasoning knob confirmed |
|
|
157
|
+
| GitHub Copilot | yes | partial: custom agent `model`, no general reasoning knob |
|
|
158
|
+
|
|
159
|
+
Each Keystone module includes a `Subagents and reasoning` section with its default reasoning level.
|
|
160
|
+
|
|
161
|
+
## Platform support
|
|
162
|
+
|
|
163
|
+
Keystone currently ships as:
|
|
164
|
+
|
|
165
|
+
- **Pi extension + skill package** via `package.json`:
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"pi": {
|
|
169
|
+
"extensions": ["./.pi/extensions/keystone.ts"],
|
|
170
|
+
"skills": ["./skills"]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
- **Pi extension source** in `.pi/extensions/keystone.ts`, which registers `/keystone`, discovers the bundled skill, and adds a small Pi-specific bootstrap.
|
|
175
|
+
- **Claude plugin metadata** in `.claude-plugin/`
|
|
176
|
+
- **Codex plugin metadata** in `.codex-plugin/`
|
|
177
|
+
- **Agents marketplace metadata** in `.agents/plugins/`
|
|
178
|
+
|
|
179
|
+
## Release automation
|
|
180
|
+
|
|
181
|
+
Pi package discovery is npm-based. The package name is `@static-var/keystone`; the unscoped `keystone` name is already taken on npm.
|
|
182
|
+
|
|
183
|
+
CI/CD:
|
|
184
|
+
|
|
185
|
+
- `.github/workflows/ci.yml` runs on PRs and `main`: `npm ci`, Pi extension typecheck, `make test`, npm pack dry-run, and uploads `dist/keystone.zip`.
|
|
186
|
+
- `.github/workflows/release.yml` runs on `v*.*.*` tags or manual dispatch: verifies the tag matches `package.json` version, validates, publishes to npm through Trusted Publishing/OIDC with provenance, and creates a GitHub Release with both `dist/keystone.zip` and the npm tarball.
|
|
187
|
+
|
|
188
|
+
Trusted Publishing setup on npm:
|
|
189
|
+
|
|
190
|
+
1. Confirm you own the npm scope in `package.json`. Keystone currently publishes as `@static-var/keystone`; if your npm username/org is different, rename the package before publishing.
|
|
191
|
+
2. Bootstrap the package once, because npm only shows the package access / Trusted Publisher UI after the package exists:
|
|
192
|
+
```bash
|
|
193
|
+
npm login
|
|
194
|
+
npm run typecheck
|
|
195
|
+
make test
|
|
196
|
+
npm publish --access public --provenance=false
|
|
197
|
+
```
|
|
198
|
+
If npm requires 2FA, append `--otp <code>`. Local bootstrap disables provenance because provenance is provided by future OIDC releases.
|
|
199
|
+
3. Open `https://www.npmjs.com/package/@static-var/keystone/access`.
|
|
200
|
+
4. In **Trusted Publisher**, choose **GitHub Actions**.
|
|
201
|
+
5. Configure:
|
|
202
|
+
- Organization or user: `static-var`
|
|
203
|
+
- Repository: `Keystone`
|
|
204
|
+
- Workflow filename: `release.yml`
|
|
205
|
+
- Environment name: leave blank unless you add a GitHub deployment environment
|
|
206
|
+
- Allowed actions: `npm publish`
|
|
207
|
+
6. Save. Future releases use OIDC; no `NPM_TOKEN` secret is needed.
|
|
208
|
+
|
|
209
|
+
Release:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npm version patch --no-git-tag-version # or minor/major; edit changelog if added later
|
|
213
|
+
make test
|
|
214
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
215
|
+
git add package.json package-lock.json
|
|
216
|
+
git commit -m "chore: release v${VERSION}"
|
|
217
|
+
git tag "v${VERSION}"
|
|
218
|
+
git push origin main --tags
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Maintainer commands
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
make regenerate # rebuild generated plugin/marketplace metadata
|
|
225
|
+
make validate # build package, validate source, validate archive
|
|
226
|
+
make routing # run routing fixture tests
|
|
227
|
+
make package # write dist/keystone.zip from packaging.allowlist
|
|
228
|
+
make test # run the full local check suite
|
|
229
|
+
npm run typecheck # typecheck the Pi extension
|
|
230
|
+
npm run pack:dry-run # preview npm package contents
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Guardrails
|
|
234
|
+
|
|
235
|
+
- Do not add public slash commands for internal modules.
|
|
236
|
+
- Do not rename `breakdown` to `plan`.
|
|
237
|
+
- Do not let `build` edit before the isolation gate.
|
|
238
|
+
- Do not let `review` fix, commit, or ship.
|
|
239
|
+
- Do not let `ship` start new implementation.
|
|
240
|
+
- Do not hand-edit generated manifests; update source and run `make regenerate`.
|
|
241
|
+
- Do not package ignored local artifacts such as `index.html`, `styles.css`, `plans/`, `docs/`, `dist/`, or pycache files.
|
|
242
|
+
|
|
243
|
+
## Read next
|
|
244
|
+
|
|
245
|
+
For the full mental model, routing lifecycle, packaging flow, and maintainer workflow, read:
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
HOW_IT_WORKS.md
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@static-var/keystone",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Keystone: one-router AI engineering workflow extension and skill system for Pi.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/static-var/Keystone.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/static-var/Keystone/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/static-var/Keystone#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"agent-skills",
|
|
18
|
+
"keystone",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"codex",
|
|
21
|
+
"opencode",
|
|
22
|
+
"copilot",
|
|
23
|
+
"pi-coding-agent",
|
|
24
|
+
"pi-extension",
|
|
25
|
+
"workflow",
|
|
26
|
+
"skills",
|
|
27
|
+
"agent-workflows"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"README.md",
|
|
31
|
+
"HOW_IT_WORKS.md",
|
|
32
|
+
"package.json",
|
|
33
|
+
"packaging.allowlist",
|
|
34
|
+
"Makefile",
|
|
35
|
+
"scripts/build-metadata.py",
|
|
36
|
+
"scripts/validate-keystone.py",
|
|
37
|
+
"scripts/validate-package.py",
|
|
38
|
+
"scripts/package-keystone.sh",
|
|
39
|
+
"skills/keystone/SKILL.md",
|
|
40
|
+
"skills/keystone/modules/router.md",
|
|
41
|
+
"skills/keystone/modules/research.md",
|
|
42
|
+
"skills/keystone/modules/shape.md",
|
|
43
|
+
"skills/keystone/modules/breakdown.md",
|
|
44
|
+
"skills/keystone/modules/build.md",
|
|
45
|
+
"skills/keystone/modules/debug.md",
|
|
46
|
+
"skills/keystone/modules/review.md",
|
|
47
|
+
"skills/keystone/modules/ship.md",
|
|
48
|
+
"skills/keystone/modules/health.md",
|
|
49
|
+
"skills/keystone/modules/helpers/subagents.md",
|
|
50
|
+
"skills/keystone/modules/gates/isolation.md",
|
|
51
|
+
"skills/keystone/modules/gates/proof.md",
|
|
52
|
+
"skills/keystone/modules/gates/red.md",
|
|
53
|
+
"skills/keystone/modules/gates/review.md",
|
|
54
|
+
"skills/keystone/modules/gates/ship.md",
|
|
55
|
+
".claude-plugin/plugin.json",
|
|
56
|
+
".claude-plugin/marketplace.json",
|
|
57
|
+
".codex-plugin/plugin.json",
|
|
58
|
+
".agents/plugins/marketplace.json",
|
|
59
|
+
".pi/extensions/keystone.ts"
|
|
60
|
+
],
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public",
|
|
63
|
+
"provenance": true
|
|
64
|
+
},
|
|
65
|
+
"pi": {
|
|
66
|
+
"extensions": [
|
|
67
|
+
"./.pi/extensions/keystone.ts"
|
|
68
|
+
],
|
|
69
|
+
"skills": [
|
|
70
|
+
"./skills"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@earendil-works/pi-coding-agent": "0.80.2",
|
|
75
|
+
"@types/node": "26.0.1",
|
|
76
|
+
"typescript": "6.0.3"
|
|
77
|
+
},
|
|
78
|
+
"scripts": {
|
|
79
|
+
"regenerate": "python3 scripts/build-metadata.py",
|
|
80
|
+
"validate": "make validate",
|
|
81
|
+
"package": "scripts/package-keystone.sh",
|
|
82
|
+
"test": "make test",
|
|
83
|
+
"typecheck": "tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck .pi/extensions/keystone.ts",
|
|
84
|
+
"pack:dry-run": "npm pack --dry-run"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Default-deny shipping allowlist for Keystone packaging.
|
|
2
|
+
# Entries are newline-delimited paths relative to the repository root.
|
|
3
|
+
README.md
|
|
4
|
+
HOW_IT_WORKS.md
|
|
5
|
+
package.json
|
|
6
|
+
packaging.allowlist
|
|
7
|
+
Makefile
|
|
8
|
+
scripts/build-metadata.py
|
|
9
|
+
scripts/validate-keystone.py
|
|
10
|
+
scripts/validate-package.py
|
|
11
|
+
scripts/package-keystone.sh
|
|
12
|
+
skills/keystone/SKILL.md
|
|
13
|
+
skills/keystone/modules/router.md
|
|
14
|
+
skills/keystone/modules/research.md
|
|
15
|
+
skills/keystone/modules/shape.md
|
|
16
|
+
skills/keystone/modules/breakdown.md
|
|
17
|
+
skills/keystone/modules/build.md
|
|
18
|
+
skills/keystone/modules/debug.md
|
|
19
|
+
skills/keystone/modules/review.md
|
|
20
|
+
skills/keystone/modules/ship.md
|
|
21
|
+
skills/keystone/modules/health.md
|
|
22
|
+
skills/keystone/modules/helpers/subagents.md
|
|
23
|
+
skills/keystone/modules/gates/isolation.md
|
|
24
|
+
skills/keystone/modules/gates/proof.md
|
|
25
|
+
skills/keystone/modules/gates/red.md
|
|
26
|
+
skills/keystone/modules/gates/review.md
|
|
27
|
+
skills/keystone/modules/gates/ship.md
|
|
28
|
+
.claude-plugin/plugin.json
|
|
29
|
+
.claude-plugin/marketplace.json
|
|
30
|
+
.codex-plugin/plugin.json
|
|
31
|
+
.agents/plugins/marketplace.json
|
|
32
|
+
.pi/extensions/keystone.ts
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate Keystone platform metadata from the canonical skill source."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
10
|
+
SKILL = ROOT / "skills" / "keystone" / "SKILL.md"
|
|
11
|
+
PACKAGE = ROOT / "package.json"
|
|
12
|
+
NAME = "keystone"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_package() -> dict:
|
|
16
|
+
if PACKAGE.exists():
|
|
17
|
+
return json.loads(PACKAGE.read_text())
|
|
18
|
+
return {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_frontmatter(text: str) -> dict:
|
|
22
|
+
if not text.startswith("---\n"):
|
|
23
|
+
return {}
|
|
24
|
+
end = text.find("\n---", 4)
|
|
25
|
+
if end == -1:
|
|
26
|
+
return {}
|
|
27
|
+
data: dict[str, object] = {}
|
|
28
|
+
current_key = None
|
|
29
|
+
for raw in text[4:end].splitlines():
|
|
30
|
+
line = raw.rstrip()
|
|
31
|
+
if not line or line.lstrip().startswith("#"):
|
|
32
|
+
continue
|
|
33
|
+
if line.startswith(" - ") and current_key:
|
|
34
|
+
data.setdefault(current_key, []).append(line[4:].strip().strip('"\''))
|
|
35
|
+
continue
|
|
36
|
+
if ":" in line:
|
|
37
|
+
key, value = line.split(":", 1)
|
|
38
|
+
current_key = key.strip()
|
|
39
|
+
value = value.strip()
|
|
40
|
+
if value == "":
|
|
41
|
+
data[current_key] = []
|
|
42
|
+
else:
|
|
43
|
+
data[current_key] = value.strip('"\'')
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def skill_summary(text: str) -> str:
|
|
48
|
+
fm = parse_frontmatter(text)
|
|
49
|
+
for key in ("description", "summary"):
|
|
50
|
+
if isinstance(fm.get(key), str) and fm[key]:
|
|
51
|
+
return str(fm[key])
|
|
52
|
+
match = re.search(r"^#\s+(.+)$", text, re.M)
|
|
53
|
+
if match:
|
|
54
|
+
return match.group(1).strip()
|
|
55
|
+
return "Keystone skill."
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def write_json(path: Path, data: dict) -> None:
|
|
59
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> int:
|
|
64
|
+
package = read_package()
|
|
65
|
+
skill_text = SKILL.read_text() if SKILL.exists() else ""
|
|
66
|
+
fm = parse_frontmatter(skill_text)
|
|
67
|
+
description = skill_summary(skill_text) if skill_text else package.get("description", "Keystone skill.")
|
|
68
|
+
version = package.get("version", "0.0.0")
|
|
69
|
+
license_name = package.get("license", "UNLICENSED")
|
|
70
|
+
keywords = package.get("keywords", ["keystone", "skill"])
|
|
71
|
+
|
|
72
|
+
plugin = {
|
|
73
|
+
"name": NAME,
|
|
74
|
+
"version": version,
|
|
75
|
+
"description": description,
|
|
76
|
+
"license": license_name,
|
|
77
|
+
"skills": [{"name": NAME, "path": "../skills/keystone/SKILL.md"}],
|
|
78
|
+
}
|
|
79
|
+
marketplace = {
|
|
80
|
+
"name": NAME,
|
|
81
|
+
"displayName": "Keystone",
|
|
82
|
+
"version": version,
|
|
83
|
+
"description": description,
|
|
84
|
+
"license": license_name,
|
|
85
|
+
"keywords": keywords,
|
|
86
|
+
"skills": [NAME],
|
|
87
|
+
}
|
|
88
|
+
if isinstance(fm.get("author"), str):
|
|
89
|
+
marketplace["author"] = fm["author"]
|
|
90
|
+
|
|
91
|
+
write_json(ROOT / ".claude-plugin" / "plugin.json", plugin)
|
|
92
|
+
write_json(ROOT / ".claude-plugin" / "marketplace.json", marketplace)
|
|
93
|
+
write_json(ROOT / ".codex-plugin" / "plugin.json", plugin)
|
|
94
|
+
write_json(ROOT / ".agents" / "plugins" / "marketplace.json", marketplace)
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
ALLOWLIST="$ROOT/packaging.allowlist"
|
|
6
|
+
DIST="$ROOT/dist"
|
|
7
|
+
ARCHIVE="$DIST/keystone.zip"
|
|
8
|
+
TMP_LIST="$DIST/keystone.files"
|
|
9
|
+
|
|
10
|
+
cd "$ROOT"
|
|
11
|
+
|
|
12
|
+
if [ ! -f "$ALLOWLIST" ]; then
|
|
13
|
+
echo "package-keystone: missing packaging.allowlist" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
python3 scripts/build-metadata.py
|
|
18
|
+
python3 scripts/validate-keystone.py
|
|
19
|
+
|
|
20
|
+
mkdir -p "$DIST"
|
|
21
|
+
: > "$TMP_LIST"
|
|
22
|
+
|
|
23
|
+
while IFS= read -r path || [ -n "$path" ]; do
|
|
24
|
+
case "$path" in
|
|
25
|
+
''|'#'*) continue ;;
|
|
26
|
+
esac
|
|
27
|
+
case "$path" in
|
|
28
|
+
docs|docs/*|plans|plans/*|maintainers|maintainers/*|index.html|styles.css|.git|.git/*|dist|dist/*|skill-engineering.md|*/skill-engineering.md|*.plan.md|*-plan.md|*.design.md|*-design.md|*/__pycache__/*|*.pyc|.DS_Store)
|
|
29
|
+
echo "package-keystone: forbidden allowlist entry: $path" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
;;
|
|
32
|
+
esac
|
|
33
|
+
if [ ! -e "$path" ]; then
|
|
34
|
+
echo "package-keystone: allowlisted path missing: $path" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
if [ -d "$path" ]; then
|
|
38
|
+
find "$path" -type f \
|
|
39
|
+
! -name '.DS_Store' \
|
|
40
|
+
! -name '*.pyc' \
|
|
41
|
+
! -path '*/__pycache__/*' \
|
|
42
|
+
! -path 'docs/*' \
|
|
43
|
+
! -path 'plans/*' \
|
|
44
|
+
! -path 'maintainers/*' \
|
|
45
|
+
! -name 'skill-engineering.md' \
|
|
46
|
+
! -name '*.plan.md' \
|
|
47
|
+
! -name '*-plan.md' \
|
|
48
|
+
! -name '*.design.md' \
|
|
49
|
+
! -name '*-design.md' >> "$TMP_LIST"
|
|
50
|
+
else
|
|
51
|
+
printf '%s\n' "$path" >> "$TMP_LIST"
|
|
52
|
+
fi
|
|
53
|
+
done < "$ALLOWLIST"
|
|
54
|
+
|
|
55
|
+
sort -u "$TMP_LIST" -o "$TMP_LIST"
|
|
56
|
+
rm -f "$ARCHIVE"
|
|
57
|
+
zip -q -X "$ARCHIVE" -@ < "$TMP_LIST"
|
|
58
|
+
python3 scripts/validate-package.py "$ARCHIVE"
|
|
59
|
+
echo "package-keystone: wrote $ARCHIVE"
|