vinta-ai-workflows 0.1.7
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/CHANGELOG.md +830 -0
- package/LICENSE +21 -0
- package/README.md +547 -0
- package/package.json +27 -0
- package/schemas/README.md +84 -0
- package/schemas/mcp-preflight-cache.v1.schema.json +50 -0
- package/schemas/prs-context-comments.v1.schema.json +51 -0
- package/schemas/prs-context-frontmatter.v1.schema.json +77 -0
- package/schemas/sub-agent.v1.schema.json +105 -0
- package/schemas/vinta-ai-workflows-config.v1.schema.json +283 -0
- package/skills/vinta-analyze-codebase/SKILL.md +325 -0
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +375 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/README.md +50 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/django/notes.md +52 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/medplum/notes.md +45 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/nextjs-app-router/notes.md +35 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/python-package/notes.md +31 -0
- package/skills/vinta-bootstrap-ai-tools/resources/stacks/typescript-monorepo/notes.md +32 -0
- package/skills/vinta-derive-skills/SKILL.md +200 -0
- package/skills/vinta-derive-skills/resources/amend-plan-template.md +355 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/add-one-off-script/SKILL.md +292 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/add-one-off-script/resources/one_off_script_base.py +602 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/add-one-off-script/resources/one_off_script_base.ts +624 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/create-qa-use-cases/SKILL.md +232 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/create-spec/SKILL.md +235 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/open-pr-from-context/SKILL.md +78 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/open-pr-from-context/scripts/open-pr.sh +357 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/plan-feature/SKILL.md +418 -0
- package/skills/vinta-derive-skills/resources/implement-plan-template.md +375 -0
- package/skills/vinta-derive-skills/resources/prs-context-template.md +91 -0
- package/skills/vinta-derive-skills/resources/systematic-debugging-mcp-tools.md +125 -0
- package/skills/vinta-derive-skills/resources/systematic-debugging-template.md +167 -0
- package/skills/vinta-derive-subagents/SKILL.md +192 -0
- package/skills/vinta-install-ai-tools-setup/SKILL.md +138 -0
- package/skills/vinta-install-ai-tools-setup/resources/setup-ai-tools.mjs +392 -0
- package/skills/vinta-migrate-plans-specs/SKILL.md +229 -0
- package/skills/vinta-sync-ai-tools/SKILL.md +184 -0
- package/skills/vinta-update-project-skills/SKILL.md +178 -0
- package/skills/vinta-write-agents-md/SKILL.md +192 -0
- package/vinta-ai-workflows.mjs +316 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// vinta-ai-workflows.mjs — install vinta-ai-workflows builder skills into a
|
|
3
|
+
// target project's per-vendor skill directory (Claude Code, Codex, Cursor,
|
|
4
|
+
// VS Code + Copilot). Project scope only; skills are namespaced with the
|
|
5
|
+
// `vinta-` prefix so they don't collide with the user's own project skills.
|
|
6
|
+
//
|
|
7
|
+
// Usage (after `npm i -D vinta-ai-workflows`):
|
|
8
|
+
// npx vinta-ai-workflows install --tool <name> [opts]
|
|
9
|
+
// npx vinta-ai-workflows update --tool <name> [opts] # uninstall + install
|
|
10
|
+
// npx vinta-ai-workflows uninstall --tool <name> [opts]
|
|
11
|
+
// npx vinta-ai-workflows list
|
|
12
|
+
//
|
|
13
|
+
// Or directly: node vinta-ai-workflows.mjs <cmd> [opts]
|
|
14
|
+
//
|
|
15
|
+
// Tools:
|
|
16
|
+
// claude-code .claude/skills/
|
|
17
|
+
// codex .agents/skills/ (official Codex path; walks up to repo root)
|
|
18
|
+
// cursor .cursor/skills/
|
|
19
|
+
// copilot .github/skills/
|
|
20
|
+
// agents .agents/skills/ universal — covers Codex + Cursor + Copilot
|
|
21
|
+
// all install into every vendor-specific path above
|
|
22
|
+
//
|
|
23
|
+
// Common options:
|
|
24
|
+
// --target <dir> Project root to install into. Defaults to $PWD.
|
|
25
|
+
// --skills <a,b,c> Subset of skills (default: all skills under ./skills).
|
|
26
|
+
// --copy Copy files instead of symlinking. Survives repo
|
|
27
|
+
// deletion but won't pick up upstream skill updates
|
|
28
|
+
// without re-running `update`.
|
|
29
|
+
// --dry-run, -n Show planned changes; touch nothing.
|
|
30
|
+
//
|
|
31
|
+
// The vinta- skills are one-shot bootstrappers — install, run them once via
|
|
32
|
+
// the AI tool, then `uninstall` to clean up. Use `update` to refresh the
|
|
33
|
+
// installed skills against the latest source (handy when this repo gets
|
|
34
|
+
// pulled with new versions and you copied rather than symlinked). See
|
|
35
|
+
// README.md for invocation per tool.
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
readdirSync, lstatSync, mkdirSync, rmSync, rmdirSync,
|
|
39
|
+
symlinkSync, unlinkSync, cpSync, writeFileSync, existsSync, realpathSync,
|
|
40
|
+
} from 'node:fs';
|
|
41
|
+
import { join, resolve, dirname, relative } from 'node:path';
|
|
42
|
+
import { fileURLToPath } from 'node:url';
|
|
43
|
+
|
|
44
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
const SKILLS_SRC = resolve(SCRIPT_DIR, 'skills');
|
|
46
|
+
const MARKER = '.installed-by-vinta-ai-workflows';
|
|
47
|
+
|
|
48
|
+
// ── Vendor target paths ──────────────────────────────────────────────────
|
|
49
|
+
//
|
|
50
|
+
// Sources (verified 2026-05):
|
|
51
|
+
// Claude Code — .claude/skills/ (native)
|
|
52
|
+
// Codex — .agents/skills/ (developers.openai.com/codex/skills,
|
|
53
|
+
// walks up to repo root)
|
|
54
|
+
// Cursor — .cursor/skills/ OR .agents/skills/ (cursor.com/docs/skills)
|
|
55
|
+
// Copilot — .github/skills/, .claude/skills/, .agents/skills/
|
|
56
|
+
// (code.visualstudio.com/docs/copilot/customization/agent-skills)
|
|
57
|
+
//
|
|
58
|
+
// `.agents/skills/` is the universal path — recognized by Codex, Cursor, and
|
|
59
|
+
// Copilot. We expose it as the `agents` virtual tool for one-shot installs.
|
|
60
|
+
|
|
61
|
+
const TOOLS = {
|
|
62
|
+
'claude-code': { project: '.claude/skills', label: 'Claude Code' },
|
|
63
|
+
codex: { project: '.agents/skills', label: 'OpenAI Codex' },
|
|
64
|
+
cursor: { project: '.cursor/skills', label: 'Cursor' },
|
|
65
|
+
copilot: { project: '.github/skills', label: 'VS Code + Copilot' },
|
|
66
|
+
agents: { project: '.agents/skills', label: 'Universal (Codex + Cursor + Copilot)' },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const TOOL_ALIASES = {
|
|
70
|
+
'claude-code': 'claude-code',
|
|
71
|
+
claude: 'claude-code',
|
|
72
|
+
codex: 'codex',
|
|
73
|
+
'openai-codex': 'codex',
|
|
74
|
+
cursor: 'cursor',
|
|
75
|
+
copilot: 'copilot',
|
|
76
|
+
vscode: 'copilot',
|
|
77
|
+
'vscode-copilot': 'copilot',
|
|
78
|
+
'github-copilot': 'copilot',
|
|
79
|
+
agents: 'agents',
|
|
80
|
+
universal: 'agents',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const ALL_TOOLS = ['claude-code', 'codex', 'cursor', 'copilot'];
|
|
84
|
+
|
|
85
|
+
// ── CLI parsing ──────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
function printHelp() {
|
|
88
|
+
console.log(`vinta-ai-workflows skills installer
|
|
89
|
+
|
|
90
|
+
Commands:
|
|
91
|
+
install Place skills under <target>/.<vendor>/skills/
|
|
92
|
+
update Re-install (uninstall + install) so latest source content lands
|
|
93
|
+
uninstall Remove skills (only artifacts created by this script)
|
|
94
|
+
list List available skills
|
|
95
|
+
|
|
96
|
+
Options:
|
|
97
|
+
--tool <name> claude-code | codex | cursor | copilot | agents | all
|
|
98
|
+
--target <dir> Project root (default: cwd)
|
|
99
|
+
--skills <a,b,c> Subset of skills
|
|
100
|
+
--copy Copy instead of symlink (recommended for shared repos
|
|
101
|
+
where contributors don't have this clone)
|
|
102
|
+
--dry-run, -n Plan only
|
|
103
|
+
--help, -h This help
|
|
104
|
+
|
|
105
|
+
Tip: --tool agents writes to .agents/skills/, which Codex, Cursor, and Copilot
|
|
106
|
+
all recognize. Use it to cover three tools with one install.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseArgs(argv) {
|
|
110
|
+
const opts = {
|
|
111
|
+
cmd: argv[0],
|
|
112
|
+
tool: null,
|
|
113
|
+
target: process.cwd(),
|
|
114
|
+
copy: false,
|
|
115
|
+
skills: null,
|
|
116
|
+
dryRun: false,
|
|
117
|
+
};
|
|
118
|
+
for (let i = 1; i < argv.length; i++) {
|
|
119
|
+
const a = argv[i];
|
|
120
|
+
const eq = (k) => a === k || a.startsWith(`${k}=`);
|
|
121
|
+
const val = (k) => (a.startsWith(`${k}=`) ? a.slice(k.length + 1) : argv[++i]);
|
|
122
|
+
if (eq('--tool')) opts.tool = val('--tool');
|
|
123
|
+
else if (eq('--target')) opts.target = resolve(val('--target'));
|
|
124
|
+
else if (a === '--copy') opts.copy = true;
|
|
125
|
+
else if (eq('--skills')) {
|
|
126
|
+
opts.skills = val('--skills').split(',').map((s) => s.trim()).filter(Boolean);
|
|
127
|
+
} else if (a === '--dry-run' || a === '-n') opts.dryRun = true;
|
|
128
|
+
else if (a === '--help' || a === '-h') { printHelp(); process.exit(0); }
|
|
129
|
+
else { console.error(`unknown arg: ${a}`); process.exit(2); }
|
|
130
|
+
}
|
|
131
|
+
return opts;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveTools(tool) {
|
|
135
|
+
if (!tool) { console.error('--tool required (or use "all")'); process.exit(2); }
|
|
136
|
+
if (tool === 'all') return ALL_TOOLS;
|
|
137
|
+
const canonical = TOOL_ALIASES[tool.toLowerCase()];
|
|
138
|
+
if (!canonical) {
|
|
139
|
+
const allowed = Object.keys(TOOL_ALIASES).sort().concat('all').join(', ');
|
|
140
|
+
console.error(`unknown tool "${tool}". Allowed: ${allowed}`);
|
|
141
|
+
process.exit(2);
|
|
142
|
+
}
|
|
143
|
+
return [canonical];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Skill discovery ──────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function listSkills() {
|
|
149
|
+
if (!existsSync(SKILLS_SRC)) {
|
|
150
|
+
console.error(`skills source missing: ${SKILLS_SRC}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
return readdirSync(SKILLS_SRC, { withFileTypes: true })
|
|
154
|
+
.filter((d) => d.isDirectory() && existsSync(join(SKILLS_SRC, d.name, 'SKILL.md')))
|
|
155
|
+
.map((d) => d.name)
|
|
156
|
+
.sort();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolveSkillSet(requested) {
|
|
160
|
+
const all = listSkills();
|
|
161
|
+
if (!requested?.length) return all;
|
|
162
|
+
const missing = requested.filter((s) => !all.includes(s));
|
|
163
|
+
if (missing.length) {
|
|
164
|
+
console.error(`unknown skills: ${missing.join(', ')}`);
|
|
165
|
+
console.error(`available: ${all.join(', ')}`);
|
|
166
|
+
process.exit(2);
|
|
167
|
+
}
|
|
168
|
+
return requested;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function targetDir(tool, target) {
|
|
172
|
+
return resolve(target, TOOLS[tool].project);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Install ──────────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
function install(opts) {
|
|
178
|
+
const tools = resolveTools(opts.tool);
|
|
179
|
+
const skills = resolveSkillSet(opts.skills);
|
|
180
|
+
|
|
181
|
+
// Dedupe by destination directory so `--tool all` + `agents` overlap
|
|
182
|
+
// doesn't double-install (codex and agents both write to .agents/skills/).
|
|
183
|
+
const seenBaseDirs = new Set();
|
|
184
|
+
|
|
185
|
+
for (const tool of tools) {
|
|
186
|
+
const baseDir = targetDir(tool, opts.target);
|
|
187
|
+
if (seenBaseDirs.has(baseDir)) {
|
|
188
|
+
console.log(`[${tool}] ${baseDir} already covered — skipping`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
seenBaseDirs.add(baseDir);
|
|
192
|
+
|
|
193
|
+
if (!opts.dryRun) mkdirSync(baseDir, { recursive: true });
|
|
194
|
+
|
|
195
|
+
for (const skill of skills) {
|
|
196
|
+
const src = join(SKILLS_SRC, skill);
|
|
197
|
+
const dest = join(baseDir, skill);
|
|
198
|
+
|
|
199
|
+
if (opts.dryRun) {
|
|
200
|
+
console.log(`[${tool}] would ${opts.copy ? 'copy' : 'symlink'} ${skill} → ${dest}`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const st = lstatSync(dest);
|
|
206
|
+
if (st.isSymbolicLink() || st.isFile()) unlinkSync(dest);
|
|
207
|
+
else if (st.isDirectory()) rmSync(dest, { recursive: true, force: true });
|
|
208
|
+
} catch { /* not present */ }
|
|
209
|
+
|
|
210
|
+
if (opts.copy) {
|
|
211
|
+
cpSync(src, dest, { recursive: true });
|
|
212
|
+
writeFileSync(
|
|
213
|
+
join(dest, MARKER),
|
|
214
|
+
`Installed by vinta-ai-workflows (vinta-ai-workflows.mjs)\nsource: ${src}\n`,
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
const rel = relative(dirname(dest), src);
|
|
218
|
+
symlinkSync(rel, dest, 'dir');
|
|
219
|
+
}
|
|
220
|
+
console.log(`[${tool}] installed ${skill} → ${dest}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Uninstall ────────────────────────────────────────────────────────────
|
|
226
|
+
//
|
|
227
|
+
// Safety: removes only
|
|
228
|
+
// - symlinks whose realpath is inside SKILLS_SRC (or dangling)
|
|
229
|
+
// - directories containing the MARKER file
|
|
230
|
+
// User-authored or hand-edited entries are left alone.
|
|
231
|
+
|
|
232
|
+
function uninstall(opts) {
|
|
233
|
+
const tools = resolveTools(opts.tool);
|
|
234
|
+
const skills = resolveSkillSet(opts.skills);
|
|
235
|
+
|
|
236
|
+
const seenBaseDirs = new Set();
|
|
237
|
+
|
|
238
|
+
for (const tool of tools) {
|
|
239
|
+
const baseDir = targetDir(tool, opts.target);
|
|
240
|
+
if (seenBaseDirs.has(baseDir)) continue;
|
|
241
|
+
seenBaseDirs.add(baseDir);
|
|
242
|
+
|
|
243
|
+
if (!existsSync(baseDir)) {
|
|
244
|
+
console.log(`[${tool}] ${baseDir} not present — nothing to do`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const skill of skills) {
|
|
249
|
+
const dest = join(baseDir, skill);
|
|
250
|
+
let st;
|
|
251
|
+
try { st = lstatSync(dest); } catch { continue; }
|
|
252
|
+
|
|
253
|
+
if (st.isSymbolicLink()) {
|
|
254
|
+
let real;
|
|
255
|
+
try { real = realpathSync(dest); } catch { real = null; }
|
|
256
|
+
if (real === null || real.startsWith(SKILLS_SRC)) {
|
|
257
|
+
if (opts.dryRun) console.log(`[${tool}] would remove symlink ${dest}`);
|
|
258
|
+
else { unlinkSync(dest); console.log(`[${tool}] uninstalled ${skill}`); }
|
|
259
|
+
} else {
|
|
260
|
+
console.warn(`[${tool}] ${dest} symlinks outside skills source — leaving`);
|
|
261
|
+
}
|
|
262
|
+
} else if (st.isDirectory()) {
|
|
263
|
+
if (existsSync(join(dest, MARKER))) {
|
|
264
|
+
if (opts.dryRun) console.log(`[${tool}] would remove ${dest}`);
|
|
265
|
+
else { rmSync(dest, { recursive: true, force: true }); console.log(`[${tool}] uninstalled ${skill}`); }
|
|
266
|
+
} else {
|
|
267
|
+
console.warn(`[${tool}] ${dest} lacks marker (hand-installed?) — leaving`);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
console.warn(`[${tool}] ${dest} unexpected entry type — leaving`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const remaining = readdirSync(baseDir);
|
|
276
|
+
if (remaining.length === 0) {
|
|
277
|
+
if (opts.dryRun) console.log(`[${tool}] would remove empty ${baseDir}`);
|
|
278
|
+
else { rmdirSync(baseDir); console.log(`[${tool}] removed empty ${baseDir}`); }
|
|
279
|
+
}
|
|
280
|
+
} catch { /* ignore */ }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Update (uninstall then install) ──────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
function update(opts) {
|
|
287
|
+
console.log('# update — phase 1: uninstall existing');
|
|
288
|
+
uninstall(opts);
|
|
289
|
+
console.log('# update — phase 2: install latest');
|
|
290
|
+
install(opts);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── List ─────────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
function list() {
|
|
296
|
+
const skills = listSkills();
|
|
297
|
+
console.log(`Skills available in ${SKILLS_SRC}:`);
|
|
298
|
+
for (const s of skills) console.log(` ${s}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── Main ─────────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
304
|
+
switch (opts.cmd) {
|
|
305
|
+
case 'install': install(opts); break;
|
|
306
|
+
case 'update': update(opts); break;
|
|
307
|
+
case 'uninstall': uninstall(opts); break;
|
|
308
|
+
case 'list': list(); break;
|
|
309
|
+
case undefined:
|
|
310
|
+
case '--help':
|
|
311
|
+
case '-h': printHelp(); break;
|
|
312
|
+
default:
|
|
313
|
+
console.error(`unknown command: ${opts.cmd}`);
|
|
314
|
+
printHelp();
|
|
315
|
+
process.exit(2);
|
|
316
|
+
}
|