skillshop 2.0.0 → 2.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.
Files changed (62) hide show
  1. package/README.md +217 -104
  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 +104 -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 +219 -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 +1234 -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,219 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { legacyAgentsContent } from './legacy-agents-content.js';
4
+
5
+ export class AgentsInstructionsError extends Error {}
6
+
7
+ const blockSuffix = '.block.md';
8
+ const referencePattern = /\{\{\s*([^{}]+?)\s*\}\}/g;
9
+ const namedReferencePattern = /^fragments\s*\[\s*(["'])([^"'\[\]]+)\1\s*\]$/;
10
+ const aggregateExpression = 'blocks';
11
+ const regionBeginPrefix = '<!-- skillshop agents: begin v';
12
+ const regionBeginPattern = /^<!-- (?:skillshop|skillz) agents: begin v\S+ -->$/;
13
+ const regionEndLine = '<!-- skillshop agents: end -->';
14
+ const oldRegionEndLine = '<!-- skillz agents: end -->';
15
+ const legacyMarkerPattern = /^<!-- skillz agents: AGENTS\.md@\S+ -->$/;
16
+ const fragmentsBegin = '<!-- skillshop fragments: begin -->';
17
+ const fragmentsEnd = '<!-- skillshop fragments: end -->';
18
+
19
+ export function discoverBlocks(contentDirectory) {
20
+ if (!fs.existsSync(contentDirectory)) return [];
21
+ return fs
22
+ .readdirSync(contentDirectory, { withFileTypes: true })
23
+ .filter((entry) => entry.isFile() && entry.name.endsWith(blockSuffix))
24
+ .map((entry) => entry.name)
25
+ .sort()
26
+ .map((name) => ({
27
+ name,
28
+ content: fs.readFileSync(path.join(contentDirectory, name), 'utf8').trim()
29
+ }));
30
+ }
31
+
32
+ function invalidReference(expression, line) {
33
+ return new AgentsInstructionsError(
34
+ `invalid fragment reference {{ ${expression} }} at line ${line} of the agents base template; ` +
35
+ 'use {{ fragments["name.block.md"] }} or {{ blocks }}'
36
+ );
37
+ }
38
+
39
+ export function composeAgentsInstructions(contentDirectory) {
40
+ const basePath = path.join(contentDirectory, 'AGENTS.md');
41
+ if (!fs.existsSync(basePath)) {
42
+ throw new AgentsInstructionsError(`missing agents base template: ${basePath}`);
43
+ }
44
+
45
+ const base = fs.readFileSync(basePath, 'utf8');
46
+ const blocks = discoverBlocks(contentDirectory);
47
+ const blocksByName = new Map(blocks.map((block) => [block.name, block.content]));
48
+
49
+ const references = [];
50
+ for (const match of base.matchAll(referencePattern)) {
51
+ const expression = match[1];
52
+ const line = base.slice(0, match.index).split('\n').length;
53
+ if (expression === aggregateExpression) {
54
+ references.push({ kind: 'aggregate', start: match.index, end: match.index + match[0].length });
55
+ continue;
56
+ }
57
+ const named = expression.match(namedReferencePattern);
58
+ if (!named || named[2].includes('/') || named[2].includes('\\')) {
59
+ throw invalidReference(expression, line);
60
+ }
61
+ references.push({ kind: 'named', name: named[2], start: match.index, end: match.index + match[0].length });
62
+ }
63
+
64
+ for (const reference of references) {
65
+ if (reference.kind !== 'named' || blocksByName.has(reference.name)) continue;
66
+ if (fs.existsSync(path.join(contentDirectory, reference.name))) {
67
+ throw new AgentsInstructionsError(
68
+ `agents base template references ${reference.name}, which is not a *.block.md fragment`
69
+ );
70
+ }
71
+ throw new AgentsInstructionsError(`agents base template references missing fragment: ${reference.name}`);
72
+ }
73
+
74
+ const referencedNames = new Set(
75
+ references.filter((reference) => reference.kind === 'named').map((reference) => reference.name)
76
+ );
77
+ const aggregate = blocks
78
+ .filter((block) => !referencedNames.has(block.name))
79
+ .map((block) => block.content)
80
+ .join('\n\n');
81
+
82
+ let content = '';
83
+ let cursor = 0;
84
+ for (const reference of references) {
85
+ content += base.slice(cursor, reference.start);
86
+ content += reference.kind === 'aggregate' ? aggregate : blocksByName.get(reference.name);
87
+ cursor = reference.end;
88
+ }
89
+ content += base.slice(cursor);
90
+
91
+ return { content };
92
+ }
93
+
94
+ function regionText(version, composed) {
95
+ return `${regionBeginPrefix}${version} -->\n${composed}\n${regionEndLine}`;
96
+ }
97
+
98
+ function ambiguous(message) {
99
+ return new AgentsInstructionsError(
100
+ `refusing to update agents instructions: ${message}; fix or remove the affected block manually, then rerun skillshop agents`
101
+ );
102
+ }
103
+
104
+ function appendRegion(existing, region) {
105
+ if (existing === '') return `${region}\n`;
106
+ const separator = existing.endsWith('\n\n') ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
107
+ return `${existing}${separator}${region}\n`;
108
+ }
109
+
110
+ function applyManagedRegion(lines, starts, ends, region, composed, version) {
111
+ const [start] = starts;
112
+ const [end] = ends;
113
+ const inner = lines.slice(start + 1, end).join('\n');
114
+ const installedVersion = lines[start].match(/<!-- (?:skillshop|skillz) agents: begin v(\S+) -->/)[1];
115
+ if (installedVersion === version && inner === composed && lines[start].startsWith(regionBeginPrefix) && lines[end] === regionEndLine) {
116
+ return { action: 'unchanged', text: lines.join('\n') };
117
+ }
118
+ const next = [...lines.slice(0, start), ...region.split('\n'), ...lines.slice(end + 1)];
119
+ return { action: 'refreshed', text: next.join('\n') };
120
+ }
121
+
122
+ function legacyChunks(lines, markerIndices) {
123
+ const legacyLines = legacyAgentsContent.split('\n');
124
+ const spans = [];
125
+ for (const markerIndex of markerIndices) {
126
+ const start = markerIndex - 1 - legacyLines.length;
127
+ const owned =
128
+ start >= 0 &&
129
+ lines.slice(start, start + legacyLines.length).join('\n') === legacyAgentsContent &&
130
+ lines[start + legacyLines.length] === '' &&
131
+ (start === 0 || lines[start - 1] === '');
132
+ if (!owned) {
133
+ throw new AgentsInstructionsError(
134
+ `refusing to update agents instructions: unrecognized legacy skillz agents marker at line ${markerIndex + 1} ` +
135
+ '(the installed guidance was edited or does not match the historical skillz content); ' +
136
+ 'remove the installed legacy guidance manually, then rerun skillshop agents'
137
+ );
138
+ }
139
+ spans.push([start, markerIndex]);
140
+ }
141
+ spans.sort((a, b) => a[0] - b[0]);
142
+ return spans;
143
+ }
144
+
145
+ function migrateLegacy(lines, markerIndices, region, composed, version) {
146
+ const spans = legacyChunks(lines, markerIndices);
147
+ const insertPoint = '\u0000skillz-region-insert-point\u0000';
148
+ const remainder = [];
149
+ let cursor = 0;
150
+ for (const [start, end] of spans) {
151
+ remainder.push(...lines.slice(cursor, start));
152
+ if (end === spans[spans.length - 1][1]) remainder.push(insertPoint);
153
+ cursor = end + 1;
154
+ }
155
+ remainder.push(...lines.slice(cursor));
156
+
157
+ const collapsed = [];
158
+ for (const line of remainder) {
159
+ if (line === '' && line !== insertPoint && collapsed[collapsed.length - 1] === '') continue;
160
+ collapsed.push(line);
161
+ }
162
+
163
+ const insertAt = collapsed.indexOf(insertPoint);
164
+ const before = collapsed.slice(0, insertAt);
165
+ const after = collapsed.slice(insertAt + 1);
166
+ let text = before.join('\n');
167
+ if (text !== '' && !text.endsWith('\n\n')) text += text.endsWith('\n') ? '\n' : '\n\n';
168
+ text += `${region}\n`;
169
+ const afterText = after.join('\n');
170
+ if (afterText !== '') text += afterText.startsWith('\n') ? afterText : `\n\n${afterText}`;
171
+ return { action: 'installed', text: text.replace(/\n+$/, '\n') };
172
+ }
173
+
174
+ export function applyInstructions(existing, composed, version) {
175
+ const composedText = composed.trimEnd();
176
+ const region = regionText(version, composedText);
177
+ const lines = existing.split('\n');
178
+ const begins = [];
179
+ const ends = [];
180
+ const legacyMarkers = [];
181
+ lines.forEach((line, index) => {
182
+ if (regionBeginPattern.test(line)) begins.push(index);
183
+ else if (line === regionEndLine || line === oldRegionEndLine) ends.push(index);
184
+ else if (legacyMarkerPattern.test(line)) legacyMarkers.push(index);
185
+ });
186
+
187
+ if (begins.length !== ends.length || begins.length > 1) {
188
+ throw ambiguous(
189
+ 'the managed agents block markers are malformed (each marker must appear exactly once on its own line)'
190
+ );
191
+ }
192
+ if (legacyMarkers.length && begins.length) {
193
+ throw ambiguous('the file contains both legacy skillz agents markers and a managed agents block');
194
+ }
195
+
196
+ if (begins.length) return applyManagedRegion(lines, begins, ends, region, composedText, version);
197
+ if (legacyMarkers.length) return migrateLegacy(lines, legacyMarkers, region, composedText, version);
198
+ return { action: 'installed', text: appendRegion(existing, region) };
199
+ }
200
+
201
+ export function applyLocalFragments(existing, contentDirectory) {
202
+ const blocks = discoverBlocks(contentDirectory);
203
+ const lines = existing.split('\n');
204
+ const begins = lines.flatMap((line, index) => line === fragmentsBegin ? [index] : []);
205
+ const ends = lines.flatMap((line, index) => line === fragmentsEnd ? [index] : []);
206
+ if (begins.length !== ends.length || begins.length > 1 || (begins.length && begins[0] >= ends[0])) {
207
+ throw new AgentsInstructionsError('local fragment markers are malformed');
208
+ }
209
+ if (!blocks.length && !begins.length) return { action: 'unchanged', text: existing };
210
+ const region = blocks.length
211
+ ? `${fragmentsBegin}\n${blocks.map((block) => block.content).join('\n\n')}\n${fragmentsEnd}`
212
+ : '';
213
+ if (!begins.length) return { action: 'installed', text: appendRegion(existing, region) };
214
+ const current = lines.slice(begins[0], ends[0] + 1).join('\n');
215
+ if (current === region) return { action: 'unchanged', text: existing };
216
+ const before = lines.slice(0, begins[0]);
217
+ const after = lines.slice(ends[0] + 1);
218
+ return { action: 'refreshed', text: [...before, ...(region ? region.split('\n') : []), ...after].join('\n') };
219
+ }
package/src/cli.js CHANGED
@@ -1,29 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'node:fs';
4
3
  import path from 'node:path';
4
+ import os from 'node:os';
5
5
  import { buildSkillLibrary, initSkillLibrary, readSkillLibrary, validateSkillLibrary } from './library.js';
6
6
  import { exportSkill, reexportSkill, unexportSkill } from './export.js';
7
7
  import { importSkill, resolveSkill } from './import.js';
8
-
9
- const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
8
+ import { installDeclaredUserSkills } from './user-install.js';
9
+ import { reconcileLegacyState, runLegacyCli } from './legacy-cli.js';
10
+ import { parseLibrarySkillReference, skillName } from './specifier.js';
10
11
 
11
12
  function help() {
12
- console.log(`skillshop v${VERSION} — build and consume portable skill libraries
13
+ console.log(`
14
+ Library commands:
13
15
 
14
- Usage:
15
- skillshop init
16
+ skillshop library init
17
+ skillshop library list
16
18
  skillshop export skill:<name> <directory>
17
19
  skillshop export skill:<name> --from <package> [--skill skill:<source-name>]
18
20
  skillshop unexport skill:<name>
19
- skillshop list
20
21
  skillshop validate
21
22
  skillshop build
22
- skillshop resolve <library> skill:<name>
23
- skillshop import <library> skill:<name> --to <directory> [--force]
23
+ skillshop resolve <library>:<skill>
24
+ skillshop import <library>:<skill> [--global] [--to <directory>] [--force]
25
+ skillshop install-user [package-dir] [--if-global] [--to <skills-dir>]
24
26
 
25
- A library publishes skills by portable specifier, for example skill:clips.
26
- Consumers resolve the same specifier regardless of where the library stores SKILL.md.`);
27
+ A library publishes skills by portable specifier, for example skill:review.
28
+ Import uses the short <library>:<skill> form and copies to .agents/skills/<skill> by default.
29
+ --global copies to ~/.agents/skills/<skill> and replaces an existing directory.`);
27
30
  }
28
31
 
29
32
  function flag(args, name) {
@@ -31,10 +34,29 @@ function flag(args, name) {
31
34
  return index === -1 ? null : args[index + 1];
32
35
  }
33
36
 
34
- async function main(argv) {
37
+ function librarySelection(args, usage) {
38
+ if (args.length === 1) return { reference: args[0] };
39
+ if (args.length === 2 && args[1].startsWith('skill:')) {
40
+ return { library: args[0], specifier: args[1] };
41
+ }
42
+ throw new Error(usage);
43
+ }
44
+
45
+ const libraryCommands = new Set(['export', 'unexport', 'validate', 'build', 'resolve', 'import', 'install-user']);
46
+
47
+ async function main(argv, { libraryNamespace = false } = {}) {
35
48
  const [command, ...args] = argv;
36
- if (!command || command === '--help' || command === '-h' || command === 'help') return help();
37
- if (command === '--version' || command === '-v') return console.log(`skillshop v${VERSION}`);
49
+ if (!libraryNamespace && command === 'library') {
50
+ if (args[0] === 'init' || args[0] === 'list') return main(args, { libraryNamespace: true });
51
+ if (!args.length || args[0] === '--help' || args[0] === '-h') return help();
52
+ throw new Error('Usage: skillshop library init | list');
53
+ }
54
+ if (!libraryNamespace && (!command || command === '--help' || command === '-h' || command === 'help')) {
55
+ await runLegacyCli(['--help']);
56
+ return help();
57
+ }
58
+ if (!libraryNamespace && (command === '--version' || command === '-v')) return runLegacyCli(argv);
59
+ if (!libraryNamespace && !libraryCommands.has(command)) return runLegacyCli(argv);
38
60
 
39
61
  if (command === 'init') {
40
62
  initSkillLibrary();
@@ -75,22 +97,64 @@ async function main(argv) {
75
97
  return;
76
98
  }
77
99
  if (command === 'resolve') {
78
- if (args.length !== 2) throw new Error('Usage: skillshop resolve <library> skill:<name>');
79
- console.log(JSON.stringify(await resolveSkill({ library: args[0], specifier: args[1] }), null, 2));
100
+ const selection = librarySelection(args, 'Usage: skillshop resolve <library>:<skill>');
101
+ console.log(JSON.stringify(await resolveSkill(selection), null, 2));
80
102
  return;
81
103
  }
82
104
  if (command === 'import') {
83
- if (args.length < 2) throw new Error('Usage: skillshop import <library> skill:<name> --to <directory> [--force]');
84
- const targetDir = flag(args, '--to');
85
- if (!targetDir) throw new Error('--to <directory> is required');
86
- const result = await importSkill({ library: args[0], specifier: args[1], targetDir, overwrite: args.includes('--force') });
105
+ const usage = 'Usage: skillshop import <library>:<skill> [--global] [--to <directory>] [--force]';
106
+ const positional = [];
107
+ let targetDir;
108
+ let globalInstall = false;
109
+ let force = false;
110
+ for (let index = 0; index < args.length; index += 1) {
111
+ const argument = args[index];
112
+ if (argument === '--to') {
113
+ targetDir = args[++index];
114
+ if (!targetDir || targetDir.startsWith('--')) throw new Error(usage);
115
+ } else if (argument === '--global' || argument === '-g') globalInstall = true;
116
+ else if (argument === '--force') force = true;
117
+ else if (argument.startsWith('-')) throw new Error(usage);
118
+ else positional.push(argument);
119
+ }
120
+ const selection = librarySelection(positional, usage);
121
+ if (globalInstall && targetDir) throw new Error('--global and --to cannot be combined');
122
+ const name = selection.reference
123
+ ? parseLibrarySkillReference(selection.reference).name
124
+ : skillName(selection.specifier);
125
+ const result = await importSkill({
126
+ ...selection,
127
+ targetDir: globalInstall ? path.join(os.homedir(), '.agents', 'skills', name) : targetDir,
128
+ overwrite: force || globalInstall,
129
+ });
87
130
  console.log(`${result.specifier} -> ${result.targetDir}`);
88
131
  return;
89
132
  }
90
- throw new Error(`Unknown command: ${command}`);
133
+ if (command === 'install-user') {
134
+ if (args.includes('--if-global') && process.env.npm_config_global !== 'true') {
135
+ console.log('Skipped user skills: this is not a global npm install');
136
+ return;
137
+ }
138
+ const library = args[0] && !args[0].startsWith('--') ? args[0] : '.';
139
+ const userSkillsDir = flag(args, '--to') || undefined;
140
+ const results = await installDeclaredUserSkills({ library, userSkillsDir });
141
+ for (const result of results) console.log(`${result.specifier} -> ${result.targetDir}${result.updated ? ' (updated)' : ''}`);
142
+ return;
143
+ }
144
+ throw new Error(`Unknown library command: ${command}`);
91
145
  }
92
146
 
93
- main(process.argv.slice(2)).catch((error) => {
147
+ const argv = process.argv.slice(2);
148
+ const usingLibraryCommands = argv[0] === 'library' || libraryCommands.has(argv[0]);
149
+
150
+ try {
151
+ if (usingLibraryCommands) reconcileLegacyState();
152
+ try {
153
+ await main(argv);
154
+ } finally {
155
+ if (usingLibraryCommands) reconcileLegacyState();
156
+ }
157
+ } catch (error) {
94
158
  console.error(`skillshop: ${error.message}`);
95
159
  process.exitCode = 1;
96
- });
160
+ }
package/src/import.js CHANGED
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { createRequire } from 'node:module';
5
5
  import { pathToFileURL, fileURLToPath } from 'node:url';
6
- import { normalizeSkillSpecifier, parseSkillSpecifier } from './specifier.js';
6
+ import { normalizeSkillSpecifier, parseSkillSpecifier, parseLibrarySkillReference } from './specifier.js';
7
7
  import { SKILLSHOP_MANIFEST_FILE, SKILLSHOP_SCHEMA_VERSION } from './library.js';
8
8
 
9
9
  async function importManifestFile(file) {
@@ -42,9 +42,13 @@ export async function loadSkillLibrary(library, { cwd = process.cwd() } = {}) {
42
42
  return validatePublishedManifest(await importManifestFile(resolved), value);
43
43
  }
44
44
 
45
- export async function resolveSkill({ library, specifier, cwd = process.cwd() }) {
46
- const normalized = normalizeSkillSpecifier(specifier);
47
- const manifest = await loadSkillLibrary(library, { cwd });
45
+ export async function resolveSkill({ reference, library, specifier, cwd = process.cwd() }) {
46
+ if (reference && (library !== undefined || specifier !== undefined)) {
47
+ throw new Error('Use reference or library and specifier, not both');
48
+ }
49
+ const selected = reference ? parseLibrarySkillReference(reference) : { library, specifier: normalizeSkillSpecifier(specifier) };
50
+ const normalized = selected.specifier;
51
+ const manifest = await loadSkillLibrary(selected.library, { cwd });
48
52
  const href = manifest.skills[normalized];
49
53
  if (typeof href !== 'string' || !href) throw new Error(`${normalized} is not exported by ${manifest.packageName || 'the library'}`);
50
54
  const url = new URL(href);
@@ -84,6 +88,7 @@ function assertSafeMergeTarget(target, relativeFile) {
84
88
  }
85
89
 
86
90
  export async function importSkill({
91
+ reference,
87
92
  library,
88
93
  specifier,
89
94
  targetDir,
@@ -91,10 +96,10 @@ export async function importSkill({
91
96
  overwrite = false,
92
97
  merge = false,
93
98
  }) {
94
- if (typeof targetDir !== 'string' || !targetDir.trim()) throw new Error('targetDir is required');
99
+ if (targetDir !== undefined && (typeof targetDir !== 'string' || !targetDir.trim())) throw new Error('targetDir must be a nonempty path');
95
100
  if (merge && overwrite) throw new Error('merge and overwrite cannot both be enabled');
96
- const skill = await resolveSkill({ library, specifier, cwd });
97
- const target = path.resolve(cwd, targetDir);
101
+ const skill = await resolveSkill({ reference, library, specifier, cwd });
102
+ const target = path.resolve(cwd, targetDir ?? path.join('.agents', 'skills', skill.name));
98
103
  const exists = fs.existsSync(target);
99
104
 
100
105
  if (exists && merge) {
package/src/index.js CHANGED
@@ -2,3 +2,4 @@ export * from './specifier.js';
2
2
  export * from './library.js';
3
3
  export * from './export.js';
4
4
  export * from './import.js';
5
+ export * from './user-install.js';
@@ -0,0 +1,36 @@
1
+ // Snapshot of agents-content/AGENTS.md as shipped through skillz 0.3.x.
2
+ // Installations made before the managed-block format appended this content verbatim
3
+ // followed by a "<!-- skillz agents: AGENTS.md@<version> -->" marker line.
4
+ export const legacyAgentsContent = [
5
+ "### Baseline tone",
6
+ "",
7
+ "This applies to every text that you write.",
8
+ "",
9
+ "Adopt a skeptical, questioning approach. Don't default to praise, criticize my ideas, provide counterpoints, dig deeper. I need you to find the blind spots in my thinking and account for them, in a straight shooting and constructive way.",
10
+ "",
11
+ "Don't overuse adjectives, don't compliment or criticize my arguments in a personalized way, just attend to the facts and topics. Tell it like it is; don't sugar-coat responses. Also don't explicitly mention you're NOT praising me or actively say you're “getting to the point”. Don't comment on your tone at all.",
12
+ "",
13
+ "When explaining things, provide depth of concepts, be extensive, with context, clarity and explanation of concepts along the way.",
14
+ "",
15
+ "## Communication and writing",
16
+ "",
17
+ "- Write directly and concisely. Prefer short, distinct sentences over long compound ones.",
18
+ "- Do not overuse em dashes. There are periods, commas, semicolons, and exclamation marks. Use them.",
19
+ "- Remove hedges and filler that add no information. Keep technical terms when they add precision.",
20
+ "- Avoid exact counts unless the number matters. \"All the callers\" is usually better than \"all 4 callers.\"",
21
+ "- Short, natural reactions are fine. Use \"Good catch!\" rather than an inflated response.",
22
+ "- Avoid throat clearing, restating the question, routine process narration, and closing offers. Brief accountability or correction is fine when it helps the reader.",
23
+ "- Do not explain in prose what a table or diff already makes clear.",
24
+ "- State genuine uncertainty briefly. Do not add defensive caveats or pre-empt objections nobody raised.",
25
+ "- Brevity must not hide important behavior, risk, uncertainty, or evidence.",
26
+ "",
27
+ "### Approval for external actions",
28
+ "",
29
+ "All external actions require explicit approval before proceeding. Even on autopilot mode, do not post without explicit written authorizaton from the user.",
30
+ "",
31
+ "- Before posting or editing a PR comment, review reply, issue comment, Slack message, or other external message, show me the exact draft and get an explicit OK. This includes edits to existing messages.",
32
+ "- Before resolving review threads, requesting reviewers, or changing other visible review state, describe the exact action and get an explicit OK.",
33
+ "- Create an issue, epic, or PR only when I explicitly request it. Before opening one, show me its proposed title and body and get an explicit OK.",
34
+ "- A direct request to update an existing PR or issue body authorizes that update without another approval. Fetch the latest body from the host immediately before editing it.",
35
+ "- Pushing requested commits does not require another approval."
36
+ ].join("\n");