scc-universal 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/.claude-plugin/plugin.json +44 -0
  2. package/.cursor/agents/deep-researcher.md +142 -0
  3. package/.cursor/agents/doc-updater.md +219 -0
  4. package/.cursor/agents/eval-runner.md +335 -0
  5. package/.cursor/agents/learning-engine.md +210 -0
  6. package/.cursor/agents/loop-operator.md +245 -0
  7. package/.cursor/agents/refactor-cleaner.md +119 -0
  8. package/.cursor/agents/sf-admin-agent.md +127 -0
  9. package/.cursor/agents/sf-agentforce-agent.md +126 -0
  10. package/.cursor/agents/sf-apex-agent.md +117 -0
  11. package/.cursor/agents/sf-architect.md +426 -0
  12. package/.cursor/agents/sf-aura-reviewer.md +369 -0
  13. package/.cursor/agents/sf-bugfix-agent.md +101 -0
  14. package/.cursor/agents/sf-flow-agent.md +155 -0
  15. package/.cursor/agents/sf-integration-agent.md +141 -0
  16. package/.cursor/agents/sf-lwc-agent.md +123 -0
  17. package/.cursor/agents/sf-review-agent.md +357 -0
  18. package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
  19. package/.cursor/hooks/adapter.js +81 -0
  20. package/.cursor/hooks/after-file-edit.js +26 -0
  21. package/.cursor/hooks/after-mcp-execution.js +12 -0
  22. package/.cursor/hooks/after-shell-execution.js +30 -0
  23. package/.cursor/hooks/after-tab-file-edit.js +12 -0
  24. package/.cursor/hooks/before-mcp-execution.js +11 -0
  25. package/.cursor/hooks/before-read-file.js +13 -0
  26. package/.cursor/hooks/before-shell-execution.js +29 -0
  27. package/.cursor/hooks/before-submit-prompt.js +23 -0
  28. package/.cursor/hooks/pre-compact.js +7 -0
  29. package/.cursor/hooks/session-end.js +10 -0
  30. package/.cursor/hooks/session-start.js +10 -0
  31. package/.cursor/hooks/stop.js +18 -0
  32. package/.cursor/hooks/subagent-start.js +10 -0
  33. package/.cursor/hooks/subagent-stop.js +10 -0
  34. package/.cursor/hooks.json +107 -0
  35. package/.cursor/skills/aside/SKILL.md +115 -0
  36. package/.cursor/skills/checkpoint/SKILL.md +50 -0
  37. package/.cursor/skills/configure-scc/SKILL.md +160 -0
  38. package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
  39. package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
  40. package/.cursor/skills/model-route/SKILL.md +81 -0
  41. package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
  42. package/.cursor/skills/refactor-clean/SKILL.md +133 -0
  43. package/.cursor/skills/resume-session/SKILL.md +111 -0
  44. package/.cursor/skills/save-session/SKILL.md +183 -0
  45. package/.cursor/skills/search-first/SKILL.md +140 -0
  46. package/.cursor/skills/security-scan/SKILL.md +142 -0
  47. package/.cursor/skills/sessions/SKILL.md +124 -0
  48. package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
  49. package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
  50. package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
  51. package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
  52. package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
  53. package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
  54. package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
  55. package/.cursor/skills/sf-api-design/SKILL.md +237 -0
  56. package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
  57. package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
  58. package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
  59. package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
  60. package/.cursor/skills/sf-debugging/SKILL.md +362 -0
  61. package/.cursor/skills/sf-deployment/SKILL.md +291 -0
  62. package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
  63. package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
  64. package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
  65. package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
  66. package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
  67. package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
  68. package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
  69. package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
  70. package/.cursor/skills/sf-help/SKILL.md +156 -0
  71. package/.cursor/skills/sf-integration/SKILL.md +479 -0
  72. package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
  73. package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
  74. package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
  75. package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
  76. package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
  77. package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
  78. package/.cursor/skills/sf-security/SKILL.md +330 -0
  79. package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
  80. package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
  81. package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
  82. package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
  83. package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
  84. package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
  85. package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
  86. package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
  87. package/.cursor/skills/strategic-compact/SKILL.md +205 -0
  88. package/.cursor/skills/update-docs/SKILL.md +162 -0
  89. package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
  90. package/.cursor-plugin/plugin.json +26 -0
  91. package/LICENSE +21 -0
  92. package/README.md +522 -0
  93. package/agents/deep-researcher.md +145 -0
  94. package/agents/doc-updater.md +222 -0
  95. package/agents/eval-runner.md +340 -0
  96. package/agents/learning-engine.md +211 -0
  97. package/agents/loop-operator.md +247 -0
  98. package/agents/refactor-cleaner.md +122 -0
  99. package/agents/sf-admin-agent.md +131 -0
  100. package/agents/sf-agentforce-agent.md +132 -0
  101. package/agents/sf-apex-agent.md +124 -0
  102. package/agents/sf-architect.md +435 -0
  103. package/agents/sf-aura-reviewer.md +372 -0
  104. package/agents/sf-bugfix-agent.md +105 -0
  105. package/agents/sf-flow-agent.md +159 -0
  106. package/agents/sf-integration-agent.md +146 -0
  107. package/agents/sf-lwc-agent.md +127 -0
  108. package/agents/sf-review-agent.md +366 -0
  109. package/agents/sf-visualforce-reviewer.md +468 -0
  110. package/assets/logo.svg +18 -0
  111. package/docs/ARCHITECTURE.md +133 -0
  112. package/docs/authoring-guide.md +373 -0
  113. package/docs/hook-development.md +578 -0
  114. package/docs/token-optimization.md +139 -0
  115. package/docs/workflow-examples.md +645 -0
  116. package/examples/agentforce-action/README.md +227 -0
  117. package/examples/apex-trigger-handler/README.md +114 -0
  118. package/examples/devops-pipeline/README.md +325 -0
  119. package/examples/flow-automation/README.md +188 -0
  120. package/examples/integration-pattern/README.md +416 -0
  121. package/examples/lwc-component/README.md +180 -0
  122. package/examples/platform-events/README.md +492 -0
  123. package/examples/scratch-org-setup/README.md +138 -0
  124. package/examples/security-audit/README.md +244 -0
  125. package/examples/visualforce-migration/README.md +314 -0
  126. package/hooks/hooks.json +338 -0
  127. package/hooks/memory-persistence/README.md +73 -0
  128. package/manifests/install-modules.json +217 -0
  129. package/manifests/install-profiles.json +17 -0
  130. package/mcp-configs/mcp-servers.json +19 -0
  131. package/package.json +89 -0
  132. package/schemas/hooks.schema.json +123 -0
  133. package/schemas/install-modules.schema.json +76 -0
  134. package/schemas/install-profiles.schema.json +28 -0
  135. package/schemas/install-state.schema.json +73 -0
  136. package/schemas/package-manager.schema.json +18 -0
  137. package/schemas/plugin.schema.json +112 -0
  138. package/schemas/scc-install-config.schema.json +29 -0
  139. package/schemas/state-store.schema.json +111 -0
  140. package/scripts/cli/install-apply.js +170 -0
  141. package/scripts/cli/uninstall.js +193 -0
  142. package/scripts/hooks/check-console-log.js +101 -0
  143. package/scripts/hooks/check-hook-enabled.js +17 -0
  144. package/scripts/hooks/check-platform-docs-age.js +48 -0
  145. package/scripts/hooks/cost-tracker.js +78 -0
  146. package/scripts/hooks/doc-file-warning.js +63 -0
  147. package/scripts/hooks/evaluate-session.js +98 -0
  148. package/scripts/hooks/governor-check.js +220 -0
  149. package/scripts/hooks/learning-observe.sh +206 -0
  150. package/scripts/hooks/mcp-health-check.js +588 -0
  151. package/scripts/hooks/post-bash-build-complete.js +34 -0
  152. package/scripts/hooks/post-bash-pr-created.js +43 -0
  153. package/scripts/hooks/post-edit-console-warn.js +61 -0
  154. package/scripts/hooks/post-edit-format.js +79 -0
  155. package/scripts/hooks/post-edit-typecheck.js +98 -0
  156. package/scripts/hooks/post-write.js +168 -0
  157. package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
  158. package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
  159. package/scripts/hooks/pre-compact.js +51 -0
  160. package/scripts/hooks/pre-tool-use.js +163 -0
  161. package/scripts/hooks/pre-write-doc-warn.js +9 -0
  162. package/scripts/hooks/quality-gate.js +251 -0
  163. package/scripts/hooks/run-with-flags-shell.sh +32 -0
  164. package/scripts/hooks/run-with-flags.js +135 -0
  165. package/scripts/hooks/session-end-marker.js +29 -0
  166. package/scripts/hooks/session-end.js +311 -0
  167. package/scripts/hooks/session-start.js +202 -0
  168. package/scripts/hooks/sfdx-scanner-check.js +142 -0
  169. package/scripts/hooks/sfdx-validate.js +119 -0
  170. package/scripts/hooks/stop-hook.js +170 -0
  171. package/scripts/hooks/suggest-compact.js +67 -0
  172. package/scripts/lib/agent-adapter.js +82 -0
  173. package/scripts/lib/apex-analysis.js +194 -0
  174. package/scripts/lib/hook-flags.js +74 -0
  175. package/scripts/lib/install-config.js +73 -0
  176. package/scripts/lib/install-executor.js +363 -0
  177. package/scripts/lib/install-state.js +121 -0
  178. package/scripts/lib/orchestration-session.js +299 -0
  179. package/scripts/lib/package-manager.js +124 -0
  180. package/scripts/lib/project-detect.js +228 -0
  181. package/scripts/lib/schema-validator.js +190 -0
  182. package/scripts/lib/skill-adapter.js +100 -0
  183. package/scripts/lib/state-store.js +376 -0
  184. package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
  185. package/scripts/lib/utils.js +313 -0
  186. package/scripts/scc.js +164 -0
  187. package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
  188. package/skills/_reference/APEX_CURSOR.md +159 -0
  189. package/skills/_reference/API_VERSIONS.md +78 -0
  190. package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
  191. package/skills/_reference/ASYNC_PATTERNS.md +163 -0
  192. package/skills/_reference/AURA_COMPONENTS.md +146 -0
  193. package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
  194. package/skills/_reference/DATA_MODELING.md +124 -0
  195. package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
  196. package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
  197. package/skills/_reference/DEPRECATIONS.md +79 -0
  198. package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
  199. package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
  200. package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
  201. package/skills/_reference/FLOW_PATTERNS.md +113 -0
  202. package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
  203. package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
  204. package/skills/_reference/LWC_PATTERNS.md +79 -0
  205. package/skills/_reference/METADATA_TYPES.md +115 -0
  206. package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
  207. package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
  208. package/skills/_reference/PLATFORM_EVENTS.md +121 -0
  209. package/skills/_reference/REPORTING_API.md +143 -0
  210. package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
  211. package/skills/_reference/SECURITY_PATTERNS.md +127 -0
  212. package/skills/_reference/SHARING_MODEL.md +120 -0
  213. package/skills/_reference/SOQL_PATTERNS.md +119 -0
  214. package/skills/_reference/TESTING_STANDARDS.md +96 -0
  215. package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
  216. package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
  217. package/skills/aside/SKILL.md +118 -0
  218. package/skills/checkpoint/SKILL.md +53 -0
  219. package/skills/configure-scc/SKILL.md +163 -0
  220. package/skills/continuous-agent-loop/SKILL.md +264 -0
  221. package/skills/mcp-server-patterns/SKILL.md +146 -0
  222. package/skills/model-route/SKILL.md +84 -0
  223. package/skills/prompt-optimizer/SKILL.md +369 -0
  224. package/skills/refactor-clean/SKILL.md +136 -0
  225. package/skills/resume-session/SKILL.md +114 -0
  226. package/skills/save-session/SKILL.md +186 -0
  227. package/skills/search-first/SKILL.md +144 -0
  228. package/skills/security-scan/SKILL.md +146 -0
  229. package/skills/sessions/SKILL.md +127 -0
  230. package/skills/sf-agentforce-development/SKILL.md +450 -0
  231. package/skills/sf-apex-async-patterns/SKILL.md +326 -0
  232. package/skills/sf-apex-best-practices/SKILL.md +425 -0
  233. package/skills/sf-apex-constraints/SKILL.md +81 -0
  234. package/skills/sf-apex-cursor/SKILL.md +338 -0
  235. package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
  236. package/skills/sf-apex-testing/SKILL.md +409 -0
  237. package/skills/sf-api-design/SKILL.md +238 -0
  238. package/skills/sf-approval-processes/SKILL.md +315 -0
  239. package/skills/sf-aura-development/SKILL.md +263 -0
  240. package/skills/sf-build-fix/SKILL.md +121 -0
  241. package/skills/sf-data-modeling/SKILL.md +278 -0
  242. package/skills/sf-debugging/SKILL.md +363 -0
  243. package/skills/sf-deployment/SKILL.md +295 -0
  244. package/skills/sf-deployment-constraints/SKILL.md +155 -0
  245. package/skills/sf-devops-ci-cd/SKILL.md +325 -0
  246. package/skills/sf-docs-lookup/SKILL.md +103 -0
  247. package/skills/sf-e2e-testing/SKILL.md +324 -0
  248. package/skills/sf-experience-cloud/SKILL.md +249 -0
  249. package/skills/sf-flow-development/SKILL.md +377 -0
  250. package/skills/sf-governor-limits/SKILL.md +323 -0
  251. package/skills/sf-harness-audit/SKILL.md +142 -0
  252. package/skills/sf-help/SKILL.md +159 -0
  253. package/skills/sf-integration/SKILL.md +483 -0
  254. package/skills/sf-lwc-constraints/SKILL.md +130 -0
  255. package/skills/sf-lwc-development/SKILL.md +303 -0
  256. package/skills/sf-lwc-testing/SKILL.md +388 -0
  257. package/skills/sf-metadata-management/SKILL.md +288 -0
  258. package/skills/sf-platform-events-cdc/SKILL.md +375 -0
  259. package/skills/sf-quickstart/SKILL.md +173 -0
  260. package/skills/sf-security/SKILL.md +334 -0
  261. package/skills/sf-security-constraints/SKILL.md +127 -0
  262. package/skills/sf-soql-constraints/SKILL.md +131 -0
  263. package/skills/sf-soql-optimization/SKILL.md +354 -0
  264. package/skills/sf-tdd-workflow/SKILL.md +336 -0
  265. package/skills/sf-testing-constraints/SKILL.md +200 -0
  266. package/skills/sf-trigger-constraints/SKILL.md +90 -0
  267. package/skills/sf-trigger-frameworks/SKILL.md +347 -0
  268. package/skills/sf-visualforce-development/SKILL.md +260 -0
  269. package/skills/strategic-compact/SKILL.md +208 -0
  270. package/skills/update-docs/SKILL.md +165 -0
  271. package/skills/update-platform-docs/SKILL.md +90 -0
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * schema-validator.js — Shared AJV schema validation for SCC.
5
+ *
6
+ * - Graceful AJV import with fallback when not installed
7
+ * - Cached schema, AJV instance, and compiled validators
8
+ * - Consistent error formatting: `${instancePath || '/'} ${message}`
9
+ * - Entity validation against $defs-based schemas
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // Graceful AJV import — fallback when not installed (bare environments)
16
+ let Ajv = null;
17
+ try {
18
+ const ajvModule = require('ajv');
19
+ Ajv = ajvModule.default || ajvModule;
20
+ } catch (_error) {
21
+ Ajv = null;
22
+ }
23
+
24
+ // Caches
25
+ let cachedAjv = null;
26
+ const cachedSchemas = new Map();
27
+ const cachedValidators = new Map();
28
+
29
+ function getAjv() {
30
+ if (cachedAjv) return cachedAjv;
31
+ if (!Ajv) return null;
32
+ cachedAjv = new Ajv({ allErrors: true, strict: false });
33
+ return cachedAjv;
34
+ }
35
+
36
+ function readSchema(schemaPath) {
37
+ const resolved = path.resolve(schemaPath);
38
+ if (cachedSchemas.has(resolved)) return cachedSchemas.get(resolved);
39
+ const schema = JSON.parse(fs.readFileSync(resolved, 'utf8'));
40
+ cachedSchemas.set(resolved, schema);
41
+ return schema;
42
+ }
43
+
44
+ function getValidator(schemaPath) {
45
+ const resolved = path.resolve(schemaPath);
46
+ if (cachedValidators.has(resolved)) return cachedValidators.get(resolved);
47
+
48
+ const ajv = getAjv();
49
+ if (!ajv) return createFallbackValidator();
50
+
51
+ const schema = readSchema(resolved);
52
+ const validator = ajv.compile(schema);
53
+ cachedValidators.set(resolved, validator);
54
+ return validator;
55
+ }
56
+
57
+ function getEntityValidator(schemaPath, entityName) {
58
+ const cacheKey = `${path.resolve(schemaPath)}#${entityName}`;
59
+ if (cachedValidators.has(cacheKey)) return cachedValidators.get(cacheKey);
60
+
61
+ const ajv = getAjv();
62
+ if (!ajv) return createFallbackValidator();
63
+
64
+ const schema = readSchema(schemaPath);
65
+ if (!schema.$defs || !schema.$defs[entityName]) {
66
+ throw new Error(`Unknown schema entity: ${entityName} in ${schemaPath}`);
67
+ }
68
+
69
+ const entitySchema = {
70
+ $schema: schema.$schema,
71
+ ...schema.$defs[entityName],
72
+ $defs: schema.$defs,
73
+ };
74
+ const validator = ajv.compile(entitySchema);
75
+ cachedValidators.set(cacheKey, validator);
76
+ return validator;
77
+ }
78
+
79
+ /**
80
+ * Fallback validator when AJV is not installed.
81
+ * Returns a validator function with same interface: validate(data) → boolean, validate.errors → array.
82
+ */
83
+ function createFallbackValidator() {
84
+ const validate = (data) => {
85
+ const errors = [];
86
+ validate.errors = errors;
87
+
88
+ if (data === null || data === undefined) {
89
+ errors.push({ instancePath: '/', message: 'must not be null or undefined' });
90
+ return false;
91
+ }
92
+ if (typeof data !== 'object' || Array.isArray(data)) {
93
+ errors.push({ instancePath: '/', message: 'must be object' });
94
+ return false;
95
+ }
96
+ return true;
97
+ };
98
+ validate.errors = [];
99
+ return validate;
100
+ }
101
+
102
+ /**
103
+ * Format AJV errors into a readable string.
104
+ */
105
+ function formatErrors(errors) {
106
+ if (!errors || errors.length === 0) return '';
107
+ return errors
108
+ .map(e => `${e.instancePath || '/'} ${e.message}`)
109
+ .join('; ');
110
+ }
111
+
112
+ // ── Public API ───────────────────────────────────────────────────────────────
113
+
114
+ /**
115
+ * Validate data against a JSON schema file.
116
+ * @param {string} schemaPath - path to JSON schema file
117
+ * @param {*} data - data to validate
118
+ * @returns {{ valid: boolean, errors: Array }}
119
+ */
120
+ function validateAgainstSchema(schemaPath, data) {
121
+ const validator = getValidator(schemaPath);
122
+ const valid = validator(data);
123
+ return { valid, errors: validator.errors || [] };
124
+ }
125
+
126
+ /**
127
+ * Validate data against a schema, throw on invalid.
128
+ * @param {string} schemaPath - path to JSON schema file
129
+ * @param {*} data - data to validate
130
+ * @param {string} [label] - label for error message
131
+ */
132
+ function assertAgainstSchema(schemaPath, data, label) {
133
+ const result = validateAgainstSchema(schemaPath, data);
134
+ if (!result.valid) {
135
+ throw new Error(`Invalid${label ? ` ${label}` : ''}: ${formatErrors(result.errors)}`);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Validate data against a named entity definition ($defs) in a schema.
141
+ * @param {string} schemaPath - path to JSON schema file
142
+ * @param {string} entityName - name of the entity in $defs
143
+ * @param {*} data - data to validate
144
+ * @returns {{ valid: boolean, errors: Array }}
145
+ */
146
+ function validateEntity(schemaPath, entityName, data) {
147
+ const validator = getEntityValidator(schemaPath, entityName);
148
+ const valid = validator(data);
149
+ return { valid, errors: validator.errors || [] };
150
+ }
151
+
152
+ /**
153
+ * Validate entity against a schema, throw on invalid.
154
+ * @param {string} schemaPath - path to JSON schema file
155
+ * @param {string} entityName - name of the entity in $defs
156
+ * @param {*} data - data to validate
157
+ * @param {string} [label] - label for error message
158
+ */
159
+ function assertValidEntity(schemaPath, entityName, data, label) {
160
+ const result = validateEntity(schemaPath, entityName, data);
161
+ if (!result.valid) {
162
+ throw new Error(`Invalid ${entityName}${label ? ` (${label})` : ''}: ${formatErrors(result.errors)}`);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Check if AJV is available.
168
+ */
169
+ function hasAjv() {
170
+ return Ajv !== null;
171
+ }
172
+
173
+ /**
174
+ * Clear all caches (useful for testing).
175
+ */
176
+ function clearCaches() {
177
+ cachedAjv = null;
178
+ cachedSchemas.clear();
179
+ cachedValidators.clear();
180
+ }
181
+
182
+ module.exports = {
183
+ validateAgainstSchema,
184
+ assertAgainstSchema,
185
+ validateEntity,
186
+ assertValidEntity,
187
+ formatErrors,
188
+ hasAjv,
189
+ clearCaches,
190
+ };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * skill-adapter.js — Transforms SCC skills from Claude Code format to Cursor format.
5
+ *
6
+ * Claude Code skills use: name, description, origin, user-invocable, allowed-tools, context, etc.
7
+ * Cursor skills use: name, description, disable-model-invocation, license, compatibility, metadata.
8
+ *
9
+ * This adapter strips Claude-only fields, maps user-invocable to disable-model-invocation,
10
+ * and outputs clean Cursor-compatible SKILL.md files.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { parseFrontmatter, serializeFrontmatter, ensureDir, copyFile } = require('./utils');
16
+
17
+ // Fields that Cursor recognizes in SKILL.md frontmatter
18
+ const CURSOR_ALLOWED_FIELDS = new Set([
19
+ 'name',
20
+ 'description',
21
+ 'disable-model-invocation',
22
+ 'license',
23
+ 'compatibility',
24
+ 'metadata',
25
+ ]);
26
+
27
+ /**
28
+ * Transform a single SKILL.md content string from Claude Code to Cursor format.
29
+ * @param {string} content - raw SKILL.md content
30
+ * @returns {string} - transformed SKILL.md content for Cursor
31
+ */
32
+ function transformSkill(content) {
33
+ const { frontmatter, body } = parseFrontmatter(content);
34
+
35
+ const cursorFm = {};
36
+
37
+ // Copy allowed fields
38
+ for (const key of Object.keys(frontmatter)) {
39
+ if (CURSOR_ALLOWED_FIELDS.has(key)) {
40
+ cursorFm[key] = frontmatter[key];
41
+ }
42
+ }
43
+
44
+ // EXample for future Map user-invocable → disable-model-invocation
45
+ /*if (frontmatter['user-invocable'] !== undefined) {
46
+ const isUserInvocable = String(frontmatter['user-invocable']).toLowerCase() === 'true';
47
+ if (isUserInvocable) {
48
+ cursorFm['disable-model-invocation'] = false;
49
+ }
50
+ // When user-invocable is false (auto-only), omit disable-model-invocation
51
+ // so Cursor defaults to model-can-invoke behavior
52
+ }*/
53
+
54
+ return serializeFrontmatter(cursorFm, body);
55
+ }
56
+
57
+ /**
58
+ * Transform and copy an entire skill directory.
59
+ * Transforms SKILL.md frontmatter; copies all other files as-is.
60
+ * @param {string} srcDir - source skill directory (e.g., skills/sf-help/)
61
+ * @param {string} destDir - destination directory (e.g., .cursor/skills/sf-help/)
62
+ */
63
+ function transformSkillDir(srcDir, destDir) {
64
+ if (!fs.existsSync(srcDir)) {
65
+ throw new Error(`Source skill directory not found: ${srcDir}`);
66
+ }
67
+
68
+ ensureDir(destDir);
69
+ copyDirRecursive(srcDir, destDir);
70
+ }
71
+
72
+ /**
73
+ * Recursively copy a directory, transforming SKILL.md files.
74
+ * @param {string} src - source directory
75
+ * @param {string} dest - destination directory
76
+ */
77
+ function copyDirRecursive(src, dest) {
78
+ ensureDir(dest);
79
+ const entries = fs.readdirSync(src, { withFileTypes: true });
80
+
81
+ for (const entry of entries) {
82
+ const srcPath = path.join(src, entry.name);
83
+ const destPath = path.join(dest, entry.name);
84
+
85
+ if (entry.isDirectory()) {
86
+ copyDirRecursive(srcPath, destPath);
87
+ } else if (entry.name === 'SKILL.md') {
88
+ // Transform SKILL.md frontmatter
89
+ const content = fs.readFileSync(srcPath, 'utf8');
90
+ const transformed = transformSkill(content);
91
+ ensureDir(path.dirname(destPath));
92
+ fs.writeFileSync(destPath, transformed, 'utf8');
93
+ } else {
94
+ // Copy other files as-is
95
+ copyFile(srcPath, destPath);
96
+ }
97
+ }
98
+ }
99
+
100
+ module.exports = { transformSkill, transformSkillDir, CURSOR_ALLOWED_FIELDS };
@@ -0,0 +1,376 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * state-store.js — Schema-validated JSON state store for SCC.
5
+ *
6
+ * Persists to ~/.scc/state.json with 6 entity types (validated against state-store.schema.json):
7
+ * - sessions: AI session tracking
8
+ * - skillRuns: Skill execution history
9
+ * - skillVersions: Skill version tracking
10
+ * - decisions: Decision records
11
+ * - installState: Installation tracking (replaces legacy installedFiles)
12
+ * - governanceEvents: Governor limit/security events
13
+ *
14
+ * Backward-compatible: saveState/loadState/clearState/removeFiles still work for existing callers.
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const os = require('os');
20
+ const { assertValidEntity } = require('./schema-validator');
21
+
22
+ const STATE_DIR = path.join(os.homedir(), '.scc');
23
+ const STATE_PATH = path.join(STATE_DIR, 'state.json');
24
+ const SCHEMA_PATH = path.join(__dirname, '..', '..', 'schemas', 'state-store.schema.json');
25
+
26
+ // Entity collection names (must match schema properties)
27
+ const ENTITY_COLLECTIONS = ['sessions', 'skillRuns', 'skillVersions', 'decisions', 'installState', 'governanceEvents'];
28
+
29
+ function ensureStateDir() {
30
+ fs.mkdirSync(STATE_DIR, { recursive: true });
31
+ }
32
+
33
+ /**
34
+ * Create an empty state object with all entity collections.
35
+ */
36
+ function emptyState() {
37
+ const state = {};
38
+ for (const col of ENTITY_COLLECTIONS) {
39
+ state[col] = [];
40
+ }
41
+ return state;
42
+ }
43
+
44
+ /**
45
+ * Load raw JSON state from disk.
46
+ */
47
+ function loadJsonState() {
48
+ try {
49
+ const raw = fs.readFileSync(STATE_PATH, 'utf8');
50
+ const parsed = JSON.parse(raw);
51
+
52
+ // Detect and migrate legacy format
53
+ if (parsed.installedFiles || parsed.lastProfile) {
54
+ return migrateLegacyState(parsed);
55
+ }
56
+
57
+ // Ensure all collections exist
58
+ const state = emptyState();
59
+ for (const col of ENTITY_COLLECTIONS) {
60
+ if (Array.isArray(parsed[col])) {
61
+ state[col] = parsed[col];
62
+ }
63
+ }
64
+ return state;
65
+ } catch {
66
+ return emptyState();
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Migrate legacy state format (installedFiles/lastProfile) to entity model.
72
+ */
73
+ function migrateLegacyState(legacy) {
74
+ const state = emptyState();
75
+
76
+ // Legacy state migration: v1.0.0 used fine-grained module IDs (rules-apex, agents-lwc, etc.)
77
+ // v2.0.0 uses 7 bundle IDs (core, apex, lwc, platform, devops, security, extended).
78
+ const LEGACY_PREFIXES = ['rules-', 'agents-', 'commands-', 'skills-', 'hooks-', 'platform-'];
79
+ const hasLegacyModules = (legacy.installedFiles || []).some(f =>
80
+ f.module && LEGACY_PREFIXES.some(prefix => f.module.startsWith(prefix))
81
+ );
82
+
83
+ // If legacy module IDs detected, wipe and return empty
84
+ if (hasLegacyModules) {
85
+ saveJsonState(state);
86
+ return state;
87
+ }
88
+
89
+ // Migrate installedFiles to installState entities (group by target)
90
+ if (Array.isArray(legacy.installedFiles) && legacy.installedFiles.length > 0) {
91
+ const byTarget = new Map();
92
+ for (const f of legacy.installedFiles) {
93
+ const target = f.target || legacy.lastTarget || 'claude';
94
+ if (!byTarget.has(target)) byTarget.set(target, []);
95
+ byTarget.get(target).push(f);
96
+ }
97
+ for (const [target, files] of byTarget) {
98
+ state.installState.push({
99
+ targetId: target,
100
+ targetRoot: process.cwd(),
101
+ profile: legacy.lastProfile || null,
102
+ modules: [],
103
+ operations: files.map(f => ({
104
+ kind: 'copy',
105
+ moduleId: f.module || '',
106
+ sourceRelativePath: f.srcPath || '',
107
+ destinationPath: f.destPath || '',
108
+ strategy: 'overwrite',
109
+ ownership: 'scc',
110
+ scaffoldOnly: false,
111
+ hash: f.hash || null,
112
+ })),
113
+ installedAt: legacy.lastInstalledAt || new Date().toISOString(),
114
+ sourceVersion: null,
115
+ });
116
+ }
117
+ }
118
+
119
+ // Migrate legacy sessions
120
+ if (Array.isArray(legacy.sessions)) {
121
+ for (const s of legacy.sessions) {
122
+ state.sessions.push({
123
+ id: `legacy-${s.installedAt || Date.now()}`,
124
+ adapterId: 'scc-install',
125
+ harness: s.target || 'claude',
126
+ state: 'completed',
127
+ repoRoot: null,
128
+ startedAt: s.installedAt || null,
129
+ endedAt: s.installedAt || null,
130
+ snapshot: { profile: s.profile, fileCount: s.fileCount },
131
+ });
132
+ }
133
+ }
134
+
135
+ // Write migrated state
136
+ saveJsonState(state);
137
+ return state;
138
+ }
139
+
140
+ function saveJsonState(state) {
141
+ ensureStateDir();
142
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
143
+ }
144
+
145
+ // ── Entity Operations (schema-validated) ────────────────────────────────────
146
+
147
+ /**
148
+ * Upsert an entity into a collection. Validates against schema before saving.
149
+ * If entity with same `id` exists, it's replaced.
150
+ */
151
+ function upsertEntity(collectionName, entityName, entity) {
152
+ assertValidEntity(SCHEMA_PATH, entityName, entity);
153
+
154
+ const state = loadJsonState();
155
+ if (!Array.isArray(state[collectionName])) {
156
+ state[collectionName] = [];
157
+ }
158
+
159
+ // Replace by id if exists, otherwise append
160
+ const idField = entity.id ? 'id' : (entity.skillId ? 'skillId' : null);
161
+ if (idField && entity[idField]) {
162
+ const idx = state[collectionName].findIndex(e => e[idField] === entity[idField]);
163
+ if (idx >= 0) {
164
+ state[collectionName][idx] = entity;
165
+ } else {
166
+ state[collectionName].push(entity);
167
+ }
168
+ } else {
169
+ state[collectionName].push(entity);
170
+ }
171
+
172
+ saveJsonState(state);
173
+ }
174
+
175
+ function upsertSession(entity) {
176
+ upsertEntity('sessions', 'session', entity);
177
+ }
178
+
179
+ function upsertSkillRun(entity) {
180
+ upsertEntity('skillRuns', 'skillRun', entity);
181
+ }
182
+
183
+ function upsertSkillVersion(entity) {
184
+ upsertEntity('skillVersions', 'skillVersion', entity);
185
+ }
186
+
187
+ function upsertDecision(entity) {
188
+ upsertEntity('decisions', 'decision', entity);
189
+ }
190
+
191
+ function upsertGovernanceEvent(entity) {
192
+ upsertEntity('governanceEvents', 'governanceEvent', entity);
193
+ }
194
+
195
+ // ── Query Helpers ───────────────────────────────────────────────────────────
196
+
197
+ function listSessions() {
198
+ return loadJsonState().sessions;
199
+ }
200
+
201
+ function listSkillRuns(sessionId) {
202
+ const runs = loadJsonState().skillRuns;
203
+ return sessionId ? runs.filter(r => r.sessionId === sessionId) : runs;
204
+ }
205
+
206
+ function listDecisions(sessionId) {
207
+ const decisions = loadJsonState().decisions;
208
+ return sessionId ? decisions.filter(d => d.sessionId === sessionId) : decisions;
209
+ }
210
+
211
+ function listGovernanceEvents(sessionId) {
212
+ const events = loadJsonState().governanceEvents;
213
+ return sessionId ? events.filter(e => e.sessionId === sessionId) : events;
214
+ }
215
+
216
+ /**
217
+ * Collect all operations across all installState entities into flat installedFiles format.
218
+ */
219
+ function allOperations(state) {
220
+ const files = [];
221
+ for (const install of state.installState) {
222
+ for (const op of install.operations) {
223
+ files.push({
224
+ destPath: op.destinationPath,
225
+ srcPath: op.sourceRelativePath,
226
+ module: op.moduleId,
227
+ hash: op.hash || null,
228
+ installedAt: install.installedAt,
229
+ profile: install.profile,
230
+ target: install.targetId,
231
+ });
232
+ }
233
+ }
234
+ return files;
235
+ }
236
+
237
+ // ── Backward-Compatible API ─────────────────────────────────────────────────
238
+
239
+ /**
240
+ * Save installation state (backward-compatible).
241
+ * Translates old format to installState entity.
242
+ */
243
+ function saveState(state) {
244
+ ensureStateDir();
245
+ const now = state.installedAt || new Date().toISOString();
246
+ const current = loadJsonState();
247
+
248
+ // Create installState entity from legacy format
249
+ const installEntity = {
250
+ targetId: state.target || 'claude',
251
+ targetRoot: process.cwd(),
252
+ profile: state.profile || null,
253
+ modules: [],
254
+ operations: (state.installedFiles || []).map(f => ({
255
+ kind: 'copy',
256
+ moduleId: f.module || '',
257
+ sourceRelativePath: f.srcPath || '',
258
+ destinationPath: f.destPath || '',
259
+ strategy: 'overwrite',
260
+ ownership: 'scc',
261
+ scaffoldOnly: false,
262
+ hash: f.hash || null,
263
+ })),
264
+ installedAt: now,
265
+ sourceVersion: null,
266
+ };
267
+
268
+ // Merge operations: replace entries with same destinationPath
269
+ const existingInstall = current.installState[current.installState.length - 1];
270
+ if (existingInstall) {
271
+ const existingOps = new Map(existingInstall.operations.map(op => [op.destinationPath, op]));
272
+ for (const op of installEntity.operations) {
273
+ existingOps.set(op.destinationPath, op);
274
+ }
275
+ installEntity.operations = Array.from(existingOps.values());
276
+ }
277
+
278
+ // Replace or append installState
279
+ if (current.installState.length > 0) {
280
+ current.installState[current.installState.length - 1] = installEntity;
281
+ } else {
282
+ current.installState.push(installEntity);
283
+ }
284
+
285
+ // Record session
286
+ current.sessions.push({
287
+ id: `install-${now}`,
288
+ adapterId: 'scc-install',
289
+ harness: state.target || 'claude',
290
+ state: 'completed',
291
+ repoRoot: null,
292
+ startedAt: now,
293
+ endedAt: now,
294
+ snapshot: { profile: state.profile, fileCount: (state.installedFiles || []).length },
295
+ });
296
+
297
+ saveJsonState(current);
298
+ }
299
+
300
+ /**
301
+ * Load current installation state (backward-compatible).
302
+ * Returns format expected by existing callers.
303
+ */
304
+ function loadState() {
305
+ const state = loadJsonState();
306
+ const latest = state.installState[state.installState.length - 1];
307
+
308
+ // Legacy module ID detection in entity format
309
+ const LEGACY_PREFIXES = ['rules-', 'agents-', 'commands-', 'skills-', 'hooks-', 'platform-'];
310
+ if (latest) {
311
+ const hasLegacy = latest.operations.some(op =>
312
+ op.moduleId && LEGACY_PREFIXES.some(prefix => op.moduleId.startsWith(prefix))
313
+ );
314
+ if (hasLegacy) {
315
+ clearState();
316
+ return { profile: null, target: null, installedAt: null, installedFiles: [], sessions: [] };
317
+ }
318
+ }
319
+
320
+ return {
321
+ profile: latest ? latest.profile : null,
322
+ target: latest ? latest.targetId : null,
323
+ installedAt: latest ? latest.installedAt : null,
324
+ installedFiles: allOperations(state),
325
+ sessions: state.sessions.map(s => ({
326
+ profile: s.snapshot?.profile || null,
327
+ target: s.harness,
328
+ fileCount: s.snapshot?.fileCount || 0,
329
+ installedAt: s.startedAt,
330
+ })),
331
+ };
332
+ }
333
+
334
+ /**
335
+ * Clear all SCC state.
336
+ */
337
+ function clearState() {
338
+ try { fs.unlinkSync(STATE_PATH); } catch { /* ignore */ }
339
+ // Also clean up legacy DB file if it exists
340
+ const legacyDbPath = path.join(STATE_DIR, 'state.db');
341
+ try { fs.unlinkSync(legacyDbPath); } catch { /* ignore */ }
342
+ }
343
+
344
+ /**
345
+ * Remove specific files from the state store.
346
+ * @param {string[]} destPaths
347
+ */
348
+ function removeFiles(destPaths) {
349
+ const state = loadJsonState();
350
+ const removeSet = new Set(destPaths);
351
+
352
+ for (const install of state.installState) {
353
+ install.operations = install.operations.filter(op => !removeSet.has(op.destinationPath));
354
+ }
355
+
356
+ saveJsonState(state);
357
+ }
358
+
359
+ module.exports = {
360
+ // Backward-compatible API
361
+ saveState,
362
+ loadState,
363
+ clearState,
364
+ removeFiles,
365
+ // Entity operations (schema-validated)
366
+ upsertSession,
367
+ upsertSkillRun,
368
+ upsertSkillVersion,
369
+ upsertDecision,
370
+ upsertGovernanceEvent,
371
+ // Query helpers
372
+ listSessions,
373
+ listSkillRuns,
374
+ listDecisions,
375
+ listGovernanceEvents,
376
+ };