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,98 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Continuous Learning - Session Evaluator
4
+ *
5
+ * Runs on Stop hook to extract reusable patterns from Claude Code sessions.
6
+ * Reads transcript_path from stdin JSON (Claude Code hook input).
7
+ *
8
+ * Why Stop hook instead of UserPromptSubmit:
9
+ * - Stop runs once at session end (lightweight)
10
+ * - UserPromptSubmit runs every message (heavy, adds latency)
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const path = require('path');
16
+ const fs = require('fs');
17
+ const { ensureDir, readFile, countInFile, log } = require('../lib/utils');
18
+
19
+ const MAX_STDIN = 1024 * 1024;
20
+ let stdinData = '';
21
+ process.stdin.setEncoding('utf8');
22
+
23
+ process.stdin.on('data', chunk => {
24
+ if (stdinData.length < MAX_STDIN) {
25
+ const remaining = MAX_STDIN - stdinData.length;
26
+ stdinData += chunk.substring(0, remaining);
27
+ }
28
+ });
29
+
30
+ process.stdin.on('end', () => {
31
+ main().catch(err => {
32
+ console.error('[ContinuousLearning] Error:', err.message);
33
+ process.exit(0);
34
+ });
35
+ });
36
+
37
+ function getLearnedSkillsDir() {
38
+ const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
39
+ return path.join(home, '.claude', 'skills', 'learned');
40
+ }
41
+
42
+ async function main() {
43
+ // Parse stdin JSON to get transcript_path
44
+ let transcriptPath;
45
+ try {
46
+ const input = JSON.parse(stdinData);
47
+ transcriptPath = input.transcript_path;
48
+ } catch {
49
+ // Fallback: try env var for backwards compatibility
50
+ transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
51
+ }
52
+
53
+ // Get script directory to find config
54
+ const scriptDir = __dirname;
55
+ const configFile = path.join(scriptDir, '..', '..', 'skills', 'continuous-learning', 'config.json');
56
+
57
+ // Default configuration
58
+ let minSessionLength = 10;
59
+ let learnedSkillsPath = getLearnedSkillsDir();
60
+
61
+ // Load config if exists
62
+ const configContent = readFile(configFile);
63
+ if (configContent) {
64
+ try {
65
+ const config = JSON.parse(configContent);
66
+ minSessionLength = config.min_session_length ?? 10;
67
+
68
+ if (config.learned_skills_path) {
69
+ // Handle ~ in path
70
+ learnedSkillsPath = config.learned_skills_path.replace(/^~/, require('os').homedir());
71
+ }
72
+ } catch (err) {
73
+ log(`[ContinuousLearning] Failed to parse config: ${err.message}, using defaults`);
74
+ }
75
+ }
76
+
77
+ // Ensure learned skills directory exists
78
+ ensureDir(learnedSkillsPath);
79
+
80
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) {
81
+ process.exit(0);
82
+ }
83
+
84
+ // Count user messages in session (allow optional whitespace around colon)
85
+ const messageCount = countInFile(transcriptPath, /"type"\s*:\s*"user"/g);
86
+
87
+ // Skip short sessions
88
+ if (messageCount < minSessionLength) {
89
+ log(`[ContinuousLearning] Session too short (${messageCount} messages), skipping`);
90
+ process.exit(0);
91
+ }
92
+
93
+ // Signal to Claude that session should be evaluated for extractable patterns
94
+ log(`[ContinuousLearning] Session has ${messageCount} messages - evaluate for extractable patterns`);
95
+ log(`[ContinuousLearning] Save learned skills to: ${learnedSkillsPath}`);
96
+
97
+ process.exit(0);
98
+ }
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Governor Limit Check Hook
4
+ *
5
+ * Salesforce-specific PostToolUse hook that checks edited Apex files
6
+ * for common governor limit violations using shared apex-analysis module.
7
+ *
8
+ * Uses apex-analysis.js for:
9
+ * - Comment/string stripping (eliminates false positives)
10
+ * - Loop depth tracking (activeLoopDepths stack + globalBraceDepth)
11
+ * - Test class detection (skips test classes entirely)
12
+ *
13
+ * Detections:
14
+ * - SOQL queries inside loops (CRITICAL)
15
+ * - SOSL queries inside loops (CRITICAL)
16
+ * - DML operations inside loops (CRITICAL)
17
+ * - HTTP callouts inside loops (CRITICAL)
18
+ * - Async operations inside loops (HIGH)
19
+ * - Non-bulkified trigger patterns (HIGH)
20
+ * - Schema describe in loops (MEDIUM)
21
+ * - Deeply nested loops — 3+ levels (MEDIUM)
22
+ * - Unbounded SOQL on large objects (LOW)
23
+ */
24
+
25
+ 'use strict';
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ const { preprocessApex, isTestClass, trackLoopDepth } = require('../lib/apex-analysis');
31
+
32
+ const MAX_STDIN = 1024 * 1024;
33
+
34
+ function log(msg) {
35
+ process.stderr.write(`${msg}\n`);
36
+ }
37
+
38
+ /**
39
+ * Analyze Apex code for governor limit violations.
40
+ */
41
+ function checkGovernorLimits(filePath) {
42
+ if (!filePath || !fs.existsSync(filePath)) return;
43
+
44
+ const ext = path.extname(filePath).toLowerCase();
45
+ if (ext !== '.cls' && ext !== '.trigger') return;
46
+
47
+ let content;
48
+ try {
49
+ content = fs.readFileSync(filePath, 'utf8');
50
+ } catch {
51
+ return;
52
+ }
53
+
54
+ // Skip test classes entirely — they don't run in production
55
+ if (isTestClass(content)) return;
56
+
57
+ // Preprocess: strip comments and string literals
58
+ const processed = preprocessApex(content);
59
+ const processedLines = processed.split('\n');
60
+ const rawLines = content.split('\n');
61
+ const depths = trackLoopDepth(processedLines);
62
+
63
+ const violations = [];
64
+ let deepNestWarned = false;
65
+
66
+ for (let i = 0; i < processedLines.length; i++) {
67
+ const line = processedLines[i];
68
+ const depth = depths[i];
69
+
70
+ // SOQL in loop
71
+ if (depth > 0 && /\[\s*SELECT\s/i.test(line)) {
72
+ violations.push({
73
+ line: i + 1,
74
+ severity: 'CRITICAL',
75
+ message: 'SOQL query inside loop — will hit 100 SOQL query limit',
76
+ fix: 'Move query before the loop and use a Map/Set for lookups',
77
+ });
78
+ }
79
+
80
+ // SOSL in loop
81
+ if (depth > 0 && /\[FIND\s/i.test(line)) {
82
+ violations.push({
83
+ line: i + 1,
84
+ severity: 'CRITICAL',
85
+ message: 'SOSL query inside loop — will hit 20 SOSL query limit',
86
+ fix: 'Move SOSL search before the loop',
87
+ });
88
+ }
89
+
90
+ // DML in loop
91
+ if (depth > 0) {
92
+ const dmlPattern = /\b(insert|update|delete|upsert|undelete|merge)\s+(?!into\b)/i;
93
+ const dbPattern = /Database\.(insert|update|delete|upsert|undelete|merge)/i;
94
+ if (dmlPattern.test(line) || dbPattern.test(line)) {
95
+ violations.push({
96
+ line: i + 1,
97
+ severity: 'CRITICAL',
98
+ message: 'DML operation inside loop — will hit 150 DML statement limit',
99
+ fix: 'Collect records in a List and perform DML after the loop',
100
+ });
101
+ }
102
+ }
103
+
104
+ // Callout in loop
105
+ if (depth > 0 && /Http[a-zA-Z]*\.(send|getContent)|HttpRequest/i.test(line)) {
106
+ violations.push({
107
+ line: i + 1,
108
+ severity: 'CRITICAL',
109
+ message: 'HTTP callout inside loop — will hit 100 callout limit',
110
+ fix: 'Batch callouts or use Queueable/Future for async processing',
111
+ });
112
+ }
113
+
114
+ // Async operations in loop
115
+ if (depth > 0 && /System\.enqueueJob\s*\(/i.test(line)) {
116
+ violations.push({
117
+ line: i + 1,
118
+ severity: 'HIGH',
119
+ message: 'System.enqueueJob() inside loop — will hit 50 Queueable job limit',
120
+ fix: 'Collect work items and enqueue a single Queueable after the loop',
121
+ });
122
+ }
123
+ if (depth > 0 && /EventBus\.publish\s*\(/i.test(line)) {
124
+ violations.push({
125
+ line: i + 1,
126
+ severity: 'HIGH',
127
+ message: 'EventBus.publish() inside loop — publish events in bulk after the loop',
128
+ fix: 'Collect events in a List and call EventBus.publish() once',
129
+ });
130
+ }
131
+ if (depth > 0 && /Messaging\.sendEmail\s*\(/i.test(line)) {
132
+ violations.push({
133
+ line: i + 1,
134
+ severity: 'HIGH',
135
+ message: 'Messaging.sendEmail() inside loop — will hit 10 email invocation limit',
136
+ fix: 'Collect emails in a List and call sendEmail() once after the loop',
137
+ });
138
+ }
139
+
140
+ // Non-bulkified trigger (single record processing)
141
+ if (ext === '.trigger' && /Trigger\.(new|old)\[0\]/.test(line)) {
142
+ violations.push({
143
+ line: i + 1,
144
+ severity: 'HIGH',
145
+ message: 'Non-bulkified trigger — accessing Trigger.new[0] directly',
146
+ fix: 'Iterate over Trigger.new/old to handle bulk operations',
147
+ });
148
+ }
149
+
150
+ // Schema describe in loop
151
+ if (depth > 0 && /Schema\.\w+\.getDescribe\(\)/.test(line)) {
152
+ violations.push({
153
+ line: i + 1,
154
+ severity: 'MEDIUM',
155
+ message: 'Schema describe call inside loop — can hit describe limit',
156
+ fix: 'Cache describe results in a variable outside the loop',
157
+ });
158
+ }
159
+
160
+ // Deeply nested loops (3+ levels)
161
+ if (depth >= 3 && !deepNestWarned) {
162
+ deepNestWarned = true;
163
+ violations.push({
164
+ line: i + 1,
165
+ severity: 'MEDIUM',
166
+ message: `Loop nesting depth ${depth} — high CPU time risk`,
167
+ fix: 'Refactor to reduce nesting or use Maps for lookups',
168
+ });
169
+ }
170
+
171
+ // Unbounded SOQL on large standard objects
172
+ if (/\[\s*SELECT\s/i.test(line) && !/LIMIT\s+\d/i.test(line) && !/COUNT\s*\(/i.test(line)) {
173
+ if (/FROM\s+(Account|Contact|Lead|Opportunity|Task|Event|Case|CampaignMember)\b/i.test(line) &&
174
+ !/WHERE\s/i.test(line)) {
175
+ violations.push({
176
+ line: i + 1,
177
+ severity: 'LOW',
178
+ message: 'SOQL query on large object without LIMIT or WHERE clause',
179
+ fix: 'Add LIMIT clause or WHERE filter to bound result set',
180
+ });
181
+ }
182
+ }
183
+ }
184
+
185
+ if (violations.length > 0) {
186
+ log(`\n[SCC Governor] ${path.basename(filePath)} — ${violations.length} potential violation(s):`);
187
+ for (const v of violations) {
188
+ log(` [${v.severity}] Line ${v.line}: ${v.message}`);
189
+ log(` Fix: ${v.fix}`);
190
+ }
191
+ log('');
192
+ }
193
+ }
194
+
195
+ function run(rawInput) {
196
+ try {
197
+ const input = JSON.parse(rawInput);
198
+ const filePath = String(input.tool_input?.file_path || '');
199
+ checkGovernorLimits(filePath);
200
+ } catch {
201
+ // Ignore errors
202
+ }
203
+ return rawInput;
204
+ }
205
+
206
+ if (require.main === module) {
207
+ let raw = '';
208
+ process.stdin.setEncoding('utf8');
209
+ process.stdin.on('data', chunk => {
210
+ if (raw.length < MAX_STDIN) {
211
+ raw += chunk.substring(0, MAX_STDIN - raw.length);
212
+ }
213
+ });
214
+ process.stdin.on('end', () => {
215
+ const result = run(raw);
216
+ process.stdout.write(result);
217
+ });
218
+ }
219
+
220
+ module.exports = { run };
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ # Continuous Learning — Observation Hook (SCC)
5
+ #
6
+ # Captures tool use events for pattern analysis by the learning-engine agent.
7
+ # Claude Code passes hook data via stdin as JSON.
8
+ #
9
+ # Registered via hooks/hooks.json (PreToolUse + PostToolUse, standard+strict profiles).
10
+
11
+ HOOK_PHASE="${1:-post}"
12
+
13
+ # ─────────────────────────────────────────────
14
+ # Read stdin (before any processing)
15
+ # ─────────────────────────────────────────────
16
+
17
+ INPUT_JSON=$(cat)
18
+
19
+ if [ -z "$INPUT_JSON" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ # ─────────────────────────────────────────────
24
+ # Find a Python interpreter
25
+ # ─────────────────────────────────────────────
26
+
27
+ resolve_python_cmd() {
28
+ if command -v python3 >/dev/null 2>&1; then
29
+ printf '%s\n' python3
30
+ return 0
31
+ fi
32
+ if command -v python >/dev/null 2>&1; then
33
+ printf '%s\n' python
34
+ return 0
35
+ fi
36
+ return 1
37
+ }
38
+
39
+ PYTHON_CMD="$(resolve_python_cmd 2>/dev/null || true)"
40
+ if [ -z "$PYTHON_CMD" ]; then
41
+ exit 0
42
+ fi
43
+
44
+ # ─────────────────────────────────────────────
45
+ # Session guards — skip automated/subagent sessions
46
+ # ─────────────────────────────────────────────
47
+
48
+ # Only run for interactive CLI sessions
49
+ case "${CLAUDE_CODE_ENTRYPOINT:-cli}" in
50
+ cli|sdk-ts) ;;
51
+ *) exit 0 ;;
52
+ esac
53
+
54
+ # Minimal profile suppresses non-essential hooks
55
+ [ "${SCC_HOOK_PROFILE:-standard}" = "minimal" ] && exit 0
56
+
57
+ # Cooperative skip for automated sessions
58
+ [ "${SCC_SKIP_OBSERVE:-0}" = "1" ] && exit 0
59
+
60
+ # Skip subagent sessions
61
+ _AGENT_ID=$(echo "$INPUT_JSON" | "$PYTHON_CMD" -c "import json,sys; print(json.load(sys.stdin).get('agent_id',''))" 2>/dev/null || true)
62
+ [ -n "$_AGENT_ID" ] && exit 0
63
+
64
+ # ─────────────────────────────────────────────
65
+ # Project detection
66
+ # ─────────────────────────────────────────────
67
+
68
+ STDIN_CWD=$(echo "$INPUT_JSON" | "$PYTHON_CMD" -c '
69
+ import json, sys
70
+ try:
71
+ data = json.load(sys.stdin)
72
+ print(data.get("cwd", ""))
73
+ except (KeyError, TypeError, ValueError):
74
+ print("")
75
+ ' 2>/dev/null || echo "")
76
+
77
+ # Determine project ID from git or cwd
78
+ if [ -n "$STDIN_CWD" ] && [ -d "$STDIN_CWD" ]; then
79
+ PROJECT_ROOT="$STDIN_CWD"
80
+ else
81
+ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
82
+ fi
83
+
84
+ PROJECT_ID=$(cd "$PROJECT_ROOT" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null | shasum -a 256 | cut -c1-16 || echo "global")
85
+
86
+ # ─────────────────────────────────────────────
87
+ # Configuration
88
+ # ─────────────────────────────────────────────
89
+
90
+ CONFIG_DIR="${HOME}/.claude/homunculus"
91
+ PROJECT_DIR="${CONFIG_DIR}/projects/${PROJECT_ID}"
92
+ mkdir -p "$PROJECT_DIR"
93
+
94
+ OBSERVATIONS_FILE="${PROJECT_DIR}/observations.jsonl"
95
+ MAX_FILE_SIZE_MB=10
96
+
97
+ # Skip if disabled
98
+ if [ -f "${CONFIG_DIR}/disabled" ]; then
99
+ exit 0
100
+ fi
101
+
102
+ # Auto-purge observation files older than 30 days (runs once per day)
103
+ PURGE_MARKER="${PROJECT_DIR}/.last-purge"
104
+ if [ ! -f "$PURGE_MARKER" ] || [ "$(find "$PURGE_MARKER" -mtime +1 2>/dev/null)" ]; then
105
+ find "${PROJECT_DIR}" -name "observations-*.jsonl" -mtime +30 -delete 2>/dev/null || true
106
+ touch "$PURGE_MARKER" 2>/dev/null || true
107
+ fi
108
+
109
+ # ─────────────────────────────────────────────
110
+ # Parse tool event and write observation
111
+ # ─────────────────────────────────────────────
112
+
113
+ PARSED=$(echo "$INPUT_JSON" | HOOK_PHASE="$HOOK_PHASE" "$PYTHON_CMD" -c '
114
+ import json, sys, os
115
+
116
+ try:
117
+ data = json.load(sys.stdin)
118
+ hook_phase = os.environ.get("HOOK_PHASE", "post")
119
+ event = "tool_start" if hook_phase == "pre" else "tool_complete"
120
+
121
+ tool_name = data.get("tool_name", data.get("tool", "unknown"))
122
+ tool_input = data.get("tool_input", data.get("input", {}))
123
+ tool_output = data.get("tool_response", data.get("tool_output", data.get("output", "")))
124
+ session_id = data.get("session_id", "unknown")
125
+ tool_use_id = data.get("tool_use_id", "")
126
+
127
+ # Truncate large values
128
+ if isinstance(tool_input, dict):
129
+ tool_input_str = json.dumps(tool_input)[:5000]
130
+ else:
131
+ tool_input_str = str(tool_input)[:5000]
132
+
133
+ if isinstance(tool_output, dict):
134
+ tool_output_str = json.dumps(tool_output)[:5000]
135
+ else:
136
+ tool_output_str = str(tool_output)[:5000]
137
+
138
+ print(json.dumps({
139
+ "parsed": True,
140
+ "event": event,
141
+ "tool": tool_name,
142
+ "input": tool_input_str if event == "tool_start" else None,
143
+ "output": tool_output_str if event == "tool_complete" else None,
144
+ "session": session_id,
145
+ "tool_use_id": tool_use_id
146
+ }))
147
+ except Exception as e:
148
+ print(json.dumps({"parsed": False, "error": str(e)}))
149
+ ')
150
+
151
+ PARSED_OK=$(echo "$PARSED" | "$PYTHON_CMD" -c "import json,sys; print(json.load(sys.stdin).get('parsed', False))" 2>/dev/null || echo "False")
152
+
153
+ if [ "$PARSED_OK" != "True" ]; then
154
+ exit 0
155
+ fi
156
+
157
+ # Archive if file too large
158
+ if [ -f "$OBSERVATIONS_FILE" ]; then
159
+ file_size_mb=$(du -m "$OBSERVATIONS_FILE" 2>/dev/null | cut -f1)
160
+ if [ "${file_size_mb:-0}" -ge "$MAX_FILE_SIZE_MB" ]; then
161
+ archive_dir="${PROJECT_DIR}/observations.archive"
162
+ mkdir -p "$archive_dir"
163
+ mv "$OBSERVATIONS_FILE" "$archive_dir/observations-$(date +%Y%m%d-%H%M%S)-$$.jsonl" 2>/dev/null || true
164
+ fi
165
+ fi
166
+
167
+ # Write observation with secret scrubbing
168
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
169
+
170
+ export PROJECT_ID_ENV="$PROJECT_ID"
171
+ export TIMESTAMP="$timestamp"
172
+
173
+ echo "$PARSED" | "$PYTHON_CMD" -c '
174
+ import json, sys, os, re
175
+
176
+ parsed = json.load(sys.stdin)
177
+ observation = {
178
+ "timestamp": os.environ["TIMESTAMP"],
179
+ "event": parsed["event"],
180
+ "tool": parsed["tool"],
181
+ "session": parsed["session"],
182
+ "project_id": os.environ.get("PROJECT_ID_ENV", "global")
183
+ }
184
+
185
+ # Scrub secrets
186
+ _SECRET_RE = re.compile(
187
+ r"(?i)(api[_-]?key|token|secret|password|authorization|credentials?|auth)"
188
+ r"""([\"'"'"'"'"'"'\s:=]+)"""
189
+ r"([A-Za-z]+\s+)?"
190
+ r"([A-Za-z0-9_\-/.+=]{8,})"
191
+ )
192
+
193
+ def scrub(val):
194
+ if val is None:
195
+ return None
196
+ return _SECRET_RE.sub(lambda m: m.group(1) + m.group(2) + (m.group(3) or "") + "[REDACTED]", str(val))
197
+
198
+ if parsed["input"]:
199
+ observation["input"] = scrub(parsed["input"])
200
+ if parsed["output"] is not None:
201
+ observation["output"] = scrub(parsed["output"])
202
+
203
+ print(json.dumps(observation))
204
+ ' >> "$OBSERVATIONS_FILE"
205
+
206
+ exit 0