@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
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require('child_process');
4
+ const path = require('path');
5
+
6
+ const cliPath = path.join(__dirname, 'cli.js');
7
+ const autoSyncDisabled = ['1', 'true', 'yes'].includes(String(process.env.PIG_SKILLS_SKIP_AUTO_SYNC || '').toLowerCase());
8
+
9
+ function print(message) {
10
+ process.stdout.write(`${message}\n`);
11
+ }
12
+
13
+ function warn(message) {
14
+ process.stderr.write(`${message}\n`);
15
+ }
16
+
17
+ function syncCodexSkills() {
18
+ const result = spawnSync(process.execPath, [cliPath, 'auto'], {
19
+ stdio: 'pipe',
20
+ env: {
21
+ ...process.env,
22
+ PIG_SKILLS_POSTINSTALL: '1'
23
+ }
24
+ });
25
+
26
+ if (result.error) {
27
+ return { ok: false, output: result.error.message };
28
+ }
29
+
30
+ const output = [result.stdout, result.stderr]
31
+ .filter(Boolean)
32
+ .map(buffer => buffer.toString('utf8').trim())
33
+ .filter(Boolean)
34
+ .join('\n');
35
+
36
+ return {
37
+ ok: result.status === 0,
38
+ output: output || `exit code ${result.status ?? 0}`
39
+ };
40
+ }
41
+
42
+ print('Pig Cloud Skills installed.');
43
+
44
+ if (autoSyncDisabled) {
45
+ print('Auto-sync skipped because PIG_SKILLS_SKIP_AUTO_SYNC is set.');
46
+ print('Run `pig-skills auto` later to sync skills for the detected client(s).');
47
+ process.exit(0);
48
+ }
49
+
50
+ const result = syncCodexSkills();
51
+
52
+ if (result.ok) {
53
+ print('Auto-sync complete: skills are installed for the detected client(s).');
54
+ process.exit(0);
55
+ }
56
+
57
+ warn('Auto-sync could not complete during install.');
58
+ warn(result.output);
59
+ warn('Run `pig-skills auto` after install to finish syncing manually.');
60
+ process.exit(0);
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const PACKAGE_ROOT = path.join(__dirname, '..');
7
+ const RULES_ROOT = path.join(PACKAGE_ROOT, 'rules');
8
+ const REGISTRY_PATH = path.join(RULES_ROOT, 'bundles.json');
9
+ const SKILL_PROFILE_MAP_PATH = path.join(RULES_ROOT, 'skill-profile-map.json');
10
+ const SKILLS_ROOT = path.join(PACKAGE_ROOT, 'skills');
11
+ const CORE_BUNDLE_NAME = 'core';
12
+ const CORE_PROFILE_NAME = 'core';
13
+ const REVIEW_BUNDLE_NAME = 'review';
14
+
15
+ function readRegistry() {
16
+ return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
17
+ }
18
+
19
+ function readSkillProfileMap() {
20
+ return JSON.parse(fs.readFileSync(SKILL_PROFILE_MAP_PATH, 'utf8'));
21
+ }
22
+
23
+ function readSkillRuleProfile(skillName) {
24
+ const skillFile = path.join(SKILLS_ROOT, skillName, 'SKILL.md');
25
+ if (!fs.existsSync(skillFile)) {
26
+ return null;
27
+ }
28
+
29
+ const content = fs.readFileSync(skillFile, 'utf8');
30
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
31
+ if (!frontmatterMatch) {
32
+ return null;
33
+ }
34
+
35
+ const profileMatch = frontmatterMatch[1].match(/^rule_profile:\s*(.+)$/m);
36
+ return profileMatch ? profileMatch[1].trim() : null;
37
+ }
38
+
39
+ function readSkillRuleBundle(skillName) {
40
+ const skillProfileMap = readSkillProfileMap();
41
+ const mapped = skillProfileMap[skillName];
42
+ if (mapped && mapped.bundle) {
43
+ return mapped.bundle;
44
+ }
45
+
46
+ const profile = readSkillRuleProfile(skillName);
47
+ if (!profile || profile === CORE_PROFILE_NAME) {
48
+ return CORE_BUNDLE_NAME;
49
+ }
50
+
51
+ return findBundleByProfile(readRegistry(), profile);
52
+ }
53
+
54
+ function combineLayers(layers, meta = {}) {
55
+ const flattenedFiles = [];
56
+ const seen = new Set();
57
+
58
+ for (const layer of layers) {
59
+ for (const file of layer.files || []) {
60
+ if (seen.has(file)) continue;
61
+ seen.add(file);
62
+ flattenedFiles.push(file);
63
+ }
64
+ }
65
+
66
+ const primary = meta.baseLayer || layers[layers.length - 1] || layers[0] || null;
67
+ return {
68
+ bundle: meta.bundle || (primary ? primary.bundle : CORE_BUNDLE_NAME),
69
+ profile: meta.profile || (primary ? primary.profile : null),
70
+ skill: meta.skill || null,
71
+ source: meta.source || 'workspace detection',
72
+ entry: meta.entry || (primary ? primary.entry : 'core/index.md'),
73
+ files: flattenedFiles,
74
+ description: primary ? primary.description : 'Baseline rules',
75
+ layers
76
+ };
77
+ }
78
+
79
+ function getLayer(registry, bundleName, profileName = '') {
80
+ const bundle = registry[bundleName];
81
+ if (!bundle) return null;
82
+
83
+ const profile = profileName && bundle.profiles && bundle.profiles[profileName]
84
+ ? profileName
85
+ : (bundle.profiles && bundle.profiles[CORE_PROFILE_NAME] ? CORE_PROFILE_NAME : '');
86
+ const selected = profile ? bundle.profiles[profile] : bundle;
87
+
88
+ if (!selected) return null;
89
+
90
+ return {
91
+ bundle: bundleName,
92
+ profile: profile || null,
93
+ entry: selected.entry,
94
+ files: selected.files,
95
+ description: selected.description || bundle.description
96
+ };
97
+ }
98
+
99
+ function listSkillDirectories() {
100
+ if (!fs.existsSync(SKILLS_ROOT)) {
101
+ return [];
102
+ }
103
+
104
+ return fs.readdirSync(SKILLS_ROOT, { withFileTypes: true })
105
+ .filter((entry) => entry.isDirectory() && entry.name !== 'references')
106
+ .map((entry) => entry.name)
107
+ .sort();
108
+ }
109
+
110
+ function shouldValidateSkillProfiles() {
111
+ const skip = String(process.env.PIG_SKILLS_SKIP_RULE_PROFILE_VALIDATION || '').trim().toLowerCase();
112
+ if (['1', 'true', 'yes'].includes(skip)) {
113
+ return false;
114
+ }
115
+
116
+ const explicit = String(process.env.PIG_SKILLS_VALIDATE_RULE_PROFILES || '').trim().toLowerCase();
117
+ if (['1', 'true', 'yes'].includes(explicit)) {
118
+ return true;
119
+ }
120
+
121
+ return process.env.NODE_ENV !== 'production';
122
+ }
123
+
124
+ function validateSkillProfileConsistency() {
125
+ const registry = readRegistry();
126
+ const skillProfileMap = readSkillProfileMap();
127
+ const skillDirs = listSkillDirectories();
128
+ const issues = [];
129
+
130
+ const mappedSkills = Object.keys(skillProfileMap).sort();
131
+ for (const skillName of skillDirs) {
132
+ if (!skillProfileMap[skillName]) {
133
+ issues.push(`Missing mapping for skill: ${skillName}`);
134
+ continue;
135
+ }
136
+
137
+ const declaredProfile = readSkillRuleProfile(skillName);
138
+ if (!declaredProfile) {
139
+ issues.push(`Missing rule_profile declaration for skill: ${skillName}`);
140
+ continue;
141
+ }
142
+
143
+ const mappedProfile = skillProfileMap[skillName].profile || CORE_PROFILE_NAME;
144
+ if (declaredProfile !== mappedProfile) {
145
+ issues.push(`Profile mismatch for ${skillName}: skill=${declaredProfile}, map=${mappedProfile}`);
146
+ }
147
+
148
+ const mappedBundle = skillProfileMap[skillName].bundle || CORE_BUNDLE_NAME;
149
+ const bundle = registry[mappedBundle];
150
+ if (!bundle) {
151
+ issues.push(`Unknown bundle mapped for ${skillName}: ${mappedBundle}`);
152
+ continue;
153
+ }
154
+
155
+ if (!bundle.profiles || !bundle.profiles[mappedProfile]) {
156
+ issues.push(`Unknown profile mapped for ${skillName}: ${mappedBundle}/${mappedProfile}`);
157
+ }
158
+ }
159
+
160
+ for (const mappedSkill of mappedSkills) {
161
+ if (!skillDirs.includes(mappedSkill)) {
162
+ issues.push(`Mapping exists for missing skill directory: ${mappedSkill}`);
163
+ }
164
+ }
165
+
166
+ if (issues.length > 0) {
167
+ const error = new Error(`Rule profile consistency check failed:\n- ${issues.join('\n- ')}`);
168
+ error.issues = issues;
169
+ throw error;
170
+ }
171
+
172
+ return {
173
+ skillCount: skillDirs.length,
174
+ mappedSkillCount: mappedSkills.length
175
+ };
176
+ }
177
+
178
+ function findBundleByProfile(registry, profileName) {
179
+ if (!profileName || profileName === CORE_PROFILE_NAME) {
180
+ return registry[CORE_BUNDLE_NAME] ? CORE_BUNDLE_NAME : Object.keys(registry)[0] || CORE_BUNDLE_NAME;
181
+ }
182
+
183
+ for (const [bundleName, bundleConfig] of Object.entries(registry)) {
184
+ if (bundleConfig && bundleConfig.profiles && bundleConfig.profiles[profileName]) {
185
+ return bundleName;
186
+ }
187
+ }
188
+
189
+ return null;
190
+ }
191
+
192
+ function existsInRoot(cwd, relativePath) {
193
+ return fs.existsSync(path.join(cwd, relativePath));
194
+ }
195
+
196
+ function normalizeChangedFiles(changedFilesInput) {
197
+ if (!changedFilesInput) {
198
+ return [];
199
+ }
200
+
201
+ const items = Array.isArray(changedFilesInput)
202
+ ? changedFilesInput
203
+ : String(changedFilesInput)
204
+ .split(/[\r\n,]+/)
205
+ .map((item) => item.trim());
206
+
207
+ return [...new Set(items.filter(Boolean))];
208
+ }
209
+
210
+ function hasChangedFile(changedFiles, matcher) {
211
+ return changedFiles.some((filePath) => matcher(filePath.replace(/\\/g, '/')));
212
+ }
213
+
214
+ function hasCodingSignals(cwd) {
215
+ const packageJsonPath = path.join(cwd, 'package.json');
216
+ if (fs.existsSync(packageJsonPath)) {
217
+ try {
218
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
219
+ if (pkg && typeof pkg === 'object') {
220
+ const hasScripts = pkg.scripts && typeof pkg.scripts === 'object' && Object.keys(pkg.scripts).length > 0;
221
+ const hasDeps = (pkg.dependencies && Object.keys(pkg.dependencies).length > 0)
222
+ || (pkg.devDependencies && Object.keys(pkg.devDependencies).length > 0);
223
+ if (hasScripts || hasDeps) {
224
+ return true;
225
+ }
226
+ }
227
+ } catch {
228
+ // Ignore malformed package.json and fall back to file heuristics.
229
+ }
230
+ }
231
+
232
+ return [
233
+ 'pom.xml',
234
+ 'build.gradle',
235
+ 'build.gradle.kts',
236
+ 'mvnw',
237
+ 'gradlew',
238
+ 'src',
239
+ 'src/main/java',
240
+ 'src/main/resources',
241
+ 'README.md',
242
+ 'README.en.md'
243
+ ].some((hint) => existsInRoot(cwd, hint));
244
+ }
245
+
246
+ function hasJavaSignals(cwd) {
247
+ return [
248
+ 'pom.xml',
249
+ 'build.gradle',
250
+ 'build.gradle.kts',
251
+ 'mvnw',
252
+ 'gradlew',
253
+ 'src/main/java',
254
+ 'src/main/resources',
255
+ 'src/test/java'
256
+ ].some((hint) => existsInRoot(cwd, hint));
257
+ }
258
+
259
+ function hasVueSignals(cwd) {
260
+ return [
261
+ 'frontend',
262
+ 'frontend/src',
263
+ 'src/App.vue',
264
+ 'src/main.ts',
265
+ 'src/main.js',
266
+ 'src/components',
267
+ 'src/views',
268
+ 'vite.config.ts',
269
+ 'vite.config.js',
270
+ 'vue.config.js'
271
+ ].some((hint) => existsInRoot(cwd, hint));
272
+ }
273
+
274
+ function hasTypeScriptSignals(cwd) {
275
+ return [
276
+ 'tsconfig.json',
277
+ 'tsconfig.app.json',
278
+ 'tsconfig.node.json',
279
+ 'src',
280
+ 'frontend/src',
281
+ 'src/main.ts',
282
+ 'src/env.d.ts',
283
+ 'frontend/tsconfig.json'
284
+ ].some((hint) => existsInRoot(cwd, hint));
285
+ }
286
+
287
+ function detectReviewAutoOverlayProfiles(cwd, registry, changedFilesInput = []) {
288
+ const profiles = [];
289
+ const reviewBundle = registry[REVIEW_BUNDLE_NAME] || {};
290
+ const reviewProfiles = reviewBundle.profiles || {};
291
+ const changedFiles = normalizeChangedFiles(changedFilesInput);
292
+
293
+ if (changedFiles.length > 0) {
294
+ if (reviewProfiles['code-java'] && (
295
+ hasChangedFile(changedFiles, (filePath) => /\.(java|kt|xml)$/i.test(filePath))
296
+ || hasChangedFile(changedFiles, (filePath) => /(^|\/)(pom\.xml|build\.gradle(\.kts)?|mvnw|gradlew)$/i.test(filePath))
297
+ || hasChangedFile(changedFiles, (filePath) => /(^|\/)(src\/main\/java|src\/test\/java)(\/|$)/i.test(filePath))
298
+ )) {
299
+ profiles.push('code-java');
300
+ }
301
+
302
+ if (reviewProfiles['code-vue'] && (
303
+ hasChangedFile(changedFiles, (filePath) => /\.vue$/i.test(filePath))
304
+ || hasChangedFile(changedFiles, (filePath) => /(^|\/)(src\/main\.(ts|js)|src\/App\.vue|src\/components|src\/views|frontend\/)/i.test(filePath))
305
+ || hasChangedFile(changedFiles, (filePath) => /(^|\/)(vite\.config\.(ts|js)|vue\.config\.js)$/i.test(filePath))
306
+ )) {
307
+ profiles.push('code-vue');
308
+ }
309
+
310
+ if (reviewProfiles['code-ts'] && (
311
+ hasChangedFile(changedFiles, (filePath) => /\.(ts|tsx|d\.ts)$/i.test(filePath))
312
+ || hasChangedFile(changedFiles, (filePath) => /(^|\/)(tsconfig(\.[^.]+)?\.json|vite\.config\.ts|eslint\.config\.ts|src\/env\.d\.ts)$/i.test(filePath))
313
+ || hasChangedFile(changedFiles, (filePath) => /(^|\/)(src\/store|src\/stores|src\/state|frontend\/src\/store|frontend\/src\/stores|frontend\/src\/state)(\/|$)/i.test(filePath))
314
+ )) {
315
+ profiles.push('code-ts');
316
+ }
317
+
318
+ return profiles;
319
+ }
320
+
321
+ if (hasJavaSignals(cwd) && reviewProfiles['code-java']) {
322
+ profiles.push('code-java');
323
+ }
324
+
325
+ if (hasVueSignals(cwd) && reviewProfiles['code-vue']) {
326
+ profiles.push('code-vue');
327
+ }
328
+
329
+ if (hasTypeScriptSignals(cwd) && reviewProfiles['code-ts']) {
330
+ profiles.push('code-ts');
331
+ }
332
+
333
+ return profiles;
334
+ }
335
+
336
+ function parseBundleOverride(override) {
337
+ if (!override) {
338
+ return { bundle: '', profile: '' };
339
+ }
340
+
341
+ if (override.includes(':')) {
342
+ const [bundle, profile] = override.split(':', 2);
343
+ return { bundle, profile };
344
+ }
345
+
346
+ if (override.includes('/')) {
347
+ const [bundle, profile] = override.split('/', 2);
348
+ return { bundle, profile };
349
+ }
350
+
351
+ return { bundle: override, profile: '' };
352
+ }
353
+
354
+ function detectRulesBundle(cwd = process.cwd(), changedFilesInput = []) {
355
+ const registry = readRegistry();
356
+ const skillProfileMap = readSkillProfileMap();
357
+ const override = String(process.env.PIG_SKILLS_RULE_BUNDLE || process.env.PIG_SKILLS_RULES_BUNDLE || '').trim();
358
+ const profileOverride = String(process.env.PIG_SKILLS_RULE_PROFILE || '').trim();
359
+ const skillOverride = String(process.env.PIG_SKILLS_RULE_SKILL || '').trim();
360
+ const overlayOverride = String(process.env.PIG_SKILLS_RULE_OVERLAY || '').trim();
361
+ const changedFiles = normalizeChangedFiles(
362
+ changedFilesInput.length > 0
363
+ ? changedFilesInput
364
+ : process.env.PIG_SKILLS_CHANGED_FILES || ''
365
+ );
366
+
367
+ if (shouldValidateSkillProfiles()) {
368
+ validateSkillProfileConsistency();
369
+ }
370
+
371
+ function resolveBundleSelection(bundleName, profileName = '') {
372
+ const layers = [];
373
+ let baseLayer = null;
374
+ const seenLayers = new Set();
375
+
376
+ function addLayer(bundle, profile, markAsBase = false) {
377
+ const layer = getLayer(registry, bundle, profile);
378
+ if (!layer) return null;
379
+ const key = `${layer.bundle}:${layer.profile || ''}`;
380
+ if (seenLayers.has(key)) {
381
+ if (markAsBase) baseLayer = layer;
382
+ return layer;
383
+ }
384
+ seenLayers.add(key);
385
+ layers.push(layer);
386
+ if (markAsBase) baseLayer = layer;
387
+ return layer;
388
+ }
389
+
390
+ if (bundleName === CORE_BUNDLE_NAME) {
391
+ addLayer(CORE_BUNDLE_NAME, CORE_PROFILE_NAME, true);
392
+ } else {
393
+ addLayer(CORE_BUNDLE_NAME, CORE_PROFILE_NAME);
394
+ addLayer(bundleName, profileName, true);
395
+ }
396
+
397
+ if (bundleName === REVIEW_BUNDLE_NAME) {
398
+ for (const autoProfile of detectReviewAutoOverlayProfiles(cwd, registry, changedFiles)) {
399
+ addLayer(REVIEW_BUNDLE_NAME, autoProfile);
400
+ }
401
+ }
402
+
403
+ if (overlayOverride && registry[overlayOverride]) {
404
+ const overlayProfile = profileOverride && registry[overlayOverride].profiles && registry[overlayOverride].profiles[profileOverride]
405
+ ? profileOverride
406
+ : (registry[overlayOverride].profiles && registry[overlayOverride].profiles[CORE_PROFILE_NAME] ? CORE_PROFILE_NAME : '');
407
+ addLayer(overlayOverride, overlayProfile);
408
+ }
409
+
410
+ if (layers.length === 0) return null;
411
+ return combineLayers(layers, {
412
+ bundle: bundleName,
413
+ profile: profileName || null,
414
+ baseLayer,
415
+ source: 'environment override'
416
+ });
417
+ }
418
+
419
+ if (override) {
420
+ const parsed = parseBundleOverride(override);
421
+ if (registry[parsed.bundle]) {
422
+ const resolved = resolveBundleSelection(parsed.bundle, profileOverride || parsed.profile || '');
423
+ if (resolved) return resolved;
424
+ }
425
+ }
426
+
427
+ if (skillOverride) {
428
+ const mapped = skillProfileMap[skillOverride] || {};
429
+ const declaredProfile = readSkillRuleProfile(skillOverride);
430
+ const selectedProfile = profileOverride || mapped.profile || declaredProfile || '';
431
+ const selectedBundle = mapped.bundle || readSkillRuleBundle(skillOverride) || findBundleByProfile(registry, selectedProfile);
432
+ const resolved = selectedBundle ? resolveBundleSelection(selectedBundle, selectedProfile) : null;
433
+ if (resolved) {
434
+ return {
435
+ ...resolved,
436
+ skill: skillOverride,
437
+ source: declaredProfile ? 'skill declaration' : 'skill mapping'
438
+ };
439
+ }
440
+ }
441
+
442
+ const bundle = hasCodingSignals(cwd) ? 'coding' : CORE_BUNDLE_NAME;
443
+ const bundleConfig = registry[bundle];
444
+ const profileName = bundle === CORE_BUNDLE_NAME
445
+ ? CORE_PROFILE_NAME
446
+ : (profileOverride && bundleConfig && bundleConfig.profiles && bundleConfig.profiles[profileOverride]
447
+ ? profileOverride
448
+ : (bundleConfig && bundleConfig.profiles && bundleConfig.profiles.analysis ? 'analysis' : 'core'));
449
+ const selection = resolveBundleSelection(bundle, profileName || '');
450
+
451
+ if (!selection) {
452
+ return combineLayers(
453
+ [getLayer(registry, CORE_BUNDLE_NAME, CORE_PROFILE_NAME)].filter(Boolean),
454
+ {
455
+ bundle: CORE_BUNDLE_NAME,
456
+ profile: CORE_PROFILE_NAME,
457
+ skill: skillOverride || null,
458
+ source: 'workspace detection'
459
+ }
460
+ );
461
+ }
462
+
463
+ return {
464
+ ...selection,
465
+ skill: skillOverride || null,
466
+ source: 'workspace detection'
467
+ };
468
+ }
469
+
470
+ if (require.main === module) {
471
+ const cwd = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd();
472
+ process.stdout.write(`${JSON.stringify(detectRulesBundle(cwd), null, 2)}\n`);
473
+ }
474
+
475
+ module.exports = {
476
+ detectRulesBundle,
477
+ readRegistry,
478
+ readSkillProfileMap,
479
+ readSkillRuleProfile,
480
+ readSkillRuleBundle,
481
+ validateSkillProfileConsistency,
482
+ shouldValidateSkillProfiles,
483
+ findBundleByProfile
484
+ };
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ const NPM_CANDIDATES = process.platform === 'win32'
8
+ ? [
9
+ path.join(process.env.ProgramFiles || 'C:\\Program Files', 'nodejs', 'npm.cmd'),
10
+ path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'nodejs', 'npm.cmd'),
11
+ path.join(process.env.LocalAppData || '', 'Programs', 'nodejs', 'npm.cmd'),
12
+ path.join(process.env.LocalAppData || '', 'Microsoft', 'WinGet', 'Links', 'npm.cmd')
13
+ ]
14
+ : [];
15
+
16
+ function commandAvailable(command) {
17
+ const result = spawnSync(command, ['--version'], { stdio: 'ignore' });
18
+ return !result.error && result.status === 0;
19
+ }
20
+
21
+ function findInstalledNpm() {
22
+ for (const candidate of NPM_CANDIDATES) {
23
+ if (candidate && fs.existsSync(candidate)) {
24
+ return candidate;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+
30
+ function refreshPathFromNpm(npmPath) {
31
+ if (!npmPath) return;
32
+ const npmDir = path.dirname(npmPath);
33
+ const currentPath = String(process.env.PATH || '');
34
+ const parts = currentPath.split(path.delimiter).filter(Boolean);
35
+ if (!parts.some((part) => part.toLowerCase() === npmDir.toLowerCase())) {
36
+ process.env.PATH = [npmDir, ...parts].join(path.delimiter);
37
+ }
38
+ }
39
+
40
+ function installNodeJsWithWinget() {
41
+ const args = [
42
+ 'install',
43
+ '--id',
44
+ 'OpenJS.NodeJS.LTS',
45
+ '-e',
46
+ '--source',
47
+ 'winget',
48
+ '--accept-package-agreements',
49
+ '--accept-source-agreements',
50
+ '--silent',
51
+ '--disable-interactivity'
52
+ ];
53
+
54
+ const result = spawnSync('winget', args, {
55
+ encoding: 'utf8',
56
+ stdio: ['ignore', 'pipe', 'pipe']
57
+ });
58
+
59
+ if (result.error) {
60
+ throw new Error(`Failed to launch winget: ${result.error.message}`);
61
+ }
62
+
63
+ if (result.status !== 0) {
64
+ const message = [result.stdout, result.stderr]
65
+ .filter(Boolean)
66
+ .map((text) => String(text).trim())
67
+ .filter(Boolean)
68
+ .join('\n');
69
+ throw new Error(message || `winget exited with code ${result.status}`);
70
+ }
71
+ }
72
+
73
+ async function ensureNpmRuntime(options = {}) {
74
+ const skip = ['1', 'true', 'yes'].includes(String(process.env.PIG_SKILLS_SKIP_RUNTIME_BOOTSTRAP || '').toLowerCase());
75
+ if (skip) {
76
+ return { installed: false, skipped: true, npmPath: commandAvailable('npm') ? 'npm' : null };
77
+ }
78
+
79
+ if (commandAvailable('npm')) {
80
+ return { installed: false, skipped: false, npmPath: 'npm' };
81
+ }
82
+
83
+ if (process.platform !== 'win32') {
84
+ throw new Error(
85
+ 'npm is not available in PATH. Install Node.js 20+ first, then rerun the skill installer.'
86
+ );
87
+ }
88
+
89
+ if (!commandAvailable('winget')) {
90
+ throw new Error(
91
+ 'npm is not available and winget is not installed. Install Node.js LTS manually, then rerun the skill installer.'
92
+ );
93
+ }
94
+
95
+ if (!options.quiet) {
96
+ process.stdout.write('npm is missing. Installing Node.js LTS through winget...\n');
97
+ }
98
+
99
+ installNodeJsWithWinget();
100
+
101
+ const npmPath = findInstalledNpm();
102
+ refreshPathFromNpm(npmPath);
103
+
104
+ if (!npmPath && !commandAvailable('npm')) {
105
+ throw new Error(
106
+ 'Node.js was installed, but npm is still not visible in this shell. Restart the shell and rerun the skill installer.'
107
+ );
108
+ }
109
+
110
+ return {
111
+ installed: true,
112
+ skipped: false,
113
+ npmPath: npmPath || 'npm'
114
+ };
115
+ }
116
+
117
+ module.exports = {
118
+ ensureNpmRuntime,
119
+ commandAvailable,
120
+ findInstalledNpm
121
+ };
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ const packageJson = require('./package.json');
2
+
3
+ module.exports = {
4
+ name: packageJson.name,
5
+ version: packageJson.version,
6
+ bin: packageJson.bin
7
+ };
package/install.cmd ADDED
@@ -0,0 +1,5 @@
1
+ @echo off
2
+ setlocal
3
+ set "SCRIPT_DIR=%~dp0"
4
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%install.ps1" %*
5
+ exit /b %ERRORLEVEL%