skillshop 2.0.0 → 2.2.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.
Files changed (62) hide show
  1. package/README.md +273 -100
  2. package/agents-content/000_communication-writing.block.md +11 -0
  3. package/agents-content/001_external-actions.block.md +9 -0
  4. package/agents-content/AGENTS.md +11 -0
  5. package/package.json +6 -3
  6. package/skills-content/architecture-scanner/SKILL.md +274 -0
  7. package/skills-content/architecture-scanner/files.json +10 -0
  8. package/skills-content/architecture-scanner/scan.sh +823 -0
  9. package/skills-content/change-review/SKILL.md +76 -0
  10. package/skills-content/change-review/agents/openai.yaml +4 -0
  11. package/skills-content/changelog/SKILL.md +99 -0
  12. package/skills-content/changeset/changeset.md +80 -0
  13. package/skills-content/code-buddy/SKILL.md +136 -0
  14. package/skills-content/design-system-catalog/SKILL.md +106 -0
  15. package/skills-content/design-system-catalog/extract-components.mjs +441 -0
  16. package/skills-content/design-system-catalog/generate-catalog.sh +255 -0
  17. package/skills-content/flare/SKILL.md +96 -0
  18. package/skills-content/grilling/SKILL.md +31 -0
  19. package/skills-content/install-sigmap/SKILL.md +97 -0
  20. package/skills-content/one-shot/SKILL.md +51 -0
  21. package/skills-content/release/SKILL.md +128 -0
  22. package/skills-content/shadow-clone/SKILL.md +228 -0
  23. package/skills-content/shadow-clone/extract-signatures.mjs +202 -0
  24. package/skills-content/shadow-clone/scan-repo.mjs +343 -0
  25. package/skills-content/ship/SKILL.md +95 -0
  26. package/skills-content/shots/SKILL.md +89 -0
  27. package/skills-content/shots/agents/openai.yaml +4 -0
  28. package/skills-content/skillshop/SKILL.md +157 -0
  29. package/skills-content/skillz/SKILL.md +15 -0
  30. package/skills-content/transcript-clean/SKILL.md +55 -0
  31. package/skills-content/transcript-edit/SKILL.md +80 -0
  32. package/skills-content/vitest/GENERATION.md +5 -0
  33. package/skills-content/vitest/SKILL.md +52 -0
  34. package/skills-content/vitest/references/advanced-environments.md +264 -0
  35. package/skills-content/vitest/references/advanced-projects.md +300 -0
  36. package/skills-content/vitest/references/advanced-type-testing.md +237 -0
  37. package/skills-content/vitest/references/advanced-vi.md +249 -0
  38. package/skills-content/vitest/references/core-cli.md +166 -0
  39. package/skills-content/vitest/references/core-config.md +174 -0
  40. package/skills-content/vitest/references/core-describe.md +193 -0
  41. package/skills-content/vitest/references/core-expect.md +219 -0
  42. package/skills-content/vitest/references/core-hooks.md +244 -0
  43. package/skills-content/vitest/references/core-test-api.md +233 -0
  44. package/skills-content/vitest/references/features-concurrency.md +250 -0
  45. package/skills-content/vitest/references/features-context.md +238 -0
  46. package/skills-content/vitest/references/features-coverage.md +207 -0
  47. package/skills-content/vitest/references/features-filtering.md +211 -0
  48. package/skills-content/vitest/references/features-mocking.md +265 -0
  49. package/skills-content/vitest/references/features-snapshots.md +207 -0
  50. package/skills-content/workleaf/SKILL.md +77 -0
  51. package/skills-content/worklog/SKILL.md +145 -0
  52. package/skills-content/worklog/references/summary-format.md +114 -0
  53. package/skills-content/worktree/SKILL.md +71 -0
  54. package/src/agents-instructions.js +256 -0
  55. package/src/cli.js +87 -23
  56. package/src/import.js +12 -7
  57. package/src/index.js +1 -0
  58. package/src/legacy-agents-content.js +36 -0
  59. package/src/legacy-cli.js +1822 -0
  60. package/src/library.js +9 -0
  61. package/src/specifier.js +11 -0
  62. package/src/user-install.js +48 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * extract-signatures.mjs — Extracts exported symbols and structural signatures
5
+ * from source files to help shadow-clone understand the public API surface
6
+ * without reading every line.
7
+ *
8
+ * Supports: JavaScript/TypeScript, Python, Go, Rust, Ruby (basic heuristics).
9
+ *
10
+ * Usage:
11
+ * node extract-signatures.mjs <file-or-directory> [--ext .ts,.tsx,.js]
12
+ *
13
+ * Outputs Markdown to stdout with extracted signatures grouped by file.
14
+ */
15
+
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import { execSync } from "node:child_process";
19
+
20
+ const args = process.argv.slice(2);
21
+
22
+ function flag(name) {
23
+ const i = args.indexOf(name);
24
+ if (i === -1) return undefined;
25
+ return args.splice(i, 2)[1];
26
+ }
27
+
28
+ const extFilter = flag("--ext")?.split(",") ?? null;
29
+ const target = path.resolve(args[0] || ".");
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // File collection
33
+ // ---------------------------------------------------------------------------
34
+
35
+ function collectFiles(dir) {
36
+ try {
37
+ const out = execSync("git ls-files --cached --others --exclude-standard", {
38
+ cwd: dir, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024,
39
+ stdio: ["pipe", "pipe", "pipe"],
40
+ });
41
+ return out.split("\n").map(l => l.trim()).filter(Boolean).map(f => path.join(dir, f));
42
+ } catch {
43
+ return walkDir(dir);
44
+ }
45
+ }
46
+
47
+ function walkDir(dir) {
48
+ const skip = new Set(["node_modules", "vendor", "__pycache__", "target", "dist", "build", ".git"]);
49
+ const results = [];
50
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
51
+ if (entry.name.startsWith(".") && entry.name !== ".") continue;
52
+ if (skip.has(entry.name)) continue;
53
+ const full = path.join(dir, entry.name);
54
+ if (entry.isDirectory()) results.push(...walkDir(full));
55
+ else results.push(full);
56
+ }
57
+ return results;
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Signature extractors
62
+ // ---------------------------------------------------------------------------
63
+
64
+ const extractors = {
65
+ // JS / TS
66
+ ".js": extractJS, ".jsx": extractJS, ".ts": extractJS, ".tsx": extractJS, ".mjs": extractJS,
67
+ // Python
68
+ ".py": extractPython,
69
+ // Go
70
+ ".go": extractGo,
71
+ // Rust
72
+ ".rs": extractRust,
73
+ // Ruby
74
+ ".rb": extractRuby,
75
+ };
76
+
77
+ function extractJS(content) {
78
+ const sigs = [];
79
+ const patterns = [
80
+ // export function / export default function
81
+ /^export\s+(default\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm,
82
+ // export const/let/var
83
+ /^export\s+(?:const|let|var)\s+(\w+)\s*(?::\s*([^=]+?))?\s*=/gm,
84
+ // export class / export default class
85
+ /^export\s+(default\s+)?class\s+(\w+)/gm,
86
+ // export interface / export type (TS)
87
+ /^export\s+(?:interface|type)\s+(\w+)/gm,
88
+ // module.exports
89
+ /^module\.exports\s*=\s*(\{[\s\S]*?\}|[\w.]+)/gm,
90
+ // Named exports block: export { ... }
91
+ /^export\s+\{([^}]+)\}/gm,
92
+ ];
93
+ for (const p of patterns) {
94
+ for (const m of content.matchAll(p)) {
95
+ sigs.push(m[0].trim().slice(0, 200));
96
+ }
97
+ }
98
+ // React components: const X = (props) => / function X(
99
+ for (const m of content.matchAll(/^(?:export\s+)?(?:const|function)\s+([A-Z]\w+)\s*(?:=\s*(?:\([^)]*\)|[^=])\s*=>|[\s(])/gm)) {
100
+ const line = m[0].trim().slice(0, 200);
101
+ if (!sigs.includes(line)) sigs.push(line);
102
+ }
103
+ return sigs;
104
+ }
105
+
106
+ function extractPython(content) {
107
+ const sigs = [];
108
+ for (const m of content.matchAll(/^(?:class|def|async\s+def)\s+\w+[^:]*:/gm)) {
109
+ sigs.push(m[0].trim().slice(0, 200));
110
+ }
111
+ // Top-level assignments that look like exports
112
+ for (const m of content.matchAll(/^[A-Z_][A-Z_0-9]*\s*=\s*.+/gm)) {
113
+ sigs.push(m[0].trim().slice(0, 200));
114
+ }
115
+ return sigs;
116
+ }
117
+
118
+ function extractGo(content) {
119
+ const sigs = [];
120
+ // Exported functions/methods (start with uppercase)
121
+ for (const m of content.matchAll(/^func\s+(?:\([^)]+\)\s+)?([A-Z]\w*)\s*\([^)]*\)[^{]*/gm)) {
122
+ sigs.push(m[0].trim().slice(0, 200));
123
+ }
124
+ // Exported types
125
+ for (const m of content.matchAll(/^type\s+([A-Z]\w*)\s+(?:struct|interface)\s*\{/gm)) {
126
+ sigs.push(m[0].trim().slice(0, 200));
127
+ }
128
+ return sigs;
129
+ }
130
+
131
+ function extractRust(content) {
132
+ const sigs = [];
133
+ for (const m of content.matchAll(/^pub\s+(?:async\s+)?(?:fn|struct|enum|trait|type|const|static|mod)\s+\w+[^{;]*/gm)) {
134
+ sigs.push(m[0].trim().slice(0, 200));
135
+ }
136
+ return sigs;
137
+ }
138
+
139
+ function extractRuby(content) {
140
+ const sigs = [];
141
+ for (const m of content.matchAll(/^\s*(?:class|module|def)\s+\S+/gm)) {
142
+ sigs.push(m[0].trim().slice(0, 200));
143
+ }
144
+ return sigs;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Main
149
+ // ---------------------------------------------------------------------------
150
+
151
+ const stat = fs.statSync(target);
152
+ let files;
153
+ if (stat.isDirectory()) {
154
+ files = collectFiles(target);
155
+ } else {
156
+ files = [target];
157
+ }
158
+
159
+ // Filter by extension
160
+ if (extFilter) {
161
+ files = files.filter(f => extFilter.includes(path.extname(f)));
162
+ } else {
163
+ files = files.filter(f => extractors[path.extname(f)]);
164
+ }
165
+
166
+ // Sort for deterministic output
167
+ files.sort();
168
+
169
+ const output = [];
170
+ output.push("# Extracted Signatures");
171
+ output.push("");
172
+
173
+ let totalSigs = 0;
174
+
175
+ for (const file of files) {
176
+ const ext = path.extname(file);
177
+ const extractor = extractors[ext];
178
+ if (!extractor) continue;
179
+
180
+ try {
181
+ const stat = fs.statSync(file);
182
+ if (stat.size > 512_000) continue; // skip large files
183
+
184
+ const content = fs.readFileSync(file, "utf-8");
185
+ const sigs = extractor(content);
186
+ if (sigs.length === 0) continue;
187
+
188
+ totalSigs += sigs.length;
189
+ const rel = path.relative(target, file) || path.basename(file);
190
+ output.push(`## \`${rel}\``);
191
+ output.push("");
192
+ for (const sig of sigs) {
193
+ output.push(`- \`${sig}\``);
194
+ }
195
+ output.push("");
196
+ } catch { /* skip unreadable files */ }
197
+ }
198
+
199
+ output.push(`---`);
200
+ output.push(`**Total:** ${totalSigs} signatures across ${files.length} files`);
201
+
202
+ console.log(output.join("\n"));
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * scan-repo.mjs — Deterministic repository scanner for the shadow-clone skill.
5
+ *
6
+ * Produces a structured Markdown report of a local repository including:
7
+ * - File tree (respecting .gitignore via `git ls-files`)
8
+ * - Per-extension statistics (file count, lines of code)
9
+ * - Key/manifest files detected
10
+ * - Dependency summaries extracted from manifest files
11
+ * - Entry-point candidates
12
+ *
13
+ * Usage:
14
+ * node scan-repo.mjs <directory> [--focus <glob>] [--max-depth <n>] [--json]
15
+ *
16
+ * Outputs Markdown to stdout (or JSON with --json).
17
+ */
18
+
19
+ import { execSync } from "node:child_process";
20
+ import fs from "node:fs";
21
+ import path from "node:path";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // CLI args
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const args = process.argv.slice(2);
28
+ function flag(name) {
29
+ const i = args.indexOf(name);
30
+ if (i === -1) return undefined;
31
+ return args.splice(i, 2)[1];
32
+ }
33
+ function bool(name) {
34
+ const i = args.indexOf(name);
35
+ if (i === -1) return false;
36
+ args.splice(i, 1);
37
+ return true;
38
+ }
39
+
40
+ const jsonOutput = bool("--json");
41
+ const focusGlob = flag("--focus");
42
+ const maxDepth = Number(flag("--max-depth")) || Infinity;
43
+ const targetDir = path.resolve(args[0] || ".");
44
+
45
+ if (!fs.existsSync(targetDir)) {
46
+ console.error(`Error: directory does not exist: ${targetDir}`);
47
+ process.exit(1);
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // File listing — prefer git ls-files, fallback to recursive readdir
52
+ // ---------------------------------------------------------------------------
53
+
54
+ function listFilesGit(dir) {
55
+ try {
56
+ const out = execSync("git ls-files --cached --others --exclude-standard", {
57
+ cwd: dir,
58
+ encoding: "utf-8",
59
+ maxBuffer: 50 * 1024 * 1024,
60
+ stdio: ["pipe", "pipe", "pipe"],
61
+ });
62
+ return out
63
+ .split("\n")
64
+ .map((l) => l.trim())
65
+ .filter(Boolean);
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ function listFilesFallback(dir, prefix = "") {
72
+ const results = [];
73
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
74
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "vendor" || entry.name === "__pycache__" || entry.name === "target" || entry.name === "dist" || entry.name === "build") continue;
75
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
76
+ if (entry.isDirectory()) {
77
+ results.push(...listFilesFallback(path.join(dir, entry.name), rel));
78
+ } else {
79
+ results.push(rel);
80
+ }
81
+ }
82
+ return results;
83
+ }
84
+
85
+ let allFiles = listFilesGit(targetDir) ?? listFilesFallback(targetDir);
86
+
87
+ // Apply focus filter
88
+ if (focusGlob) {
89
+ const pattern = new RegExp(
90
+ "^" +
91
+ focusGlob
92
+ .replace(/\./g, "\\.")
93
+ .replace(/\*\*/g, "⚡")
94
+ .replace(/\*/g, "[^/]*")
95
+ .replace(/⚡/g, ".*") +
96
+ "$"
97
+ );
98
+ allFiles = allFiles.filter((f) => pattern.test(f));
99
+ }
100
+
101
+ // Apply max-depth filter
102
+ if (maxDepth < Infinity) {
103
+ allFiles = allFiles.filter((f) => f.split("/").length <= maxDepth);
104
+ }
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // Analysis
108
+ // ---------------------------------------------------------------------------
109
+
110
+ const MANIFEST_FILES = new Set([
111
+ "package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
112
+ "Cargo.toml", "Cargo.lock",
113
+ "go.mod", "go.sum",
114
+ "requirements.txt", "setup.py", "setup.cfg", "pyproject.toml", "Pipfile",
115
+ "Gemfile", "Gemfile.lock",
116
+ "composer.json",
117
+ "build.gradle", "build.gradle.kts", "pom.xml",
118
+ "Makefile", "CMakeLists.txt",
119
+ "Dockerfile", "docker-compose.yml", "docker-compose.yaml",
120
+ ".env.example",
121
+ "tsconfig.json", "jsconfig.json",
122
+ "vite.config.js", "vite.config.ts", "webpack.config.js", "rollup.config.js",
123
+ "next.config.js", "next.config.mjs", "nuxt.config.ts",
124
+ "tailwind.config.js", "tailwind.config.ts", "postcss.config.js",
125
+ ".eslintrc.js", ".eslintrc.json", "eslint.config.js",
126
+ ".prettierrc", ".prettierrc.json",
127
+ ]);
128
+
129
+ const ENTRY_PATTERNS = [
130
+ /^src\/(main|index|app|entry)\.\w+$/,
131
+ /^(main|index|app)\.\w+$/,
132
+ /^cmd\/[^/]+\/main\.go$/,
133
+ /^lib\.rs$/,
134
+ /^src\/lib\.rs$/,
135
+ /^src\/main\.rs$/,
136
+ ];
137
+
138
+ const extStats = {}; // ext -> { count, lines }
139
+ const manifests = []; // relative paths
140
+ const entryPoints = [];
141
+ const dirSet = new Set();
142
+
143
+ for (const rel of allFiles) {
144
+ const ext = path.extname(rel) || "(none)";
145
+ if (!extStats[ext]) extStats[ext] = { count: 0, lines: 0 };
146
+ extStats[ext].count++;
147
+
148
+ // Count lines for source-like files (skip binaries / large blobs)
149
+ const abs = path.join(targetDir, rel);
150
+ try {
151
+ const stat = fs.statSync(abs);
152
+ if (stat.size < 512_000) {
153
+ const content = fs.readFileSync(abs, "utf-8");
154
+ extStats[ext].lines += content.split("\n").length;
155
+ }
156
+ } catch { /* skip unreadable */ }
157
+
158
+ // Track directories
159
+ const parts = rel.split("/");
160
+ for (let i = 1; i < parts.length; i++) {
161
+ dirSet.add(parts.slice(0, i).join("/"));
162
+ }
163
+
164
+ // Manifests
165
+ const basename = path.basename(rel);
166
+ if (MANIFEST_FILES.has(basename) || basename === "README.md" || basename === "README.rst") {
167
+ manifests.push(rel);
168
+ }
169
+
170
+ // Entry points
171
+ if (ENTRY_PATTERNS.some((p) => p.test(rel))) {
172
+ entryPoints.push(rel);
173
+ }
174
+ }
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Dependency extraction from key manifests
178
+ // ---------------------------------------------------------------------------
179
+
180
+ function extractDeps(relPath) {
181
+ const abs = path.join(targetDir, relPath);
182
+ const basename = path.basename(relPath);
183
+ try {
184
+ const raw = fs.readFileSync(abs, "utf-8");
185
+ if (basename === "package.json") {
186
+ const pkg = JSON.parse(raw);
187
+ return {
188
+ type: "npm",
189
+ name: pkg.name,
190
+ deps: Object.keys(pkg.dependencies || {}),
191
+ devDeps: Object.keys(pkg.devDependencies || {}),
192
+ scripts: Object.keys(pkg.scripts || {}),
193
+ };
194
+ }
195
+ if (basename === "go.mod") {
196
+ const modLine = raw.match(/^module\s+(.+)$/m);
197
+ const requires = [...raw.matchAll(/^\t(.+?)\s/gm)].map((m) => m[1]);
198
+ return { type: "go", module: modLine?.[1], deps: requires };
199
+ }
200
+ if (basename === "requirements.txt") {
201
+ const deps = raw.split("\n").map((l) => l.trim().split(/[>=<!\s]/)[0]).filter(Boolean);
202
+ return { type: "python-requirements", deps };
203
+ }
204
+ if (basename === "pyproject.toml") {
205
+ const nameLine = raw.match(/^name\s*=\s*"(.+?)"/m);
206
+ return { type: "python-pyproject", name: nameLine?.[1], raw_snippet: raw.slice(0, 500) };
207
+ }
208
+ if (basename === "Cargo.toml") {
209
+ const nameLine = raw.match(/^name\s*=\s*"(.+?)"/m);
210
+ const depsSection = raw.match(/\[dependencies\]([\s\S]*?)(\[|$)/);
211
+ const deps = depsSection
212
+ ? [...depsSection[1].matchAll(/^(\w[\w-]*)/gm)].map((m) => m[1])
213
+ : [];
214
+ return { type: "cargo", name: nameLine?.[1], deps };
215
+ }
216
+ if (basename === "Gemfile") {
217
+ const gems = [...raw.matchAll(/gem\s+['"](.+?)['"]/g)].map((m) => m[1]);
218
+ return { type: "ruby", deps: gems };
219
+ }
220
+ } catch { /* skip */ }
221
+ return null;
222
+ }
223
+
224
+ const depSummaries = {};
225
+ for (const m of manifests) {
226
+ const info = extractDeps(m);
227
+ if (info) depSummaries[m] = info;
228
+ }
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Build file tree (compact)
232
+ // ---------------------------------------------------------------------------
233
+
234
+ function buildTree(files, maxItems = 500) {
235
+ const truncated = files.length > maxItems;
236
+ const subset = truncated ? files.slice(0, maxItems) : files;
237
+ const lines = [];
238
+ const dirs = new Map(); // dir -> files[]
239
+
240
+ for (const f of subset) {
241
+ const dir = path.dirname(f);
242
+ if (!dirs.has(dir)) dirs.set(dir, []);
243
+ dirs.get(dir).push(path.basename(f));
244
+ }
245
+
246
+ const sortedDirs = [...dirs.keys()].sort();
247
+ for (const dir of sortedDirs) {
248
+ if (dir !== ".") lines.push(`${dir}/`);
249
+ for (const file of dirs.get(dir).sort()) {
250
+ lines.push(dir === "." ? ` ${file}` : ` ${dir}/${file}`);
251
+ }
252
+ }
253
+
254
+ if (truncated) lines.push(` ... and ${files.length - maxItems} more files`);
255
+ return lines.join("\n");
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Output
260
+ // ---------------------------------------------------------------------------
261
+
262
+ const report = {
263
+ target: targetDir,
264
+ totalFiles: allFiles.length,
265
+ totalDirs: dirSet.size,
266
+ extensions: Object.entries(extStats)
267
+ .sort((a, b) => b[1].count - a[1].count)
268
+ .map(([ext, s]) => ({ ext, ...s })),
269
+ manifests,
270
+ entryPoints,
271
+ depSummaries,
272
+ tree: buildTree(allFiles),
273
+ };
274
+
275
+ if (jsonOutput) {
276
+ console.log(JSON.stringify(report, null, 2));
277
+ } else {
278
+ const lines = [];
279
+ lines.push(`# Repository Scan: ${path.basename(targetDir)}`);
280
+ lines.push("");
281
+ lines.push(`**Path:** \`${targetDir}\``);
282
+ lines.push(`**Files:** ${report.totalFiles} | **Directories:** ${report.totalDirs}`);
283
+ lines.push("");
284
+
285
+ // Extension stats
286
+ lines.push("## File Statistics");
287
+ lines.push("");
288
+ lines.push("| Extension | Files | Lines |");
289
+ lines.push("|-----------|------:|------:|");
290
+ for (const { ext, count, lines: loc } of report.extensions) {
291
+ lines.push(`| ${ext} | ${count} | ${loc.toLocaleString()} |`);
292
+ }
293
+ lines.push("");
294
+
295
+ // Key files
296
+ lines.push("## Key / Manifest Files");
297
+ lines.push("");
298
+ for (const m of manifests) lines.push(`- \`${m}\``);
299
+ lines.push("");
300
+
301
+ // Entry points
302
+ if (entryPoints.length) {
303
+ lines.push("## Detected Entry Points");
304
+ lines.push("");
305
+ for (const e of entryPoints) lines.push(`- \`${e}\``);
306
+ lines.push("");
307
+ }
308
+
309
+ // Dependencies
310
+ if (Object.keys(depSummaries).length) {
311
+ lines.push("## Dependency Summary");
312
+ lines.push("");
313
+ for (const [file, info] of Object.entries(depSummaries)) {
314
+ lines.push(`### \`${file}\` (${info.type})`);
315
+ if (info.name) lines.push(`**Name:** ${info.name}`);
316
+ if (info.module) lines.push(`**Module:** ${info.module}`);
317
+ if (info.deps?.length) {
318
+ lines.push(`**Dependencies (${info.deps.length}):** ${info.deps.join(", ")}`);
319
+ }
320
+ if (info.devDeps?.length) {
321
+ lines.push(`**Dev Dependencies (${info.devDeps.length}):** ${info.devDeps.join(", ")}`);
322
+ }
323
+ if (info.scripts?.length) {
324
+ lines.push(`**Scripts:** ${info.scripts.join(", ")}`);
325
+ }
326
+ if (info.raw_snippet) {
327
+ lines.push("```toml");
328
+ lines.push(info.raw_snippet);
329
+ lines.push("```");
330
+ }
331
+ lines.push("");
332
+ }
333
+ }
334
+
335
+ // File tree
336
+ lines.push("## File Tree");
337
+ lines.push("");
338
+ lines.push("```");
339
+ lines.push(report.tree);
340
+ lines.push("```");
341
+
342
+ console.log(lines.join("\n"));
343
+ }
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: ship
3
+ description: Runs a repository-aware workflow to isolate, plan, implement, review, validate, and push a change.
4
+ metadata:
5
+ author: Daniel Fosco
6
+ version: "2026.8.30"
7
+ ---
8
+
9
+ # Ship Skill
10
+
11
+ > Triggered by: "ship", "ship this", "ship a feature", "ship it", "ship a change"
12
+ >
13
+ > This skill ships a change to a reviewable remote branch. It must not publish a package or create a release unless the user separately invokes the release workflow.
14
+
15
+ ## What This Does
16
+
17
+ Carries a change from repository discovery through implementation, validation, review, documentation, and push. It follows the target repository's branch, worktree, planning, testing, commit, and review conventions rather than prescribing a particular toolchain.
18
+
19
+ ## Step 1: Discover Repository Policy
20
+
21
+ Before choosing a branch or path, inspect repository instructions, current worktrees, branch state, remotes, package or build tooling, required checks, contribution guidance, and available local skills. Identify whether the repository requires:
22
+
23
+ - a worktree or permits work on the current branch;
24
+ - a branch naming convention or issue identifier;
25
+ - a checked-in plan or change request;
26
+ - specific test, lint, build, formatting, or documentation commands;
27
+ - commit-message conventions or attribution trailers;
28
+ - a pull request instead of a direct branch push.
29
+
30
+ Do not turn examples from this skill into repository policy.
31
+
32
+ ## Step 2: Isolate The Change
33
+
34
+ Use the `worktree` skill when available and appropriate. Derive a short branch name from the requested outcome, preserving any user-provided branch name and documented prefix. Confirm the name only when it is ambiguous or repository policy requires confirmation.
35
+
36
+ Use the path returned by the worktree workflow for all subsequent commands. Do not assume it is `worktrees/<branch-name>`. Never discard changes in the source or target worktree.
37
+
38
+ If the repository explicitly does not use worktrees, create or use the required branch without silently switching a dirty working tree.
39
+
40
+ ## Step 3: Plan Against Goals
41
+
42
+ Explore the relevant code before writing a plan. Define one to three measurable user-facing goals, then describe the smallest approach that satisfies them, affected files, implementation steps, edge cases, compatibility concerns, and validation.
43
+
44
+ Write the plan only when the repository has an established plan location or the user asks for a plan artifact. Otherwise keep it in task tracking. If approval is needed because the request is ambiguous, risky, destructive, or materially larger than stated, present the tradeoff and ask before implementation; do not add an unconditional approval gate to routine work.
45
+
46
+ Use repository-local issue or task tooling when available and useful. Do not require a specific tracker.
47
+
48
+ ## Step 4: Implement
49
+
50
+ Make the smallest correct changes consistent with existing architecture and style. Preserve unrelated worktree changes. Add or update tests alongside the behavior they cover using the repository's existing framework and patterns.
51
+
52
+ Do not split implementation and tests into separate commits by default. Commit boundaries should represent coherent reviewable changes, not phases imposed by this skill.
53
+
54
+ ## Step 5: Validate
55
+
56
+ Run the checks discovered in Step 1. Prefer documented aggregate commands. If no aggregate command exists, run the relevant tests and static checks for the changed area, expanding to the full suite when risk or repository policy warrants it.
57
+
58
+ Do not assume Node.js, npm, Vitest, or scripts named `lint`, `build`, `test`, or `dev`. Report any check that could not run and why.
59
+
60
+ ## Step 6: Review
61
+
62
+ Review the full branch diff against its base, not only the last commit. Check for behavioral regressions, security problems, broken interfaces, concurrency or error-path failures, missing tests, and needless complexity.
63
+
64
+ Use an available review agent or skill when it adds value, but do not require an agent name that may not exist. Apply high-confidence fixes, rerun affected checks, and avoid speculative refactors that do not serve a stated goal.
65
+
66
+ Read existing architecture documentation for changed files when present. Update generated or authored architecture docs only through the repository's documented process.
67
+
68
+ ## Step 7: Document And Commit
69
+
70
+ Update user-facing or architectural documentation when behavior, interfaces, operations, or durable design contracts changed. Do not edit every common documentation filename mechanically.
71
+
72
+ Stage only intended files and inspect the staged diff. Follow the repository's commit conventions. Add co-author or sign-off trailers only when required by repository policy or requested by the user; never hardcode an identity.
73
+
74
+ ## Step 8: Push And Report
75
+
76
+ Push the branch to the configured remote only when shipping includes a push under the user's request and repository policy. Use the actual remote name rather than assuming `origin`. If the repository expects a pull request and the user asked for one, create it using the repository template and return its URL.
77
+
78
+ Report:
79
+
80
+ - branch and worktree path;
81
+ - concise change summary;
82
+ - validation commands and results;
83
+ - review findings resolved or left open;
84
+ - commit, remote branch, and pull request URL when created;
85
+ - any manual verification still required.
86
+
87
+ Start a development server only when requested or needed for verification. Use the documented command and report how to stop it.
88
+
89
+ ## Rules
90
+
91
+ - Never ship directly to a protected or default branch unless repository policy and the user explicitly require it.
92
+ - Never release or publish as part of this workflow.
93
+ - Never assume a worktree directory, branch prefix, package manager, test framework, tracker, plan path, dev command, remote name, or commit trailer.
94
+ - Stop before destructive operations or unresolved high-severity failures; ordinary recoverable failures should be investigated and fixed.
95
+ - Preserve unrelated changes and state exactly what was pushed.