@pigcloud/skills 1.0.11 → 1.1.2

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 (298) hide show
  1. package/CHANGELOG.md +25 -20
  2. package/README.en.md +41 -75
  3. package/README.md +26 -39
  4. package/bin/cli.js +260 -151
  5. package/bin/rules-loader.js +271 -484
  6. package/codex-commands/README.md +25 -23
  7. package/codex-commands/commands/analyze.md +21 -22
  8. package/codex-commands/commands/build.md +22 -22
  9. package/codex-commands/commands/design.md +21 -22
  10. package/codex-commands/commands/distill.md +21 -21
  11. package/codex-commands/commands/doc.md +21 -22
  12. package/codex-commands/commands/infra.md +21 -21
  13. package/codex-commands/commands/init.md +20 -20
  14. package/codex-commands/commands/kb.md +21 -20
  15. package/codex-commands/commands/perf.md +21 -21
  16. package/codex-commands/commands/prd.md +21 -22
  17. package/codex-commands/commands/review.md +21 -22
  18. package/codex-commands/commands/security.md +21 -22
  19. package/codex-commands/commands/test.md +21 -21
  20. package/codex-commands/commands/workflow.md +21 -20
  21. package/package.json +5 -2
  22. package/rules/core/index.md +26 -41
  23. package/rules/delivery/index.md +25 -0
  24. package/rules/design/index.md +25 -0
  25. package/rules/discovery/index.md +25 -0
  26. package/rules/implementation/index.md +25 -0
  27. package/rules/index.md +24 -39
  28. package/rules/overlays/index.md +19 -19
  29. package/rules/overlays/pig-cloud.md +20 -45
  30. package/rules/shared/index.md +25 -0
  31. package/rules/skill-stage-map.json +26 -0
  32. package/rules/stages.json +48 -0
  33. package/rules/validation/index.md +25 -0
  34. package/scripts/add-skill-reference-nav.js +3 -0
  35. package/scripts/bootstrap-skill-specs.js +96 -0
  36. package/scripts/ci-validator.sh +51 -114
  37. package/scripts/generate-skill-prompt-library.js +3 -0
  38. package/scripts/golden-prompt-suite.current.js +211 -0
  39. package/scripts/migrate-skill-packages.js +309 -0
  40. package/scripts/run-golden-replays.js +110 -79
  41. package/scripts/validate-rules.js +128 -125
  42. package/scripts/validate-skill-replay-signals.js +45 -57
  43. package/scripts/validate-skill-shapes.js +153 -127
  44. package/scripts/validate-skill-stop-rules.js +54 -46
  45. package/skills/01-discovery/ambiguity-detection/SKILL.md +30 -0
  46. package/skills/01-discovery/ambiguity-detection/assets/golden-prompt-suite.current.js +22 -0
  47. package/skills/01-discovery/ambiguity-detection/references/README.md +17 -0
  48. package/skills/01-discovery/ambiguity-detection/references/cases.md +26 -0
  49. package/skills/01-discovery/ambiguity-detection/references/prompt-template.md +18 -0
  50. package/skills/01-discovery/ambiguity-detection/skill-spec.json +26 -0
  51. package/skills/01-discovery/business-analysis/SKILL.md +30 -0
  52. package/skills/01-discovery/business-analysis/assets/golden-prompt-suite.current.js +22 -0
  53. package/skills/01-discovery/business-analysis/references/README.md +17 -0
  54. package/skills/01-discovery/business-analysis/references/cases.md +26 -0
  55. package/skills/01-discovery/business-analysis/references/prompt-template.md +18 -0
  56. package/skills/01-discovery/business-analysis/skill-spec.json +26 -0
  57. package/skills/01-discovery/impact-analysis/SKILL.md +30 -0
  58. package/skills/01-discovery/impact-analysis/assets/golden-prompt-suite.current.js +22 -0
  59. package/skills/01-discovery/impact-analysis/references/README.md +17 -0
  60. package/skills/01-discovery/impact-analysis/references/cases.md +26 -0
  61. package/skills/01-discovery/impact-analysis/references/prompt-template.md +18 -0
  62. package/skills/01-discovery/impact-analysis/skill-spec.json +26 -0
  63. package/skills/01-discovery/requirement-discovery/SKILL.md +30 -0
  64. package/skills/01-discovery/requirement-discovery/assets/golden-prompt-suite.current.js +24 -0
  65. package/skills/01-discovery/requirement-discovery/references/README.md +17 -0
  66. package/skills/01-discovery/requirement-discovery/references/cases.md +28 -0
  67. package/skills/01-discovery/requirement-discovery/references/prompt-template.md +18 -0
  68. package/skills/01-discovery/requirement-discovery/skill-spec.json +26 -0
  69. package/skills/02-design/api-design/SKILL.md +29 -0
  70. package/skills/02-design/api-design/assets/golden-prompt-suite.current.js +22 -0
  71. package/skills/02-design/api-design/references/README.md +17 -0
  72. package/skills/02-design/api-design/references/cases.md +26 -0
  73. package/skills/02-design/api-design/references/prompt-template.md +18 -0
  74. package/skills/02-design/api-design/skill-spec.json +25 -0
  75. package/skills/02-design/architecture-design/SKILL.md +29 -0
  76. package/skills/02-design/architecture-design/assets/golden-prompt-suite.current.js +22 -0
  77. package/skills/02-design/architecture-design/references/README.md +17 -0
  78. package/skills/02-design/architecture-design/references/cases.md +26 -0
  79. package/skills/02-design/architecture-design/references/prompt-template.md +18 -0
  80. package/skills/02-design/architecture-design/skill-spec.json +25 -0
  81. package/skills/02-design/database-design/SKILL.md +29 -0
  82. package/skills/02-design/database-design/assets/golden-prompt-suite.current.js +22 -0
  83. package/skills/02-design/database-design/references/README.md +17 -0
  84. package/skills/02-design/database-design/references/cases.md +26 -0
  85. package/skills/02-design/database-design/references/prompt-template.md +18 -0
  86. package/skills/02-design/database-design/skill-spec.json +25 -0
  87. package/skills/02-design/task-breakdown/SKILL.md +29 -0
  88. package/skills/02-design/task-breakdown/assets/golden-prompt-suite.current.js +22 -0
  89. package/skills/02-design/task-breakdown/references/README.md +17 -0
  90. package/skills/02-design/task-breakdown/references/cases.md +26 -0
  91. package/skills/02-design/task-breakdown/references/prompt-template.md +18 -0
  92. package/skills/02-design/task-breakdown/skill-spec.json +25 -0
  93. package/skills/03-implementation/backend-development/SKILL.md +29 -0
  94. package/skills/03-implementation/backend-development/assets/golden-prompt-suite.current.js +22 -0
  95. package/skills/03-implementation/backend-development/references/README.md +17 -0
  96. package/skills/03-implementation/backend-development/references/cases.md +26 -0
  97. package/skills/03-implementation/backend-development/references/prompt-template.md +18 -0
  98. package/skills/03-implementation/backend-development/skill-spec.json +25 -0
  99. package/skills/03-implementation/bug-fix/SKILL.md +29 -0
  100. package/skills/03-implementation/bug-fix/assets/golden-prompt-suite.current.js +22 -0
  101. package/skills/03-implementation/bug-fix/references/README.md +17 -0
  102. package/skills/03-implementation/bug-fix/references/cases.md +26 -0
  103. package/skills/03-implementation/bug-fix/references/prompt-template.md +18 -0
  104. package/skills/03-implementation/bug-fix/skill-spec.json +25 -0
  105. package/skills/03-implementation/database-change/SKILL.md +29 -0
  106. package/skills/03-implementation/database-change/assets/golden-prompt-suite.current.js +22 -0
  107. package/skills/03-implementation/database-change/references/README.md +17 -0
  108. package/skills/03-implementation/database-change/references/cases.md +26 -0
  109. package/skills/03-implementation/database-change/references/prompt-template.md +18 -0
  110. package/skills/03-implementation/database-change/skill-spec.json +25 -0
  111. package/skills/03-implementation/frontend-development/SKILL.md +29 -0
  112. package/skills/03-implementation/frontend-development/assets/golden-prompt-suite.current.js +22 -0
  113. package/skills/03-implementation/frontend-development/references/README.md +17 -0
  114. package/skills/03-implementation/frontend-development/references/cases.md +26 -0
  115. package/skills/03-implementation/frontend-development/references/prompt-template.md +18 -0
  116. package/skills/03-implementation/frontend-development/skill-spec.json +25 -0
  117. package/skills/04-validation/code-review/SKILL.md +29 -0
  118. package/skills/04-validation/code-review/assets/golden-prompt-suite.current.js +22 -0
  119. package/skills/04-validation/code-review/references/README.md +17 -0
  120. package/skills/04-validation/code-review/references/cases.md +26 -0
  121. package/skills/04-validation/code-review/references/prompt-template.md +18 -0
  122. package/skills/04-validation/code-review/skill-spec.json +25 -0
  123. package/skills/04-validation/performance-review/SKILL.md +29 -0
  124. package/skills/04-validation/performance-review/assets/golden-prompt-suite.current.js +22 -0
  125. package/skills/04-validation/performance-review/references/README.md +17 -0
  126. package/skills/04-validation/performance-review/references/cases.md +26 -0
  127. package/skills/04-validation/performance-review/references/prompt-template.md +18 -0
  128. package/skills/04-validation/performance-review/skill-spec.json +25 -0
  129. package/skills/04-validation/regression-check/SKILL.md +29 -0
  130. package/skills/04-validation/regression-check/assets/golden-prompt-suite.current.js +22 -0
  131. package/skills/04-validation/regression-check/references/README.md +17 -0
  132. package/skills/04-validation/regression-check/references/cases.md +26 -0
  133. package/skills/04-validation/regression-check/references/prompt-template.md +18 -0
  134. package/skills/04-validation/regression-check/skill-spec.json +25 -0
  135. package/skills/04-validation/security-review/SKILL.md +29 -0
  136. package/skills/04-validation/security-review/assets/golden-prompt-suite.current.js +22 -0
  137. package/skills/04-validation/security-review/references/README.md +17 -0
  138. package/skills/04-validation/security-review/references/cases.md +26 -0
  139. package/skills/04-validation/security-review/references/prompt-template.md +18 -0
  140. package/skills/04-validation/security-review/skill-spec.json +25 -0
  141. package/skills/04-validation/unit-test/SKILL.md +29 -0
  142. package/skills/04-validation/unit-test/assets/golden-prompt-suite.current.js +22 -0
  143. package/skills/04-validation/unit-test/references/README.md +17 -0
  144. package/skills/04-validation/unit-test/references/cases.md +26 -0
  145. package/skills/04-validation/unit-test/references/prompt-template.md +18 -0
  146. package/skills/04-validation/unit-test/skill-spec.json +25 -0
  147. package/skills/05-delivery/change-log/SKILL.md +29 -0
  148. package/skills/05-delivery/change-log/assets/golden-prompt-suite.current.js +22 -0
  149. package/skills/05-delivery/change-log/references/README.md +17 -0
  150. package/skills/05-delivery/change-log/references/cases.md +26 -0
  151. package/skills/05-delivery/change-log/references/prompt-template.md +18 -0
  152. package/skills/05-delivery/change-log/skill-spec.json +25 -0
  153. package/skills/05-delivery/deployment-guide/SKILL.md +29 -0
  154. package/skills/05-delivery/deployment-guide/assets/golden-prompt-suite.current.js +22 -0
  155. package/skills/05-delivery/deployment-guide/references/README.md +17 -0
  156. package/skills/05-delivery/deployment-guide/references/cases.md +26 -0
  157. package/skills/05-delivery/deployment-guide/references/prompt-template.md +18 -0
  158. package/skills/05-delivery/deployment-guide/skill-spec.json +25 -0
  159. package/skills/05-delivery/release-check/SKILL.md +29 -0
  160. package/skills/05-delivery/release-check/assets/golden-prompt-suite.current.js +22 -0
  161. package/skills/05-delivery/release-check/references/README.md +17 -0
  162. package/skills/05-delivery/release-check/references/cases.md +26 -0
  163. package/skills/05-delivery/release-check/references/prompt-template.md +18 -0
  164. package/skills/05-delivery/release-check/skill-spec.json +25 -0
  165. package/skills/05-delivery/release-validation/SKILL.md +29 -0
  166. package/skills/05-delivery/release-validation/assets/golden-prompt-suite.current.js +22 -0
  167. package/skills/05-delivery/release-validation/references/README.md +17 -0
  168. package/skills/05-delivery/release-validation/references/cases.md +26 -0
  169. package/skills/05-delivery/release-validation/references/prompt-template.md +18 -0
  170. package/skills/05-delivery/release-validation/skill-spec.json +25 -0
  171. package/skills/shared/codebase-learning/SKILL.md +29 -0
  172. package/skills/shared/codebase-learning/assets/golden-prompt-suite.current.js +22 -0
  173. package/skills/shared/codebase-learning/references/README.md +17 -0
  174. package/skills/shared/codebase-learning/references/cases.md +26 -0
  175. package/skills/shared/codebase-learning/references/prompt-template.md +18 -0
  176. package/skills/shared/codebase-learning/skill-spec.json +25 -0
  177. package/skills/shared/evidence-collector/SKILL.md +29 -0
  178. package/skills/shared/evidence-collector/assets/golden-prompt-suite.current.js +22 -0
  179. package/skills/shared/evidence-collector/references/README.md +17 -0
  180. package/skills/shared/evidence-collector/references/cases.md +26 -0
  181. package/skills/shared/evidence-collector/references/prompt-template.md +18 -0
  182. package/skills/shared/evidence-collector/skill-spec.json +25 -0
  183. package/skills/shared/framework-guide/SKILL.md +28 -0
  184. package/skills/shared/framework-guide/assets/golden-prompt-suite.current.js +22 -0
  185. package/skills/shared/framework-guide/references/README.md +17 -0
  186. package/skills/shared/framework-guide/references/cases.md +26 -0
  187. package/skills/shared/framework-guide/references/prompt-template.md +18 -0
  188. package/skills/shared/framework-guide/skill-spec.json +24 -0
  189. package/rules/bundles.json +0 -358
  190. package/rules/coding/analysis.md +0 -27
  191. package/rules/coding/backend/cache-invalidation.md +0 -30
  192. package/rules/coding/backend/cache-keying.md +0 -30
  193. package/rules/coding/backend/cache.md +0 -37
  194. package/rules/coding/backend/database.md +0 -32
  195. package/rules/coding/backend/feign.md +0 -30
  196. package/rules/coding/backend/index.md +0 -42
  197. package/rules/coding/backend/query.md +0 -32
  198. package/rules/coding/backend/remote.md +0 -33
  199. package/rules/coding/backend/transaction-boundary.md +0 -30
  200. package/rules/coding/backend/transaction-rollback.md +0 -30
  201. package/rules/coding/backend/transaction.md +0 -38
  202. package/rules/coding/boundary.md +0 -25
  203. package/rules/coding/implementation.md +0 -26
  204. package/rules/coding/index.md +0 -38
  205. package/rules/coding/scaffold.md +0 -28
  206. package/rules/coding/testing.md +0 -29
  207. package/rules/coding/validation.md +0 -29
  208. package/rules/core/code-quality.md +0 -30
  209. package/rules/core/evidence.md +0 -26
  210. package/rules/core/interface.md +0 -26
  211. package/rules/core/iteration.md +0 -26
  212. package/rules/core/layer-boundary.md +0 -25
  213. package/rules/core/logging.md +0 -26
  214. package/rules/core/security.md +0 -26
  215. package/rules/core/task-boundary.md +0 -27
  216. package/rules/docs/api.md +0 -34
  217. package/rules/docs/capture-summary.md +0 -29
  218. package/rules/docs/capture.md +0 -34
  219. package/rules/docs/contract.md +0 -30
  220. package/rules/docs/decision-log.md +0 -32
  221. package/rules/docs/examples.md +0 -28
  222. package/rules/docs/index.md +0 -49
  223. package/rules/docs/reference.md +0 -32
  224. package/rules/overlays/pig-cloud/controller.md +0 -33
  225. package/rules/overlays/pig-cloud/dto-vo.md +0 -33
  226. package/rules/overlays/pig-cloud/entity.md +0 -32
  227. package/rules/overlays/pig-cloud/exception.md +0 -32
  228. package/rules/overlays/pig-cloud/layering.md +0 -31
  229. package/rules/overlays/pig-cloud/mapper.md +0 -32
  230. package/rules/overlays/pig-cloud/query-style.md +0 -32
  231. package/rules/overlays/pig-cloud/rest-response.md +0 -33
  232. package/rules/overlays/pig-cloud/service.md +0 -33
  233. package/rules/overlays/pig-cloud/transactions.md +0 -32
  234. package/rules/overlays/pig-cloud/validation.md +0 -33
  235. package/rules/product/acceptance.md +0 -25
  236. package/rules/product/briefing.md +0 -27
  237. package/rules/product/index.md +0 -36
  238. package/rules/product/intake.md +0 -27
  239. package/rules/product/modeling.md +0 -25
  240. package/rules/product/project-context.md +0 -29
  241. package/rules/review/code.md +0 -35
  242. package/rules/review/evidence.md +0 -31
  243. package/rules/review/index.md +0 -50
  244. package/rules/review/java.md +0 -42
  245. package/rules/review/performance.md +0 -38
  246. package/rules/review/rubric.md +0 -28
  247. package/rules/review/security.md +0 -38
  248. package/rules/review/ts.md +0 -33
  249. package/rules/review/vue.md +0 -33
  250. package/rules/skill-profile-map.json +0 -59
  251. package/rules/skill-profile-map.md +0 -29
  252. package/rules/workflow/handoff.md +0 -25
  253. package/rules/workflow/index.md +0 -37
  254. package/rules/workflow/refinement.md +0 -29
  255. package/rules/workflow/router.md +0 -25
  256. package/rules/workflow/selection.md +0 -25
  257. package/rules/workflow/stop.md +0 -25
  258. package/skills/api-contract-docs/SKILL.md +0 -77
  259. package/skills/business-fact-extraction/SKILL.md +0 -337
  260. package/skills/business-fact-extraction/scripts/write-knowledge-base.js +0 -228
  261. package/skills/code-review/SKILL.md +0 -136
  262. package/skills/code-review/references/findings-template.md +0 -51
  263. package/skills/code-review/references/performance-checklist.md +0 -213
  264. package/skills/code-review/references/rubric.md +0 -232
  265. package/skills/code-review/references/rules.md +0 -32
  266. package/skills/code-review/references/security-checklist.md +0 -178
  267. package/skills/code-review/references/stack-notes.md +0 -25
  268. package/skills/code-review/references/template-review.md +0 -39
  269. package/skills/code-review/scripts/lint-code-review.mjs +0 -431
  270. package/skills/domain-modeling/SKILL.md +0 -81
  271. package/skills/domain-modeling/references/README.md +0 -134
  272. package/skills/domain-modeling/references/distillation-checklist.md +0 -44
  273. package/skills/domain-modeling/references/test-cases-template.md +0 -128
  274. package/skills/environment-deploy/SKILL.md +0 -81
  275. package/skills/feature-build/SKILL.md +0 -122
  276. package/skills/feature-build/references/coding-checklist.md +0 -97
  277. package/skills/feature-build/references/comment-specification.md +0 -89
  278. package/skills/knowledge-capture/SKILL.md +0 -93
  279. package/skills/performance-audit/SKILL.md +0 -118
  280. package/skills/project-bootstrap/SKILL.md +0 -81
  281. package/skills/references/anti-rationalization.md +0 -144
  282. package/skills/references/business-fact-extraction.md +0 -415
  283. package/skills/references/engineering-delivery-method.md +0 -64
  284. package/skills/references/engineering-delivery-template.md +0 -81
  285. package/skills/references/golden-prompt-suite.js +0 -436
  286. package/skills/references/golden-prompt-suite.md +0 -33
  287. package/skills/references/project-requirement-alignment.md +0 -42
  288. package/skills/references/rule-loading-map.md +0 -117
  289. package/skills/references/skill-authoring-standard.md +0 -74
  290. package/skills/references/skill-boundary-template.md +0 -71
  291. package/skills/references/skill-enhanced-template.md +0 -103
  292. package/skills/references/skill-reference-matrix.md +0 -62
  293. package/skills/security-audit/SKILL.md +0 -119
  294. package/skills/spec-refinement/SKILL.md +0 -149
  295. package/skills/technical-design/SKILL.md +0 -106
  296. package/skills/technical-design/references/solid-checklist.md +0 -199
  297. package/skills/test-design/SKILL.md +0 -92
  298. package/skills/workflow-router/SKILL.md +0 -86
@@ -1,484 +1,271 @@
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
- };
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 STAGE_REGISTRY_PATH = path.join(RULES_ROOT, 'stages.json');
9
+ const SKILL_STAGE_MAP_PATH = path.join(RULES_ROOT, 'skill-stage-map.json');
10
+ const SKILLS_ROOT = path.join(PACKAGE_ROOT, 'skills');
11
+ const CORE_STAGE_NAME = 'core';
12
+ const OVERLAY_STAGE_NAME = 'overlays';
13
+
14
+ function readRegistry() {
15
+ return JSON.parse(fs.readFileSync(STAGE_REGISTRY_PATH, 'utf8'));
16
+ }
17
+
18
+ function readSkillStageMap() {
19
+ return JSON.parse(fs.readFileSync(SKILL_STAGE_MAP_PATH, 'utf8'));
20
+ }
21
+
22
+ function readSkillStage(skillName) {
23
+ return readSkillStageMap()[skillName]?.stage || null;
24
+ }
25
+
26
+ function listSkillDirectories() {
27
+ if (!fs.existsSync(SKILLS_ROOT)) {
28
+ return [];
29
+ }
30
+
31
+ const results = [];
32
+
33
+ function walk(dir) {
34
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
35
+ if (entry.name === 'references') {
36
+ continue;
37
+ }
38
+
39
+ const fullPath = path.join(dir, entry.name);
40
+ if (entry.isDirectory()) {
41
+ const skillFile = path.join(fullPath, 'SKILL.md');
42
+ if (fs.existsSync(skillFile)) {
43
+ results.push(path.relative(SKILLS_ROOT, fullPath).replace(/\\/g, '/'));
44
+ }
45
+ walk(fullPath);
46
+ }
47
+ }
48
+ }
49
+
50
+ walk(SKILLS_ROOT);
51
+ return results.sort();
52
+ }
53
+
54
+ function readSkillName(skillDir) {
55
+ const skillFile = path.join(skillDir, 'SKILL.md');
56
+ if (!fs.existsSync(skillFile)) {
57
+ return '';
58
+ }
59
+
60
+ const content = fs.readFileSync(skillFile, 'utf8');
61
+ const match = content.match(/^name:\s*(.+)$/m);
62
+ return match ? match[1].trim() : '';
63
+ }
64
+
65
+ function validateSkillStageConsistency() {
66
+ const registry = readRegistry();
67
+ const stageMap = readSkillStageMap();
68
+ const skillDirs = listSkillDirectories();
69
+ const issues = [];
70
+
71
+ const mappedSkills = Object.keys(stageMap).sort();
72
+ const discoveredSkillNames = [];
73
+
74
+ for (const skillPath of skillDirs) {
75
+ const skillName = readSkillName(path.join(SKILLS_ROOT, skillPath));
76
+ if (!skillName) {
77
+ issues.push(`Missing skill name in: ${skillPath}`);
78
+ continue;
79
+ }
80
+
81
+ discoveredSkillNames.push(skillName);
82
+ const mapping = stageMap[skillName];
83
+ if (!mapping) {
84
+ issues.push(`Missing stage mapping for skill: ${skillPath}`);
85
+ continue;
86
+ }
87
+
88
+ const stageName = mapping.stage;
89
+ const stage = registry[stageName];
90
+ if (!stage) {
91
+ issues.push(`Unknown stage mapped for ${skillPath}: ${stageName}`);
92
+ continue;
93
+ }
94
+
95
+ const files = new Set(stage.files || []);
96
+ if (stage.entry) {
97
+ files.add(stage.entry);
98
+ }
99
+ for (const relativePath of files) {
100
+ const filePath = path.join(RULES_ROOT, relativePath);
101
+ if (!fs.existsSync(filePath)) {
102
+ issues.push(`Missing stage file for ${skillPath}: ${relativePath}`);
103
+ }
104
+ }
105
+ }
106
+
107
+ for (const mappedSkill of mappedSkills) {
108
+ if (!discoveredSkillNames.includes(mappedSkill)) {
109
+ issues.push(`Mapping exists for missing skill directory: ${mappedSkill}`);
110
+ }
111
+ }
112
+
113
+ if (issues.length > 0) {
114
+ const error = new Error(`Rule stage consistency check failed:\n- ${issues.join('\n- ')}`);
115
+ error.issues = issues;
116
+ throw error;
117
+ }
118
+
119
+ return {
120
+ skillCount: skillDirs.length,
121
+ mappedSkillCount: mappedSkills.length
122
+ };
123
+ }
124
+
125
+ function normalizeStageName(stageName) {
126
+ return String(stageName || '').trim();
127
+ }
128
+
129
+ function getLayer(registry, stageName, variantName = '') {
130
+ const stage = registry[stageName];
131
+ if (!stage) return null;
132
+
133
+ if (variantName) {
134
+ const variant = stage.variants && stage.variants[variantName];
135
+ if (!variant) return null;
136
+ return {
137
+ stage: stageName,
138
+ variant: variantName,
139
+ entry: variant.entry,
140
+ files: variant.files,
141
+ description: variant.description || stage.description
142
+ };
143
+ }
144
+
145
+ return {
146
+ stage: stageName,
147
+ variant: null,
148
+ entry: stage.entry,
149
+ files: stage.files,
150
+ description: stage.description
151
+ };
152
+ }
153
+
154
+ function combineLayers(layers, meta = {}) {
155
+ const flattenedFiles = [];
156
+ const seen = new Set();
157
+
158
+ for (const layer of layers) {
159
+ for (const file of layer.files || []) {
160
+ if (seen.has(file)) continue;
161
+ seen.add(file);
162
+ flattenedFiles.push(file);
163
+ }
164
+ }
165
+
166
+ const primary = meta.baseLayer || layers[layers.length - 1] || layers[0] || null;
167
+ return {
168
+ stage: meta.stage || (primary ? primary.stage : CORE_STAGE_NAME),
169
+ overlay: meta.overlay || null,
170
+ skill: meta.skill || null,
171
+ source: meta.source || 'stage map',
172
+ entry: meta.entry || (primary ? primary.entry : 'core/index.md'),
173
+ files: flattenedFiles,
174
+ description: primary ? primary.description : 'Baseline rules',
175
+ layers
176
+ };
177
+ }
178
+
179
+ function resolveStageSelection(registry, stageName, overlayName = '', meta = {}) {
180
+ const layers = [];
181
+ let baseLayer = null;
182
+ const seenLayers = new Set();
183
+
184
+ function addLayer(selectedStage, variant = '', markAsBase = false) {
185
+ const layer = getLayer(registry, selectedStage, variant);
186
+ if (!layer) return null;
187
+ const key = `${layer.stage}:${layer.variant || ''}`;
188
+ if (seenLayers.has(key)) {
189
+ if (markAsBase) baseLayer = layer;
190
+ return layer;
191
+ }
192
+ seenLayers.add(key);
193
+ layers.push(layer);
194
+ if (markAsBase) baseLayer = layer;
195
+ return layer;
196
+ }
197
+
198
+ addLayer(CORE_STAGE_NAME, '', stageName === CORE_STAGE_NAME);
199
+
200
+ if (stageName !== CORE_STAGE_NAME) {
201
+ addLayer(stageName, '', true);
202
+ }
203
+
204
+ if (overlayName) {
205
+ const overlayExists = registry[OVERLAY_STAGE_NAME]
206
+ && registry[OVERLAY_STAGE_NAME].variants
207
+ && registry[OVERLAY_STAGE_NAME].variants[overlayName];
208
+ if (!overlayExists) {
209
+ throw new Error(`Unknown overlay: ${overlayName}`);
210
+ }
211
+ addLayer(OVERLAY_STAGE_NAME, overlayName, false);
212
+ }
213
+
214
+ if (layers.length === 0) return null;
215
+ return combineLayers(layers, {
216
+ stage: stageName,
217
+ overlay: overlayName || null,
218
+ baseLayer,
219
+ source: meta.source || 'explicit selection'
220
+ });
221
+ }
222
+
223
+ function readEnvironmentStage() {
224
+ return normalizeStageName(process.env.PIG_SKILLS_RULE_STAGE || process.env.PIG_SKILLS_RULES_STAGE);
225
+ }
226
+
227
+ function readEnvironmentOverlay() {
228
+ return normalizeStageName(process.env.PIG_SKILLS_RULE_OVERLAY);
229
+ }
230
+
231
+ function detectRulesStage(cwd = process.cwd()) { // eslint-disable-line no-unused-vars
232
+ const registry = readRegistry();
233
+ const stageMap = readSkillStageMap();
234
+ const stageOverride = readEnvironmentStage();
235
+ const skillOverride = normalizeStageName(process.env.PIG_SKILLS_RULE_SKILL);
236
+ const overlayOverride = readEnvironmentOverlay();
237
+
238
+ if (stageOverride) {
239
+ if (!registry[stageOverride]) {
240
+ throw new Error(`Unknown stage: ${stageOverride}`);
241
+ }
242
+ return resolveStageSelection(registry, stageOverride, overlayOverride, { source: 'stage override' });
243
+ }
244
+
245
+ if (skillOverride) {
246
+ const mapped = stageMap[skillOverride];
247
+ if (!mapped) {
248
+ throw new Error(`Unknown skill mapping: ${skillOverride}`);
249
+ }
250
+ return resolveStageSelection(registry, mapped.stage, overlayOverride, {
251
+ skill: skillOverride,
252
+ source: 'skill mapping'
253
+ });
254
+ }
255
+
256
+ return resolveStageSelection(registry, CORE_STAGE_NAME, overlayOverride, { source: 'default' });
257
+ }
258
+
259
+ if (require.main === module) {
260
+ const cwd = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd();
261
+ process.stdout.write(`${JSON.stringify(detectRulesStage(cwd), null, 2)}\n`);
262
+ }
263
+
264
+ module.exports = {
265
+ detectRulesStage,
266
+ readRegistry,
267
+ readSkillStageMap,
268
+ readSkillStage,
269
+ validateSkillStageConsistency,
270
+ resolveStageSelection
271
+ };