@pigcloud/skills 1.0.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 (161) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +201 -0
  3. package/README.en.md +84 -0
  4. package/README.md +79 -0
  5. package/bin/cli.js +663 -0
  6. package/bin/postinstall.js +60 -0
  7. package/bin/rules-loader.js +484 -0
  8. package/bin/runtime-bootstrap.js +121 -0
  9. package/index.js +7 -0
  10. package/install.cmd +5 -0
  11. package/install.ps1 +74 -0
  12. package/install.sh +168 -0
  13. package/package.json +70 -0
  14. package/pig-cloud-skills-commands/.codex-plugin/plugin.json +35 -0
  15. package/pig-cloud-skills-commands/README.md +24 -0
  16. package/pig-cloud-skills-commands/commands/analyze.md +21 -0
  17. package/pig-cloud-skills-commands/commands/build.md +21 -0
  18. package/pig-cloud-skills-commands/commands/design.md +21 -0
  19. package/pig-cloud-skills-commands/commands/distill.md +21 -0
  20. package/pig-cloud-skills-commands/commands/doc.md +21 -0
  21. package/pig-cloud-skills-commands/commands/infra.md +21 -0
  22. package/pig-cloud-skills-commands/commands/init.md +20 -0
  23. package/pig-cloud-skills-commands/commands/kb.md +20 -0
  24. package/pig-cloud-skills-commands/commands/perf.md +20 -0
  25. package/pig-cloud-skills-commands/commands/prd.md +21 -0
  26. package/pig-cloud-skills-commands/commands/review.md +21 -0
  27. package/pig-cloud-skills-commands/commands/security.md +21 -0
  28. package/pig-cloud-skills-commands/commands/test.md +21 -0
  29. package/pig-cloud-skills-commands/commands/workflow.md +20 -0
  30. package/rules/bundles.json +358 -0
  31. package/rules/coding/analysis.md +27 -0
  32. package/rules/coding/backend/cache-invalidation.md +30 -0
  33. package/rules/coding/backend/cache-keying.md +30 -0
  34. package/rules/coding/backend/cache.md +37 -0
  35. package/rules/coding/backend/database.md +32 -0
  36. package/rules/coding/backend/feign.md +30 -0
  37. package/rules/coding/backend/index.md +42 -0
  38. package/rules/coding/backend/query.md +32 -0
  39. package/rules/coding/backend/remote.md +33 -0
  40. package/rules/coding/backend/transaction-boundary.md +30 -0
  41. package/rules/coding/backend/transaction-rollback.md +30 -0
  42. package/rules/coding/backend/transaction.md +38 -0
  43. package/rules/coding/boundary.md +25 -0
  44. package/rules/coding/implementation.md +30 -0
  45. package/rules/coding/index.md +38 -0
  46. package/rules/coding/scaffold.md +28 -0
  47. package/rules/coding/testing.md +29 -0
  48. package/rules/coding/validation.md +29 -0
  49. package/rules/core/code-quality.md +30 -0
  50. package/rules/core/evidence.md +26 -0
  51. package/rules/core/index.md +41 -0
  52. package/rules/core/interface.md +26 -0
  53. package/rules/core/iteration.md +26 -0
  54. package/rules/core/layer-boundary.md +25 -0
  55. package/rules/core/logging.md +26 -0
  56. package/rules/core/security.md +26 -0
  57. package/rules/core/task-boundary.md +27 -0
  58. package/rules/docs/api.md +34 -0
  59. package/rules/docs/capture-summary.md +29 -0
  60. package/rules/docs/capture.md +34 -0
  61. package/rules/docs/contract.md +30 -0
  62. package/rules/docs/decision-log.md +32 -0
  63. package/rules/docs/examples.md +28 -0
  64. package/rules/docs/index.md +49 -0
  65. package/rules/docs/reference.md +32 -0
  66. package/rules/index.md +46 -0
  67. package/rules/overlays/index.md +28 -0
  68. package/rules/overlays/pig-cloud/controller.md +33 -0
  69. package/rules/overlays/pig-cloud/dto-vo.md +33 -0
  70. package/rules/overlays/pig-cloud/entity.md +32 -0
  71. package/rules/overlays/pig-cloud/exception.md +32 -0
  72. package/rules/overlays/pig-cloud/layering.md +31 -0
  73. package/rules/overlays/pig-cloud/mapper.md +32 -0
  74. package/rules/overlays/pig-cloud/query-style.md +32 -0
  75. package/rules/overlays/pig-cloud/rest-response.md +33 -0
  76. package/rules/overlays/pig-cloud/service.md +33 -0
  77. package/rules/overlays/pig-cloud/transactions.md +32 -0
  78. package/rules/overlays/pig-cloud/validation.md +33 -0
  79. package/rules/overlays/pig-cloud.md +45 -0
  80. package/rules/product/acceptance.md +25 -0
  81. package/rules/product/briefing.md +27 -0
  82. package/rules/product/index.md +36 -0
  83. package/rules/product/intake.md +27 -0
  84. package/rules/product/modeling.md +25 -0
  85. package/rules/product/project-context.md +29 -0
  86. package/rules/review/code.md +35 -0
  87. package/rules/review/evidence.md +31 -0
  88. package/rules/review/index.md +50 -0
  89. package/rules/review/java.md +42 -0
  90. package/rules/review/performance.md +38 -0
  91. package/rules/review/rubric.md +28 -0
  92. package/rules/review/security.md +38 -0
  93. package/rules/review/ts.md +33 -0
  94. package/rules/review/vue.md +33 -0
  95. package/rules/skill-profile-map.json +58 -0
  96. package/rules/skill-profile-map.md +28 -0
  97. package/rules/workflow/handoff.md +25 -0
  98. package/rules/workflow/index.md +37 -0
  99. package/rules/workflow/refinement.md +29 -0
  100. package/rules/workflow/router.md +25 -0
  101. package/rules/workflow/selection.md +25 -0
  102. package/rules/workflow/stop.md +25 -0
  103. package/scripts/ci-validator.sh +114 -0
  104. package/scripts/run-golden-replays.js +312 -0
  105. package/scripts/validate-rules.js +125 -0
  106. package/scripts/validate-skill-replay-signals.js +75 -0
  107. package/scripts/validate-skill-shapes.js +141 -0
  108. package/scripts/validate-skill-stop-rules.js +139 -0
  109. package/scripts/validate-skills.cmd +3 -0
  110. package/scripts/validate-skills.ps1 +42 -0
  111. package/scripts/validate-skills.sh +36 -0
  112. package/skills/api-docs/SKILL.md +76 -0
  113. package/skills/code-review/SKILL.md +135 -0
  114. package/skills/code-review/references/findings-template.md +51 -0
  115. package/skills/code-review/references/performance-checklist.md +213 -0
  116. package/skills/code-review/references/rubric.md +232 -0
  117. package/skills/code-review/references/rules.md +32 -0
  118. package/skills/code-review/references/security-checklist.md +178 -0
  119. package/skills/code-review/references/stack-notes.md +25 -0
  120. package/skills/code-review/references/template-review.md +214 -0
  121. package/skills/code-review/scripts/lint-code-review.mjs +431 -0
  122. package/skills/domain-modeling/SKILL.md +80 -0
  123. package/skills/domain-modeling/references/README.md +134 -0
  124. package/skills/domain-modeling/references/distillation-checklist.md +152 -0
  125. package/skills/domain-modeling/references/test-cases-template.md +128 -0
  126. package/skills/environment-deploy/SKILL.md +81 -0
  127. package/skills/feature-build/SKILL.md +122 -0
  128. package/skills/feature-build/references/coding-checklist.md +97 -0
  129. package/skills/feature-build/references/comment-specification.md +102 -0
  130. package/skills/knowledge-capture/SKILL.md +84 -0
  131. package/skills/performance-check/SKILL.md +117 -0
  132. package/skills/product-intake/SKILL.md +98 -0
  133. package/skills/project-bootstrap/SKILL.md +80 -0
  134. package/skills/references/agent-personas.md +34 -0
  135. package/skills/references/anti-rationalization.md +144 -0
  136. package/skills/references/engineering-delivery-method.md +63 -0
  137. package/skills/references/engineering-delivery-template.md +80 -0
  138. package/skills/references/flow-test-cases.md +62 -0
  139. package/skills/references/full-chain-replay-scenarios.md +79 -0
  140. package/skills/references/golden-prompt-suite.js +385 -0
  141. package/skills/references/golden-prompt-suite.md +33 -0
  142. package/skills/references/hooks.md +67 -0
  143. package/skills/references/negative-replay-scenarios.md +49 -0
  144. package/skills/references/project-requirement-alignment.md +41 -0
  145. package/skills/references/prompt-replay-checklist.md +128 -0
  146. package/skills/references/requirements-separation-map.md +71 -0
  147. package/skills/references/rule-loading-map.md +108 -0
  148. package/skills/references/skill-authoring-standard.md +73 -0
  149. package/skills/references/skill-boundary-template.md +38 -0
  150. package/skills/references/skill-enhanced-template.md +53 -0
  151. package/skills/references/skill-reference-matrix.md +53 -0
  152. package/skills/references/slash-commands.md +34 -0
  153. package/skills/security-review/SKILL.md +117 -0
  154. package/skills/spec-refinement/SKILL.md +143 -0
  155. package/skills/spec-refinement/references/ears-syntax.md +127 -0
  156. package/skills/spec-refinement/references/requirement-checklist.md +139 -0
  157. package/skills/spec-refinement/references/spec-workbook.md +75 -0
  158. package/skills/technical-design/SKILL.md +105 -0
  159. package/skills/technical-design/references/solid-checklist.md +199 -0
  160. package/skills/test-design/SKILL.md +91 -0
  161. package/skills/workflow-router/SKILL.md +86 -0
package/bin/cli.js ADDED
@@ -0,0 +1,663 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs/promises');
4
+ const fsSync = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { detectRulesBundle } = require('./rules-loader');
8
+ const { ensureNpmRuntime } = require('./runtime-bootstrap');
9
+
10
+ const PACKAGE_ROOT = path.join(__dirname, '..');
11
+ const SKILLS_DIR = path.join(PACKAGE_ROOT, 'skills');
12
+ const VERSION = require('../package.json').version;
13
+ const CODEX_HOME = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
14
+ const AGENTS_HOME = path.join(os.homedir(), '.agents');
15
+ const COMMAND_PACK_NAME = 'pig-cloud-skills-commands';
16
+ const COMMAND_PACK_SOURCE = path.join(PACKAGE_ROOT, COMMAND_PACK_NAME);
17
+ const CODEX_HOME_SKILLS_DIR = path.join(CODEX_HOME, 'skills');
18
+ const AGENTS_PLUGINS_DIR = path.join(AGENTS_HOME, 'plugins');
19
+ const AGENTS_MARKETPLACE_FILE = path.join(AGENTS_PLUGINS_DIR, 'marketplace.json');
20
+ const INSTALL_ROOT = path.resolve(process.env.INIT_CWD || process.cwd());
21
+ const SUPPORTED_TOOLS = ['codex', 'claude', 'trae', 'cursor', 'copilot', 'windsurf'];
22
+ const TOOL_ALIASES = {
23
+ opencode: 'codex',
24
+ claude_code: 'claude'
25
+ };
26
+
27
+ const COLORS = {
28
+ reset: '\x1b[0m',
29
+ blue: '\x1b[34m',
30
+ green: '\x1b[32m',
31
+ red: '\x1b[31m',
32
+ yellow: '\x1b[33m',
33
+ gray: '\x1b[90m'
34
+ };
35
+
36
+ const chalk = {
37
+ blue: text => colorize(COLORS.blue, text),
38
+ green: text => colorize(COLORS.green, text),
39
+ red: text => colorize(COLORS.red, text),
40
+ yellow: text => colorize(COLORS.yellow, text),
41
+ gray: text => colorize(COLORS.gray, text)
42
+ };
43
+
44
+ const SKILLS = [
45
+ { name: 'workflow-router', category: 'core', description: 'route workflow stages' },
46
+ { name: 'technical-design', category: 'core', description: 'shape implementation design' },
47
+ { name: 'feature-build', category: 'core', description: 'implement features safely' },
48
+ { name: 'code-review', category: 'core', description: 'review code changes' },
49
+ { name: 'product-intake', category: 'analysis', description: 'capture product intent' },
50
+ { name: 'spec-refinement', category: 'analysis', description: 'turn demand into specs' },
51
+ { name: 'domain-modeling', category: 'analysis', description: 'extract domain knowledge' },
52
+ { name: 'api-docs', category: 'integration', description: 'document API contracts' },
53
+ { name: 'test-design', category: 'integration', description: 'design test coverage' },
54
+ { name: 'security-review', category: 'review', description: 'review security risks' },
55
+ { name: 'performance-check', category: 'review', description: 'review performance risks' },
56
+ { name: 'knowledge-capture', category: 'collaboration', description: 'capture reusable knowledge' },
57
+ { name: 'project-bootstrap', category: 'infrastructure', description: 'bootstrap project scaffolds' },
58
+ { name: 'environment-deploy', category: 'infrastructure', description: 'deploy supporting environments' }
59
+ ];
60
+
61
+ const TOOL_CONFIGS = {
62
+ codex: { dir: '.agents/skills', rootFile: '.agents/AGENTS.md', markers: ['.agents/AGENTS.md', '.agents'] },
63
+ opencode: { dir: '.agents/skills', rootFile: '.agents/AGENTS.md', markers: ['.agents/AGENTS.md', '.agents'] },
64
+ claude: { dir: '.claude/skills', rootFile: '.claude/CLAUDE.md', markers: ['.claude/CLAUDE.md', '.claude'] },
65
+ claude_code: { dir: '.claude/skills', rootFile: '.claude/CLAUDE.md', markers: ['.claude/CLAUDE.md', '.claude'] },
66
+ trae: { dir: '.trae/skills', rootFile: '.trae/AGENTS.md', markers: ['.trae/AGENTS.md', '.trae'] },
67
+ cursor: { dir: '.cursor/rules', rootFile: '.cursor/rules/project-rules.mdc', markers: ['.cursor/rules/project-rules.mdc', '.cursor/rules'] },
68
+ copilot: { dir: '.github', rootFile: '.github/copilot-instructions.md', markers: ['.github/copilot-instructions.md'] },
69
+ windsurf: { dir: '.', rootFile: '.windsurfrules', markers: ['.windsurfrules'] }
70
+ };
71
+
72
+ function colorize(code, text) {
73
+ const value = String(text);
74
+ if (!process.stdout.isTTY) return value;
75
+ return `${code}${value}${COLORS.reset}`;
76
+ }
77
+
78
+ function normalizeSkillName(skill) {
79
+ return String(skill || '').trim();
80
+ }
81
+
82
+ function normalizeToolName(tool) {
83
+ if (!tool) return null;
84
+ const value = String(tool).trim().toLowerCase().replace(/[\s-]+/g, '_');
85
+ if (!value) return null;
86
+ if (['auto', 'detect', 'default'].includes(value)) return 'auto';
87
+ if (['all', '*'].includes(value)) return 'all';
88
+ if (Object.prototype.hasOwnProperty.call(TOOL_ALIASES, value)) return TOOL_ALIASES[value];
89
+ if (SUPPORTED_TOOLS.includes(value)) return value;
90
+ return null;
91
+ }
92
+
93
+ function parseToolList(value) {
94
+ if (!value) return [];
95
+ return String(value)
96
+ .split(',')
97
+ .map(item => normalizeToolName(item))
98
+ .filter(tool => tool && tool !== 'auto' && tool !== 'all');
99
+ }
100
+
101
+ function resolveRequestedTools(toolOption, cwd = getInstallRoot()) {
102
+ if (!toolOption) {
103
+ return detectTools(cwd);
104
+ }
105
+
106
+ const normalized = normalizeToolName(toolOption);
107
+ if (normalized === 'all') {
108
+ return [...SUPPORTED_TOOLS];
109
+ }
110
+
111
+ const explicitList = parseToolList(toolOption);
112
+ if (explicitList.length > 0) {
113
+ return [...new Set(explicitList)];
114
+ }
115
+
116
+ if (normalized === 'auto' || normalized === null) {
117
+ return detectTools(cwd);
118
+ }
119
+
120
+ return [normalized];
121
+ }
122
+
123
+ function getSkillInfo(name) {
124
+ return SKILLS.find(skill => skill.name === name);
125
+ }
126
+
127
+ function getSelectedSkills(rawSkills, includeAll = false) {
128
+ if (includeAll || !rawSkills) return [...SKILLS.map(skill => skill.name)];
129
+ return [...new Set(rawSkills.split(',').map(item => normalizeSkillName(item.trim())).filter(Boolean))];
130
+ }
131
+
132
+ function getInstallRoot(cwd = INSTALL_ROOT) {
133
+ return path.resolve(cwd);
134
+ }
135
+
136
+ function findWorkspaceRoot(startDir = getInstallRoot()) {
137
+ let current = getInstallRoot(startDir);
138
+
139
+ while (true) {
140
+ if (SUPPORTED_TOOLS.some(tool => hasToolMarker(tool, current))) {
141
+ return current;
142
+ }
143
+
144
+ const parent = path.dirname(current);
145
+ if (parent === current) {
146
+ return startDir;
147
+ }
148
+
149
+ current = parent;
150
+ }
151
+ }
152
+
153
+ function hasToolMarker(tool, cwd = getInstallRoot()) {
154
+ const config = TOOL_CONFIGS[tool];
155
+ if (!config) return false;
156
+ return (config.markers || []).some(marker => fsSync.existsSync(path.join(cwd, marker)));
157
+ }
158
+
159
+ function detectTools(cwd = getInstallRoot()) {
160
+ const root = findWorkspaceRoot(cwd);
161
+ const rawOverride = process.env.PIG_SKILLS_TARGET_TOOL || process.env.PIG_SKILLS_TARGET_TOOLS;
162
+ const override = normalizeToolName(rawOverride);
163
+ const overrideList = parseToolList(rawOverride);
164
+
165
+ if (override === 'all') {
166
+ return [...SUPPORTED_TOOLS];
167
+ }
168
+ if (overrideList.length > 0) {
169
+ return [...new Set(overrideList)];
170
+ }
171
+
172
+ const detected = SUPPORTED_TOOLS.filter(tool => hasToolMarker(tool, root));
173
+ return detected.length > 0 ? detected : ['codex'];
174
+ }
175
+
176
+ function detectTool(cwd = getInstallRoot()) {
177
+ return detectTools(cwd)[0] || null;
178
+ }
179
+
180
+ function getTargetSkillDir(tool, cwd = process.cwd()) {
181
+ const config = TOOL_CONFIGS[tool];
182
+ if (!config) return null;
183
+ return path.join(cwd, config.dir);
184
+ }
185
+
186
+ async function pathExists(targetPath) {
187
+ try {
188
+ await fs.access(targetPath);
189
+ return true;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ async function ensureDir(targetPath) {
196
+ await fs.mkdir(targetPath, { recursive: true });
197
+ }
198
+
199
+ async function copyDirectory(sourcePath, destinationPath) {
200
+ await ensureDir(path.dirname(destinationPath));
201
+ await fs.cp(sourcePath, destinationPath, { recursive: true, force: true });
202
+ }
203
+
204
+ async function copyDirectoryIfMissing(sourcePath, destinationPath) {
205
+ if (await pathExists(destinationPath)) {
206
+ return false;
207
+ }
208
+
209
+ await copyDirectory(sourcePath, destinationPath);
210
+ return true;
211
+ }
212
+
213
+ async function readJson(filePath) {
214
+ const content = await fs.readFile(filePath, 'utf8');
215
+ return JSON.parse(content);
216
+ }
217
+
218
+ async function writeJson(filePath, value) {
219
+ await ensureDir(path.dirname(filePath));
220
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
221
+ }
222
+
223
+ async function removePath(targetPath) {
224
+ await fs.rm(targetPath, { recursive: true, force: true });
225
+ }
226
+
227
+ async function copySkill(skillName, tool, cwd = process.cwd()) {
228
+ const skill = normalizeSkillName(skillName);
229
+ if (!getSkillInfo(skill)) {
230
+ throw new Error(`Unknown skill: ${skillName}`);
231
+ }
232
+
233
+ const targetSkillDir = getTargetSkillDir(tool, cwd);
234
+ if (!targetSkillDir) {
235
+ throw new Error(`Unsupported tool: ${tool}`);
236
+ }
237
+
238
+ const sourceDir = path.join(SKILLS_DIR, skill);
239
+ const destDir = path.join(targetSkillDir, skill);
240
+ await ensureDir(targetSkillDir);
241
+ await copyDirectory(sourceDir, destDir);
242
+ }
243
+
244
+ async function writeRootFile(tool, cwd = process.cwd()) {
245
+ const config = TOOL_CONFIGS[tool];
246
+ if (!config || !config.rootFile) return;
247
+
248
+ const rootTarget = path.join(cwd, config.rootFile);
249
+ const rootSource = path.join(PACKAGE_ROOT, path.basename(config.rootFile));
250
+ if (await pathExists(rootSource)) {
251
+ await ensureDir(path.dirname(rootTarget));
252
+ await fs.copyFile(rootSource, rootTarget);
253
+ }
254
+ }
255
+
256
+ async function copySkillToCodexHome(skillName) {
257
+ const skill = normalizeSkillName(skillName);
258
+ if (!getSkillInfo(skill)) {
259
+ throw new Error(`Unknown skill: ${skillName}`);
260
+ }
261
+
262
+ const sourceDir = path.join(SKILLS_DIR, skill);
263
+ const destDir = path.join(CODEX_HOME_SKILLS_DIR, skill);
264
+ await ensureDir(CODEX_HOME_SKILLS_DIR);
265
+ await copyDirectory(sourceDir, destDir);
266
+ }
267
+
268
+ function buildMarketplaceEntry(pluginName) {
269
+ return {
270
+ name: pluginName,
271
+ source: {
272
+ source: 'local',
273
+ path: `./plugins/${pluginName}`
274
+ },
275
+ policy: {
276
+ installation: 'AVAILABLE',
277
+ authentication: 'ON_INSTALL'
278
+ },
279
+ category: 'Productivity'
280
+ };
281
+ }
282
+
283
+ async function writeCodexMarketplaceEntry(pluginName) {
284
+ await ensureDir(AGENTS_PLUGINS_DIR);
285
+ const existing = (await pathExists(AGENTS_MARKETPLACE_FILE))
286
+ ? await readJson(AGENTS_MARKETPLACE_FILE)
287
+ : { name: 'personal', interface: { displayName: 'Personal' }, plugins: [] };
288
+
289
+ if (!existing.name) existing.name = 'personal';
290
+ if (!existing.interface) existing.interface = { displayName: 'Personal' };
291
+ if (!Array.isArray(existing.plugins)) existing.plugins = [];
292
+
293
+ const entry = buildMarketplaceEntry(pluginName);
294
+ const index = existing.plugins.findIndex(plugin => plugin && plugin.name === pluginName);
295
+ if (index >= 0) {
296
+ existing.plugins[index] = entry;
297
+ } else {
298
+ existing.plugins.push(entry);
299
+ }
300
+
301
+ await writeJson(AGENTS_MARKETPLACE_FILE, existing);
302
+ }
303
+
304
+ async function installCodexCommandPack() {
305
+ if (!(await pathExists(COMMAND_PACK_SOURCE))) {
306
+ throw new Error(`Codex command pack not found: ${COMMAND_PACK_SOURCE}`);
307
+ }
308
+
309
+ const destDir = path.join(AGENTS_PLUGINS_DIR, COMMAND_PACK_NAME);
310
+ await ensureDir(AGENTS_PLUGINS_DIR);
311
+ await copyDirectory(COMMAND_PACK_SOURCE, destDir);
312
+ await writeCodexMarketplaceEntry(COMMAND_PACK_NAME);
313
+ }
314
+
315
+ async function installSharedWorkspaceAssets(cwd = getInstallRoot()) {
316
+ const sharedAssets = [
317
+ { source: path.join(PACKAGE_ROOT, 'skills', 'references'), destination: path.join(cwd, 'skills', 'references') },
318
+ { source: path.join(PACKAGE_ROOT, 'rules'), destination: path.join(cwd, 'rules') }
319
+ ];
320
+
321
+ for (const asset of sharedAssets) {
322
+ if (await pathExists(asset.source)) {
323
+ await copyDirectoryIfMissing(asset.source, asset.destination);
324
+ }
325
+ }
326
+ }
327
+
328
+ async function installSkills(skills, tool, cwd = getInstallRoot()) {
329
+ await installSharedWorkspaceAssets(cwd);
330
+ for (const skill of skills) {
331
+ await copySkill(skill, tool, cwd);
332
+ }
333
+ await writeRootFile(tool, cwd);
334
+ if (tool === 'codex') {
335
+ for (const skill of skills) {
336
+ await copySkillToCodexHome(skill);
337
+ }
338
+ await installCodexCommandPack();
339
+ }
340
+ }
341
+
342
+ async function installSkillsForTools(skills, tools, cwd = getInstallRoot()) {
343
+ const uniqueTools = [...new Set(tools.filter(Boolean))];
344
+ if (uniqueTools.length === 0) {
345
+ throw new Error('Unable to resolve any install targets.');
346
+ }
347
+
348
+ for (const tool of uniqueTools) {
349
+ await installSkills(skills, tool, cwd);
350
+ }
351
+ }
352
+
353
+ function parseArgs(argv) {
354
+ const positionals = [];
355
+ const options = {};
356
+
357
+ for (let index = 0; index < argv.length; index += 1) {
358
+ const token = argv[index];
359
+ if (token === '--all') {
360
+ options.all = true;
361
+ continue;
362
+ }
363
+
364
+ if (token === '-t' || token === '--tool') {
365
+ options.tool = argv[++index];
366
+ continue;
367
+ }
368
+
369
+ if (token.startsWith('--tool=')) {
370
+ options.tool = token.slice('--tool='.length);
371
+ continue;
372
+ }
373
+
374
+ if (token === '-s' || token === '--skills') {
375
+ options.skills = argv[++index];
376
+ continue;
377
+ }
378
+
379
+ if (token.startsWith('--skills=')) {
380
+ options.skills = token.slice('--skills='.length);
381
+ continue;
382
+ }
383
+
384
+ if (token === '-b' || token === '--bundle') {
385
+ options.bundle = argv[++index];
386
+ continue;
387
+ }
388
+
389
+ if (token.startsWith('--bundle=')) {
390
+ options.bundle = token.slice('--bundle='.length);
391
+ continue;
392
+ }
393
+
394
+ if (token === '-k' || token === '--skill') {
395
+ options.skill = argv[++index];
396
+ continue;
397
+ }
398
+
399
+ if (token.startsWith('--skill=')) {
400
+ options.skill = token.slice('--skill='.length);
401
+ continue;
402
+ }
403
+
404
+ if (token === '-p' || token === '--profile') {
405
+ options.profile = argv[++index];
406
+ continue;
407
+ }
408
+
409
+ if (token.startsWith('--profile=')) {
410
+ options.profile = token.slice('--profile='.length);
411
+ continue;
412
+ }
413
+
414
+ if (token === '-f' || token === '--files') {
415
+ options.files = argv[++index];
416
+ continue;
417
+ }
418
+
419
+ if (token.startsWith('--files=')) {
420
+ options.files = token.slice('--files='.length);
421
+ continue;
422
+ }
423
+
424
+ positionals.push(token);
425
+ }
426
+
427
+ return { options, positionals };
428
+ }
429
+
430
+ function printHelp() {
431
+ console.log(chalk.blue('Pig Skills CLI'));
432
+ console.log(chalk.gray(`Version: ${VERSION}`));
433
+ console.log('');
434
+ console.log('Usage:');
435
+ console.log(' pig-skills init [--tool codex] [--skills a,b] [--all]');
436
+ console.log(' pig-skills auto [--skills a,b] [--all]');
437
+ console.log(' pig-skills codex [--skills a,b] [--all]');
438
+ console.log(' pig-skills bootstrap');
439
+ console.log(' pig-skills rules [--bundle <bundle>] [--profile <profile>] [--skill name] [--files a,b]');
440
+ console.log(' pig-skills rules [--bundle <bundle>] [--profile <profile>] [--files a,b]');
441
+ console.log(' pig-skills list');
442
+ console.log(' pig-skills add <skill> [--tool codex]');
443
+ console.log(' pig-skills remove <skill> [--tool codex]');
444
+ console.log(' pig-skills info <skill>');
445
+ console.log(' pig-skills update [--tool codex]');
446
+ }
447
+
448
+ async function ensureRuntimeForInstall() {
449
+ const result = await ensureNpmRuntime({ quiet: false });
450
+ if (result.installed) {
451
+ console.log(chalk.green('Installed Node.js/npm runtime for this session.'));
452
+ }
453
+ return result;
454
+ }
455
+
456
+ async function run() {
457
+ const { options, positionals } = parseArgs(process.argv.slice(2));
458
+ const command = positionals[0];
459
+
460
+ if (!command) {
461
+ printHelp();
462
+ return;
463
+ }
464
+
465
+ if (command === 'init') {
466
+ const skills = getSelectedSkills(options.skills, options.all || !options.skills);
467
+ try {
468
+ await ensureRuntimeForInstall();
469
+ const tools = resolveRequestedTools(options.tool);
470
+ await installSkillsForTools(skills, tools);
471
+ console.log(chalk.green(`Installed ${skills.length} skills for ${tools.join(', ')}`));
472
+ } catch (error) {
473
+ console.error(chalk.red(error.message));
474
+ process.exitCode = 1;
475
+ }
476
+ return;
477
+ }
478
+
479
+ if (command === 'auto') {
480
+ const skills = getSelectedSkills(options.skills, options.all || !options.skills);
481
+ try {
482
+ await ensureRuntimeForInstall();
483
+ const tools = resolveRequestedTools('auto');
484
+ await installSkillsForTools(skills, tools);
485
+ console.log(chalk.green(`Auto-installed ${skills.length} skills for ${tools.join(', ')}`));
486
+ } catch (error) {
487
+ console.error(chalk.red(error.message));
488
+ process.exitCode = 1;
489
+ }
490
+ return;
491
+ }
492
+
493
+ if (command === 'codex') {
494
+ const skills = getSelectedSkills(options.skills, options.all || !options.skills);
495
+ try {
496
+ await ensureRuntimeForInstall();
497
+ await installSkills(skills, 'codex');
498
+ console.log(chalk.green(`Synced ${skills.length} skills into Codex`));
499
+ console.log(chalk.green(`Installed Codex command pack: ${COMMAND_PACK_NAME}`));
500
+ } catch (error) {
501
+ console.error(chalk.red(error.message));
502
+ process.exitCode = 1;
503
+ }
504
+ return;
505
+ }
506
+
507
+ if (command === 'rules') {
508
+ try {
509
+ if (options.bundle) {
510
+ process.env.PIG_SKILLS_RULE_BUNDLE = options.bundle;
511
+ }
512
+ if (options.profile) {
513
+ process.env.PIG_SKILLS_RULE_PROFILE = options.profile;
514
+ }
515
+ if (options.skill) {
516
+ process.env.PIG_SKILLS_RULE_SKILL = options.skill;
517
+ }
518
+ if (options.files) {
519
+ process.env.PIG_SKILLS_CHANGED_FILES = options.files;
520
+ }
521
+ const selection = detectRulesBundle(
522
+ process.env.INIT_CWD || process.cwd(),
523
+ options.files ? options.files.split(',').map(item => item.trim()).filter(Boolean) : []
524
+ );
525
+ console.log(JSON.stringify(selection, null, 2));
526
+ } catch (error) {
527
+ console.error(chalk.red(error.message));
528
+ process.exitCode = 1;
529
+ }
530
+ return;
531
+ }
532
+
533
+ if (command === 'bootstrap') {
534
+ try {
535
+ const result = await ensureRuntimeForInstall();
536
+ if (result.skipped) {
537
+ console.log(chalk.yellow('Runtime bootstrap skipped by PIG_SKILLS_SKIP_RUNTIME_BOOTSTRAP.'));
538
+ } else if (result.installed) {
539
+ console.log(chalk.green('Node.js/npm runtime is ready.'));
540
+ } else {
541
+ console.log(chalk.green('Node.js/npm runtime is already available.'));
542
+ }
543
+ } catch (error) {
544
+ console.error(chalk.red(error.message));
545
+ process.exitCode = 1;
546
+ }
547
+ return;
548
+ }
549
+
550
+ if (command === 'list') {
551
+ console.log(chalk.blue('Installed skills:'));
552
+ SKILLS.forEach(skill => {
553
+ console.log(chalk.green(` 鉁?${skill.name}`) + chalk.gray(` - ${skill.description}`));
554
+ });
555
+ console.log(chalk.gray(`\nTotal: ${SKILLS.length} skills`));
556
+ return;
557
+ }
558
+
559
+ if (command === 'add') {
560
+ const skillName = positionals[1];
561
+ if (!skillName) {
562
+ console.error(chalk.red('Missing skill name.'));
563
+ process.exitCode = 1;
564
+ return;
565
+ }
566
+
567
+ try {
568
+ await ensureRuntimeForInstall();
569
+ const tools = resolveRequestedTools(options.tool);
570
+ await installSharedWorkspaceAssets();
571
+ for (const tool of tools) {
572
+ await copySkill(skillName, tool);
573
+ await writeRootFile(tool);
574
+ if (tool === 'codex') {
575
+ await copySkillToCodexHome(skillName);
576
+ await installCodexCommandPack();
577
+ }
578
+ }
579
+ console.log(chalk.green(`Installed: ${normalizeSkillName(skillName)} -> ${tools.join(', ')}`));
580
+ } catch (error) {
581
+ console.error(chalk.red(error.message));
582
+ process.exitCode = 1;
583
+ }
584
+ return;
585
+ }
586
+
587
+ if (command === 'remove') {
588
+ const skillName = positionals[1];
589
+ if (!skillName) {
590
+ console.error(chalk.red('Missing skill name.'));
591
+ process.exitCode = 1;
592
+ return;
593
+ }
594
+
595
+ const normalizedSkill = normalizeSkillName(skillName);
596
+ try {
597
+ const tools = resolveRequestedTools(options.tool);
598
+ let removedAny = false;
599
+ for (const tool of tools) {
600
+ const targetSkillDir = getTargetSkillDir(tool);
601
+ if (!targetSkillDir) continue;
602
+ const skillDir = path.join(targetSkillDir, normalizedSkill);
603
+ if (await pathExists(skillDir)) {
604
+ await removePath(skillDir);
605
+ removedAny = true;
606
+ }
607
+ }
608
+ console.log(removedAny
609
+ ? chalk.green(`Removed: ${normalizedSkill}`)
610
+ : chalk.yellow(`Skill not found: ${normalizedSkill}`));
611
+ } catch (error) {
612
+ console.error(chalk.red(error.message));
613
+ process.exitCode = 1;
614
+ }
615
+ return;
616
+ }
617
+
618
+ if (command === 'info') {
619
+ const skillName = positionals[1];
620
+ if (!skillName) {
621
+ console.error(chalk.red('Missing skill name.'));
622
+ process.exitCode = 1;
623
+ return;
624
+ }
625
+
626
+ const normalizedSkill = normalizeSkillName(skillName);
627
+ const info = getSkillInfo(normalizedSkill);
628
+ if (!info) {
629
+ console.error(chalk.red(`Unknown skill: ${skillName}`));
630
+ process.exitCode = 1;
631
+ return;
632
+ }
633
+
634
+ console.log(chalk.blue(info.name));
635
+ console.log(chalk.gray(`Category: ${info.category}`));
636
+ console.log(chalk.gray(`Description: ${info.description}`));
637
+ console.log(chalk.gray(`Path: skills/${info.name}/SKILL.md`));
638
+ return;
639
+ }
640
+
641
+ if (command === 'update') {
642
+ try {
643
+ await ensureRuntimeForInstall();
644
+ const tools = resolveRequestedTools(options.tool);
645
+ await installSkillsForTools(SKILLS.map(skill => skill.name), tools);
646
+ console.log(chalk.green(`Updated ${SKILLS.length} skills for ${tools.join(', ')}`));
647
+ } catch (error) {
648
+ console.error(chalk.red(error.message));
649
+ process.exitCode = 1;
650
+ }
651
+ return;
652
+ }
653
+
654
+ console.error(chalk.red(`Unknown command: ${command}`));
655
+ printHelp();
656
+ process.exitCode = 1;
657
+ }
658
+
659
+ run().catch(error => {
660
+ console.error(chalk.red(error.message));
661
+ process.exitCode = 1;
662
+ });
663
+