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.
- package/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- 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
|