cyber-aced 0.3.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/.claude-plugin/plugin.json +18 -0
- package/.plugin/pins.json +1 -0
- package/.plugin/plugin.json +18 -0
- package/agents/aced-case-judge.md +165 -0
- package/agents/aced-impl-judge.md +55 -0
- package/agents/aced-scenario-writer.md +76 -0
- package/agents/aced-spec-validator.md +66 -0
- package/governances/README.md +52 -0
- package/governances/agent-tool-output.md +86 -0
- package/governances/cli-resolution.md +102 -0
- package/governances/skill-design.md +258 -0
- package/governances/skill-repo-structure.md +173 -0
- package/package.json +44 -0
- package/readme.md +124 -0
- package/skills/aced-architect-impl/README.md +9 -0
- package/skills/aced-architect-impl/SKILL.md +73 -0
- package/skills/aced-builder-impl/README.md +14 -0
- package/skills/aced-builder-impl/SKILL.md +46 -0
- package/skills/aced-builder-spec/README.md +15 -0
- package/skills/aced-builder-spec/SKILL.md +130 -0
- package/skills/aced-fit/README.md +21 -0
- package/skills/aced-fit/SKILL.md +105 -0
- package/skills/aced-impl-producer/SKILL.md +75 -0
- package/skills/add-scenario/README.md +25 -0
- package/skills/add-scenario/SKILL.md +75 -0
- package/skills/bench/README.md +64 -0
- package/skills/bench/SKILL.md +199 -0
- package/skills/bench/scripts/bench-claude-code.mts +187 -0
- package/skills/bench/scripts/bench-compare.mts +369 -0
- package/skills/bench/scripts/bench-record.mts +133 -0
- package/skills/bench/scripts/bench.mts +930 -0
- package/skills/check-freshness/README.md +57 -0
- package/skills/check-freshness/SKILL.md +76 -0
- package/skills/check-freshness/scripts/check-freshness.mts +399 -0
- package/skills/compare/README.md +25 -0
- package/skills/compare/SKILL.md +87 -0
- package/skills/contribute-skill/README.md +25 -0
- package/skills/contribute-skill/SKILL.md +241 -0
- package/skills/contribute-skill/scripts/source.mts +188 -0
- package/skills/define-agent/README.md +40 -0
- package/skills/define-agent/SKILL.md +279 -0
- package/skills/define-command/README.md +36 -0
- package/skills/define-command/SKILL.md +174 -0
- package/skills/define-governance/SKILL.md +151 -0
- package/skills/define-skill/README.md +37 -0
- package/skills/define-skill/SKILL.md +200 -0
- package/skills/extract-situation/README.md +32 -0
- package/skills/extract-situation/SKILL.md +54 -0
- package/skills/extract-situation/scripts/extract-situation.mts +287 -0
- package/skills/improve/README.md +25 -0
- package/skills/improve/SKILL.md +100 -0
- package/skills/improve-skill/README.md +38 -0
- package/skills/improve-skill/SKILL.md +169 -0
- package/skills/improve-skill/references/check-definitions.md +215 -0
- package/skills/improve-skill/scripts/validate.mts +1033 -0
- package/skills/init-aced/README.md +5 -0
- package/skills/init-aced/SKILL.md +76 -0
- package/skills/init-aced/scripts/ensure-results-ignored.mts +117 -0
- package/skills/list-skills/README.md +19 -0
- package/skills/list-skills/SKILL.md +55 -0
- package/skills/list-skills/scripts/list-skills.mts +210 -0
- package/skills/manage/README.md +35 -0
- package/skills/manage/SKILL.md +65 -0
- package/skills/manage-model-runners/README.md +25 -0
- package/skills/manage-model-runners/SKILL.md +113 -0
- package/skills/manage-skill-dirs/README.md +17 -0
- package/skills/manage-skill-dirs/SKILL.md +54 -0
- package/skills/manage-skill-dirs/scripts/manage-skill-dirs.mts +328 -0
- package/skills/repair-private-skills/README.md +17 -0
- package/skills/repair-private-skills/SKILL.md +59 -0
- package/skills/repair-private-skills/scripts/repair-private-skills.mts +279 -0
- package/skills/report/README.md +25 -0
- package/skills/report/SKILL.md +74 -0
- package/skills/run/README.md +25 -0
- package/skills/run/SKILL.md +220 -0
- package/skills/skillify/README.md +25 -0
- package/skills/skillify/SKILL.md +141 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: contribute-skill
|
|
3
|
+
description: Use this skill when contributing local improvements to an installed skill back to its source repo via PR.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Contribute Skill
|
|
7
|
+
|
|
8
|
+
When you have improved a skill you installed from another repo, this skill guides you through contributing that improvement back to the source via a pull request.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- You modified a skill (global `~/.agents/skills/<name>/` or repo-internal `.agents/skills/<name>/`) and want to send the improvement upstream
|
|
13
|
+
- The skill was installed from a public repo tracked in `skills-lock.json`
|
|
14
|
+
|
|
15
|
+
Do NOT use for repo-native skills under this repo's `skills/<name>/` — if you're the author here, this repo IS the source.
|
|
16
|
+
|
|
17
|
+
## Source repository scope (required)
|
|
18
|
+
|
|
19
|
+
Only update files under the source repo's canonical skills tree:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
<repo-root>/skills/<skill-name>/
|
|
23
|
+
SKILL.md
|
|
24
|
+
scripts/ ← include when changed
|
|
25
|
+
… ← any other files in that skill folder
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Rules:
|
|
29
|
+
|
|
30
|
+
- **Always** map upstream paths to `skills/<skill-name>/…`, even when `skills-lock.json` `skillPath` points at `.agents/skills/…` or another layout in the consumer repo.
|
|
31
|
+
- **Never** update `.agents/skills/`, duplicate trees, or paths outside `skills/<skill-name>/` in the source repository.
|
|
32
|
+
- Include every changed file under that skill folder (e.g. `scripts/*.mjs`), not only `SKILL.md`.
|
|
33
|
+
- Never include `SKILL.local.md` — machine-local augmentations stay local.
|
|
34
|
+
|
|
35
|
+
Derive paths:
|
|
36
|
+
|
|
37
|
+
| Input | Upstream path |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| Lock key / folder name `skillify` | `skills/<skill-name>/` |
|
|
40
|
+
| `skillPath`: `.agents/skills/skillify/SKILL.md` | `skills/<skill-name>/SKILL.md` |
|
|
41
|
+
| `skillPath`: `skills/fix-security-pr/SKILL.md` | `skills/fix-security-pr/SKILL.md` |
|
|
42
|
+
|
|
43
|
+
## Steps
|
|
44
|
+
|
|
45
|
+
### 1. Identify the skill(s) to patch
|
|
46
|
+
|
|
47
|
+
From context or ask the user. Look up origin using this skill's own `source.mts` script, which resolves in a fixed order: the repo-local lock, then the global lock, then `npx skills find`, returning the FIRST hit:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
node "<this-skill's-dir>/scripts/source.mts" <skill-name> --format json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Output is JSON: `{ name, source, sourceUrl, skillPath, foundIn }`. Extract `source` (`owner/repo`). `foundIn` tells you which rung resolved it (`repo`, `global`, or `npx-skills`).
|
|
54
|
+
|
|
55
|
+
If the script exits non-zero (`foundIn: null`), fall back to reading the lockfiles by hand, in the same order:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. Repo-local (committed, version 1 schema):
|
|
59
|
+
jq '.skills["<skill-name>"]' skills-lock.json
|
|
60
|
+
|
|
61
|
+
# 2. Global (version 3 schema, has sourceUrl):
|
|
62
|
+
jq '.skills["<skill-name>"]' ~/.agents/.skill-lock.json
|
|
63
|
+
|
|
64
|
+
# 3. npx skills find <skill-name>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If none of the three resolve, ask the user for `owner/repo` and the skill folder name — never guess.
|
|
68
|
+
|
|
69
|
+
For multiple related skills (user confirms one PR), repeat file discovery for each `skills/<name>/` directory.
|
|
70
|
+
|
|
71
|
+
### 2. Find the local skill directory
|
|
72
|
+
|
|
73
|
+
| Kind | Local directory |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| Global | `~/.agents/skills/<name>/` |
|
|
76
|
+
| Repo internal | `.agents/skills/<name>/` |
|
|
77
|
+
|
|
78
|
+
Collect files to contribute (exclude machine-local files):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
find "<local-dir>" -type f ! -name 'SKILL.local.md' | sort
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Diff against source
|
|
85
|
+
|
|
86
|
+
For each local file, map to `skills/<skill-name>/<relative-path>` and compare to upstream on the default branch.
|
|
87
|
+
|
|
88
|
+
List upstream skill folder (optional sanity check):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
gh api "repos/<owner>/<repo>/contents/skills/<skill-name>?ref=<default-branch>" \
|
|
92
|
+
--jq '.[] | .path'
|
|
93
|
+
# If scripts/ exists, list nested files too:
|
|
94
|
+
gh api "repos/<owner>/<repo>/contents/skills/<skill-name>/scripts?ref=<default-branch>" \
|
|
95
|
+
--jq '.[]? | .path' 2>/dev/null
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Per-file diff:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
UPSTREAM="skills/<skill-name>/SKILL.md"
|
|
102
|
+
gh api "repos/<owner>/<repo>/contents/${UPSTREAM}" --jq '.content' | base64 -d > /tmp/upstream.md
|
|
103
|
+
diff /tmp/upstream.md "<local-dir>/SKILL.md"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **No diffs** across all mapped files → nothing to contribute; stop
|
|
107
|
+
- **Any diff** → show unified diffs and get user confirmation before proceeding
|
|
108
|
+
|
|
109
|
+
### 4. Check write access
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
gh api repos/<owner>/<repo> --jq '{push: .permissions.push, default: .default_branch}'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- `push: true` → branch on `owner/repo`
|
|
116
|
+
- `push: false` → `gh repo fork <owner>/<repo> --clone=false`, then use `<your-username>/<repo>`
|
|
117
|
+
|
|
118
|
+
### 5. Create branch and push (single commit — Git Data API)
|
|
119
|
+
|
|
120
|
+
Use the **Git Data API** so all files land in **one commit**. Do not use `PUT /contents/{path}` per file (that creates one commit per file).
|
|
121
|
+
|
|
122
|
+
Set variables:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
OWNER=<owner>
|
|
126
|
+
REPO=<repo>
|
|
127
|
+
REPO_FULL="${OWNER}/${REPO}"
|
|
128
|
+
DEFAULT_BRANCH=main # from step 4
|
|
129
|
+
BRANCH="patch/<skill-name>-<short-slug>" # or shared slug for multi-skill PR
|
|
130
|
+
MSG="fix(<skill-name>): <description>"
|
|
131
|
+
SKILL_NAME=<skill-name>
|
|
132
|
+
LOCAL_DIR="<absolute-local-skill-dir>"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### 5a. Create branch ref from default branch tip
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
BASE_SHA=$(gh api "repos/${REPO_FULL}/git/refs/heads/${DEFAULT_BRANCH}" --jq '.object.sha')
|
|
139
|
+
BASE_TREE=$(gh api "repos/${REPO_FULL}/git/commits/${BASE_SHA}" --jq '.tree.sha')
|
|
140
|
+
|
|
141
|
+
gh api "repos/${REPO_FULL}/git/refs" \
|
|
142
|
+
--method POST \
|
|
143
|
+
--field ref="refs/heads/${BRANCH}" \
|
|
144
|
+
--field sha="${BASE_SHA}"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If the branch already exists, skip creation and set `BASE_SHA` to the branch tip instead (for amending, prefer a fresh branch).
|
|
148
|
+
|
|
149
|
+
#### 5b. Create blobs and tree entries for each local file
|
|
150
|
+
|
|
151
|
+
Build a JSON array of tree items. Each local file maps to `skills/${SKILL_NAME}/<relative-path>`.
|
|
152
|
+
|
|
153
|
+
**Use temp files for `--input`**, not inline `$(jq …)`. Embedding file content in the shell argument hits `ARG_MAX` and fails with errors like `file name too long` on typical `SKILL.md` sizes.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
TREE_ITEMS='[]'
|
|
157
|
+
while IFS= read -r -d '' file; do
|
|
158
|
+
rel="${file#${LOCAL_DIR}/}"
|
|
159
|
+
upstream_path="skills/${SKILL_NAME}/${rel}"
|
|
160
|
+
|
|
161
|
+
jq -n --rawfile content "$file" '{content: $content, encoding: "utf-8"}' > /tmp/patch-blob-input.json
|
|
162
|
+
blob_sha=$(gh api "repos/${REPO_FULL}/git/blobs" \
|
|
163
|
+
--method POST \
|
|
164
|
+
--input /tmp/patch-blob-input.json \
|
|
165
|
+
--jq '.sha')
|
|
166
|
+
|
|
167
|
+
TREE_ITEMS=$(jq -n \
|
|
168
|
+
--argjson items "$TREE_ITEMS" \
|
|
169
|
+
--arg path "$upstream_path" \
|
|
170
|
+
--arg sha "$blob_sha" \
|
|
171
|
+
'$items + [{path: $path, mode: "100644", type: "blob", sha: $sha}]')
|
|
172
|
+
done < <(find "$LOCAL_DIR" -type f ! -name 'SKILL.local.md' -print0)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Repeat the loop for additional skills in the same PR (append to `TREE_ITEMS` with each skill's `SKILL_NAME` and `LOCAL_DIR`).
|
|
176
|
+
|
|
177
|
+
#### 5c. Create tree, commit, update branch ref
|
|
178
|
+
|
|
179
|
+
Write tree and commit payloads to temp files as well (large multi-skill `TREE_ITEMS` can also exceed shell limits):
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
jq -n \
|
|
183
|
+
--arg base "$BASE_TREE" \
|
|
184
|
+
--argjson tree "$TREE_ITEMS" \
|
|
185
|
+
'{base_tree: $base, tree: $tree}' > /tmp/patch-tree-input.json
|
|
186
|
+
NEW_TREE=$(gh api "repos/${REPO_FULL}/git/trees" \
|
|
187
|
+
--method POST \
|
|
188
|
+
--input /tmp/patch-tree-input.json \
|
|
189
|
+
--jq '.sha')
|
|
190
|
+
|
|
191
|
+
jq -n \
|
|
192
|
+
--arg msg "$MSG" \
|
|
193
|
+
--arg tree "$NEW_TREE" \
|
|
194
|
+
--arg parent "$BASE_SHA" \
|
|
195
|
+
'{message: $msg, tree: $tree, parents: [$parent]}' > /tmp/patch-commit-input.json
|
|
196
|
+
COMMIT_SHA=$(gh api "repos/${REPO_FULL}/git/commits" \
|
|
197
|
+
--method POST \
|
|
198
|
+
--input /tmp/patch-commit-input.json \
|
|
199
|
+
--jq '.sha')
|
|
200
|
+
|
|
201
|
+
gh api "repos/${REPO_FULL}/git/refs/heads/${BRANCH}" \
|
|
202
|
+
--method PATCH \
|
|
203
|
+
--field sha="${COMMIT_SHA}"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Result: exactly **one commit** on the patch branch containing all updated paths under `skills/<skill-name>/`.
|
|
207
|
+
|
|
208
|
+
### 6. Open PR
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
gh pr create \
|
|
212
|
+
--repo "${REPO_FULL}" \
|
|
213
|
+
--base "${DEFAULT_BRANCH}" \
|
|
214
|
+
--head "${BRANCH}" \
|
|
215
|
+
--title "fix(<skill-name>): <description>" \
|
|
216
|
+
--body "$(cat <<'EOF'
|
|
217
|
+
## Summary
|
|
218
|
+
|
|
219
|
+
<what changed and why — only under `skills/<skill-name>/`>
|
|
220
|
+
|
|
221
|
+
## Test plan
|
|
222
|
+
|
|
223
|
+
- [ ] Run improve-skill / `audit validate` against the updated skill(s)
|
|
224
|
+
- [ ] Confirm no files outside `skills/` were modified
|
|
225
|
+
|
|
226
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
227
|
+
EOF
|
|
228
|
+
)"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 7. Report
|
|
232
|
+
|
|
233
|
+
Output the PR URL. After merge, run `npx skills update` in the consumer repo to refresh `skills-lock.json` hashes.
|
|
234
|
+
|
|
235
|
+
## What NOT to do
|
|
236
|
+
|
|
237
|
+
- Do not include `SKILL.local.md` content in the PR
|
|
238
|
+
- Do not push without showing diffs and getting user confirmation
|
|
239
|
+
- Do not create a PR if every mapped file is identical to upstream
|
|
240
|
+
- Do not update `.agents/skills/` or any path outside `skills/<skill-name>/` in the source repo
|
|
241
|
+
- Do not use Contents API `PUT` per file (multi-commit noise); use Git Data API (section 5)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// source — the concrete skill-source-resolution engine for contribute-skill. Given a skill name,
|
|
3
|
+
// answers where its upstream repo lives so the skill's "Locating the source repo" step never
|
|
4
|
+
// guesses: it walks a fixed precedence ladder — repo-local lock, then global lock, then
|
|
5
|
+
// `npx skills find` — and returns the FIRST hit; nothing downstream of a hit is consulted. When
|
|
6
|
+
// none of the three resolve, every field comes back null so the caller surfaces the gap to the
|
|
7
|
+
// user instead of fabricating an owner/repo.
|
|
8
|
+
//
|
|
9
|
+
// Resolution order (frozen, do not reorder):
|
|
10
|
+
// 1. <root>/skills-lock.json — repo-local lock (version 1 schema)
|
|
11
|
+
// 2. <homeDir>/.agents/.skill-lock.json — global lock (version 3 schema, carries sourceUrl)
|
|
12
|
+
// 3. `npx --yes skills find <skillName>` — shells out, parses "owner/repo@skillName" hits
|
|
13
|
+
// 4. not found — source/sourceUrl/skillPath/foundIn all null
|
|
14
|
+
//
|
|
15
|
+
// Pure functions are exported for node:test; running the file directly drives the CLI. No deps
|
|
16
|
+
// (the repo's node-≥23.6 convention). Imports node:* only.
|
|
17
|
+
|
|
18
|
+
import { spawnSync } from 'node:child_process'
|
|
19
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
20
|
+
import { homedir } from 'node:os'
|
|
21
|
+
import { join } from 'node:path'
|
|
22
|
+
import { pathToFileURL } from 'node:url'
|
|
23
|
+
|
|
24
|
+
export interface SkillSourceResult {
|
|
25
|
+
name: string
|
|
26
|
+
source: string | null
|
|
27
|
+
sourceUrl: string | null
|
|
28
|
+
skillPath: string | null
|
|
29
|
+
foundIn: 'repo' | 'global' | 'npx-skills' | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type NpxFindFn = (skillName: string) => string
|
|
33
|
+
|
|
34
|
+
// ── lockfile reads ──
|
|
35
|
+
|
|
36
|
+
export function readLock(lockPath: string): Record<string, unknown> | null {
|
|
37
|
+
if (!existsSync(lockPath)) return null
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(lockPath, 'utf8'))
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── npx skills find output parsing ──
|
|
46
|
+
|
|
47
|
+
// Strips ANSI color codes, then scans for "owner/repo@skillName" tokens; returns the first whose
|
|
48
|
+
// found name equals skillName.
|
|
49
|
+
export function parseNpxSkillsOutput(raw: string, skillName: string): { source: string } | null {
|
|
50
|
+
const ansiRe = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g')
|
|
51
|
+
const clean = raw.replace(ansiRe, '')
|
|
52
|
+
const regex = /([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)@([a-zA-Z0-9_-]+)/g
|
|
53
|
+
let match: RegExpExecArray | null
|
|
54
|
+
while ((match = regex.exec(clean)) !== null) {
|
|
55
|
+
const [, ownerRepo, foundName] = match
|
|
56
|
+
if (foundName === skillName) return { source: ownerRepo! }
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Real shellout: `npx --yes skills find <skillName>`, combined stdout+stderr, empty on any
|
|
62
|
+
// failure (npx missing, timeout, non-zero exit with no parseable output).
|
|
63
|
+
export function defaultNpxFind(skillName: string): string {
|
|
64
|
+
try {
|
|
65
|
+
const result = spawnSync('npx', ['--yes', 'skills', 'find', skillName], {
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
timeout: 4_000,
|
|
68
|
+
env: { ...process.env, NO_COLOR: '1', CI: '1' },
|
|
69
|
+
})
|
|
70
|
+
return (result.stdout ?? '') + (result.stderr ?? '')
|
|
71
|
+
} catch {
|
|
72
|
+
return ''
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── resolution ladder ──
|
|
77
|
+
|
|
78
|
+
// Resolves a skill's source repo by walking repo-local lock -> global lock -> npx skills find,
|
|
79
|
+
// returning the FIRST hit (nothing downstream of a hit is consulted). `homeDir` and `npxFind` are
|
|
80
|
+
// injectable for deterministic node:test coverage; production callers can omit both.
|
|
81
|
+
export function findSkillSource(
|
|
82
|
+
skillName: string,
|
|
83
|
+
root: string,
|
|
84
|
+
homeDir: string = homedir(),
|
|
85
|
+
npxFind: NpxFindFn = defaultNpxFind,
|
|
86
|
+
): SkillSourceResult {
|
|
87
|
+
const repoLock = readLock(join(root, 'skills-lock.json'))
|
|
88
|
+
if (repoLock) {
|
|
89
|
+
const entry = (repoLock as { skills?: Record<string, Record<string, string>> }).skills?.[skillName]
|
|
90
|
+
if (entry?.source) {
|
|
91
|
+
return {
|
|
92
|
+
name: skillName,
|
|
93
|
+
source: entry.source,
|
|
94
|
+
sourceUrl: `https://github.com/${entry.source}`,
|
|
95
|
+
skillPath: entry.skillPath ?? null,
|
|
96
|
+
foundIn: 'repo',
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const globalLock = readLock(join(homeDir, '.agents', '.skill-lock.json'))
|
|
102
|
+
if (globalLock) {
|
|
103
|
+
const entry = (globalLock as { skills?: Record<string, Record<string, string>> }).skills?.[skillName]
|
|
104
|
+
if (entry?.source) {
|
|
105
|
+
return {
|
|
106
|
+
name: skillName,
|
|
107
|
+
source: entry.source,
|
|
108
|
+
sourceUrl: entry.sourceUrl ?? `https://github.com/${entry.source}`,
|
|
109
|
+
skillPath: entry.skillPath ?? null,
|
|
110
|
+
foundIn: 'global',
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const out = npxFind(skillName)
|
|
116
|
+
const parsed = parseNpxSkillsOutput(out, skillName)
|
|
117
|
+
if (parsed) {
|
|
118
|
+
return {
|
|
119
|
+
name: skillName,
|
|
120
|
+
source: parsed.source,
|
|
121
|
+
sourceUrl: `https://github.com/${parsed.source}`,
|
|
122
|
+
skillPath: `skills/${skillName}/SKILL.md`,
|
|
123
|
+
foundIn: 'npx-skills',
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
name: skillName,
|
|
129
|
+
source: null,
|
|
130
|
+
sourceUrl: null,
|
|
131
|
+
skillPath: null,
|
|
132
|
+
foundIn: null,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── CLI ──
|
|
137
|
+
|
|
138
|
+
function flag(argv: string[], name: string): string | undefined {
|
|
139
|
+
const i = argv.indexOf(name)
|
|
140
|
+
return i === -1 ? undefined : argv[i + 1]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The lone positional arg: any token that isn't a known flag or a known flag's value.
|
|
144
|
+
function positional(argv: string[]): string | undefined {
|
|
145
|
+
const consumed = new Set<number>()
|
|
146
|
+
for (const name of ['--root', '--format']) {
|
|
147
|
+
const i = argv.indexOf(name)
|
|
148
|
+
if (i !== -1) {
|
|
149
|
+
consumed.add(i)
|
|
150
|
+
consumed.add(i + 1)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (let i = 0; i < argv.length; i++) {
|
|
154
|
+
if (!consumed.has(i)) return argv[i]
|
|
155
|
+
}
|
|
156
|
+
return undefined
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function main(argv: string[]): number {
|
|
160
|
+
const root = flag(argv, '--root') ?? '.'
|
|
161
|
+
const format = flag(argv, '--format') ?? 'text'
|
|
162
|
+
const skillName = positional(argv)
|
|
163
|
+
|
|
164
|
+
if (!skillName) {
|
|
165
|
+
process.stdout.write('usage: source <skill-name> [--root <path>] [--format json|text]\n')
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const result = findSkillSource(skillName, root)
|
|
170
|
+
|
|
171
|
+
if (format === 'json') {
|
|
172
|
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
|
173
|
+
} else {
|
|
174
|
+
if (result.foundIn) {
|
|
175
|
+
process.stdout.write(`${result.name}: ${result.source} (found in ${result.foundIn})\n`)
|
|
176
|
+
if (result.skillPath) process.stdout.write(` skillPath: ${result.skillPath}\n`)
|
|
177
|
+
process.stdout.write(` sourceUrl: ${result.sourceUrl}\n`)
|
|
178
|
+
} else {
|
|
179
|
+
process.stdout.write(`${result.name}: not found in repo lock, global lock, or npx skills find\n`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result.foundIn ? 0 : 1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
187
|
+
process.exit(main(process.argv.slice(2)))
|
|
188
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# define-agent
|
|
2
|
+
|
|
3
|
+
Create or improve an agent definition — a named, reusable role that can be delegated as a subagent, loaded in-context as a persona, or both.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
Use this skill when you want to author a new agent definition or improve an existing one.
|
|
8
|
+
|
|
9
|
+
Good triggers include:
|
|
10
|
+
|
|
11
|
+
- Building a conductor, orchestrator, or persona for a workflow
|
|
12
|
+
- Adding a reusable role to a plugin or project
|
|
13
|
+
- Deciding between a delegated worker and an interactive in-context persona
|
|
14
|
+
- Scaffolding the canonical file + runtime symlinks + companion command in one pass
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
The skill walks you through three decisions before writing anything:
|
|
19
|
+
|
|
20
|
+
1. **Mode** — Delegated (subagent only), Invokable (dual-mode: subagent + in-context via a companion command), or In-context only
|
|
21
|
+
2. **Placement** — user-global (`~/.agents/agents/`), project (`.agents/agents/`), or inside a plugin (`plugins/<name>/agents/`)
|
|
22
|
+
3. **Runtimes** — which runtime symlinks to create (Claude Code, Cursor, Codex)
|
|
23
|
+
|
|
24
|
+
It then collects the role, responsibilities, output format, human-in-the-loop rules, and out-of-scope list — drafts the agent definition, runs quality checks (F1–F9, B1–B8), and for Invokable mode scaffolds a thin companion command file that loads the agent into the current session.
|
|
25
|
+
|
|
26
|
+
## Dual-mode (Invokable) pattern
|
|
27
|
+
|
|
28
|
+
The Invokable mode implements the **dual-mode persona** pattern:
|
|
29
|
+
|
|
30
|
+
- The agent definition file is the single source of truth for the role
|
|
31
|
+
- A companion command reads that file into the current context at session start
|
|
32
|
+
- The same role body backs both delegation (subagent spawn) and in-context adoption (interactive steering)
|
|
33
|
+
|
|
34
|
+
This lets you hand off work to the agent autonomously, or step into its role yourself and operate at the same level.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx skills add cyberuni/cyberplace --skill aced/define-agent
|
|
40
|
+
```
|