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,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop Hook (Session End) - Persist learnings during active sessions
|
|
4
|
+
*
|
|
5
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
6
|
+
*
|
|
7
|
+
* Runs on Stop events (after each response). Extracts a meaningful summary
|
|
8
|
+
* from the session transcript (via stdin JSON transcript_path) and updates a
|
|
9
|
+
* session file for cross-session continuity.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
const SUMMARY_START_MARKER = '<!-- SCC:SUMMARY:START -->';
|
|
16
|
+
const SUMMARY_END_MARKER = '<!-- SCC:SUMMARY:END -->';
|
|
17
|
+
const SESSION_SEPARATOR = '\n---\n';
|
|
18
|
+
|
|
19
|
+
function getSessionsDir() {
|
|
20
|
+
return path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude', 'sessions');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getDateString() {
|
|
24
|
+
const d = new Date();
|
|
25
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getTimeString() {
|
|
29
|
+
const d = new Date();
|
|
30
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getSessionIdShort() {
|
|
34
|
+
return Math.random().toString(36).slice(2, 10);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getProjectName() {
|
|
38
|
+
try {
|
|
39
|
+
return path.basename(process.cwd());
|
|
40
|
+
} catch {
|
|
41
|
+
return 'unknown';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureDir(dirPath) {
|
|
46
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readFile(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeFile(filePath, content) {
|
|
58
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runCommand(cmd) {
|
|
62
|
+
try {
|
|
63
|
+
const { execSync } = require('child_process');
|
|
64
|
+
const output = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
65
|
+
return { success: true, output };
|
|
66
|
+
} catch {
|
|
67
|
+
return { success: false, output: '' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function log(msg) {
|
|
72
|
+
console.error(msg);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract a meaningful summary from the session transcript.
|
|
77
|
+
*/
|
|
78
|
+
function extractSessionSummary(transcriptPath) {
|
|
79
|
+
const content = readFile(transcriptPath);
|
|
80
|
+
if (!content) return null;
|
|
81
|
+
|
|
82
|
+
const lines = content.split('\n').filter(Boolean);
|
|
83
|
+
const userMessages = [];
|
|
84
|
+
const toolsUsed = new Set();
|
|
85
|
+
const filesModified = new Set();
|
|
86
|
+
let parseErrors = 0;
|
|
87
|
+
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
try {
|
|
90
|
+
const entry = JSON.parse(line);
|
|
91
|
+
|
|
92
|
+
if (entry.type === 'user' || entry.role === 'user' || entry.message?.role === 'user') {
|
|
93
|
+
const rawContent = entry.message?.content ?? entry.content;
|
|
94
|
+
const text = typeof rawContent === 'string'
|
|
95
|
+
? rawContent
|
|
96
|
+
: Array.isArray(rawContent)
|
|
97
|
+
? rawContent.map(c => (c && c.text) || '').join(' ')
|
|
98
|
+
: '';
|
|
99
|
+
if (text.trim()) {
|
|
100
|
+
userMessages.push(text.trim().slice(0, 200));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (entry.type === 'tool_use' || entry.tool_name) {
|
|
105
|
+
const toolName = entry.tool_name || entry.name || '';
|
|
106
|
+
if (toolName) toolsUsed.add(toolName);
|
|
107
|
+
const filePath = entry.tool_input?.file_path || entry.input?.file_path || '';
|
|
108
|
+
if (filePath && (toolName === 'Edit' || toolName === 'Write')) {
|
|
109
|
+
filesModified.add(filePath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (entry.type === 'assistant' && Array.isArray(entry.message?.content)) {
|
|
114
|
+
for (const block of entry.message.content) {
|
|
115
|
+
if (block.type === 'tool_use') {
|
|
116
|
+
const toolName = block.name || '';
|
|
117
|
+
if (toolName) toolsUsed.add(toolName);
|
|
118
|
+
const filePath = block.input?.file_path || '';
|
|
119
|
+
if (filePath && (toolName === 'Edit' || toolName === 'Write')) {
|
|
120
|
+
filesModified.add(filePath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
parseErrors++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (parseErrors > 0) {
|
|
131
|
+
log(`[SessionEnd] Skipped ${parseErrors}/${lines.length} unparseable transcript lines`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (userMessages.length === 0) return null;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
userMessages: userMessages.slice(-10),
|
|
138
|
+
toolsUsed: Array.from(toolsUsed).slice(0, 20),
|
|
139
|
+
filesModified: Array.from(filesModified).slice(0, 30),
|
|
140
|
+
totalMessages: userMessages.length
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Read hook input from stdin
|
|
145
|
+
const MAX_STDIN = 1024 * 1024;
|
|
146
|
+
let stdinData = '';
|
|
147
|
+
process.stdin.setEncoding('utf8');
|
|
148
|
+
|
|
149
|
+
process.stdin.on('data', chunk => {
|
|
150
|
+
if (stdinData.length < MAX_STDIN) {
|
|
151
|
+
const remaining = MAX_STDIN - stdinData.length;
|
|
152
|
+
stdinData += chunk.substring(0, remaining);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
process.stdin.on('end', () => {
|
|
157
|
+
runMain();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
function runMain() {
|
|
161
|
+
main().catch(err => {
|
|
162
|
+
console.error('[SessionEnd] Error:', err.message);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getSessionMetadata() {
|
|
168
|
+
const branchResult = runCommand('git rev-parse --abbrev-ref HEAD');
|
|
169
|
+
return {
|
|
170
|
+
project: getProjectName() || 'unknown',
|
|
171
|
+
branch: branchResult.success ? branchResult.output : 'unknown',
|
|
172
|
+
worktree: process.cwd()
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractHeaderField(header, label) {
|
|
177
|
+
const match = header.match(new RegExp(`\\*\\*${escapeRegExp(label)}:\\*\\*\\s*(.+)$`, 'm'));
|
|
178
|
+
return match ? match[1].trim() : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildSessionHeader(today, currentTime, metadata, existingContent = '') {
|
|
182
|
+
const headingMatch = existingContent.match(/^#\s+.+$/m);
|
|
183
|
+
const heading = headingMatch ? headingMatch[0] : `# Session: ${today}`;
|
|
184
|
+
const date = extractHeaderField(existingContent, 'Date') || today;
|
|
185
|
+
const started = extractHeaderField(existingContent, 'Started') || currentTime;
|
|
186
|
+
|
|
187
|
+
return [
|
|
188
|
+
heading,
|
|
189
|
+
`**Date:** ${date}`,
|
|
190
|
+
`**Started:** ${started}`,
|
|
191
|
+
`**Last Updated:** ${currentTime}`,
|
|
192
|
+
`**Project:** ${metadata.project}`,
|
|
193
|
+
`**Branch:** ${metadata.branch}`,
|
|
194
|
+
`**Worktree:** ${metadata.worktree}`,
|
|
195
|
+
''
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function mergeSessionHeader(content, today, currentTime, metadata) {
|
|
200
|
+
const separatorIndex = content.indexOf(SESSION_SEPARATOR);
|
|
201
|
+
if (separatorIndex === -1) return null;
|
|
202
|
+
const existingHeader = content.slice(0, separatorIndex);
|
|
203
|
+
const body = content.slice(separatorIndex + SESSION_SEPARATOR.length);
|
|
204
|
+
const nextHeader = buildSessionHeader(today, currentTime, metadata, existingHeader);
|
|
205
|
+
return `${nextHeader}${SESSION_SEPARATOR}${body}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function main() {
|
|
209
|
+
let transcriptPath;
|
|
210
|
+
try {
|
|
211
|
+
const input = JSON.parse(stdinData);
|
|
212
|
+
transcriptPath = input.transcript_path;
|
|
213
|
+
} catch {
|
|
214
|
+
transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const sessionsDir = getSessionsDir();
|
|
218
|
+
const today = getDateString();
|
|
219
|
+
const shortId = getSessionIdShort();
|
|
220
|
+
const sessionFile = path.join(sessionsDir, `${today}-${shortId}-session.tmp`);
|
|
221
|
+
const sessionMetadata = getSessionMetadata();
|
|
222
|
+
|
|
223
|
+
ensureDir(sessionsDir);
|
|
224
|
+
|
|
225
|
+
const currentTime = getTimeString();
|
|
226
|
+
|
|
227
|
+
let summary = null;
|
|
228
|
+
if (transcriptPath) {
|
|
229
|
+
if (fs.existsSync(transcriptPath)) {
|
|
230
|
+
summary = extractSessionSummary(transcriptPath);
|
|
231
|
+
} else {
|
|
232
|
+
log(`[SessionEnd] Transcript not found: ${transcriptPath}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (fs.existsSync(sessionFile)) {
|
|
237
|
+
const existing = readFile(sessionFile);
|
|
238
|
+
let updatedContent = existing;
|
|
239
|
+
|
|
240
|
+
if (existing) {
|
|
241
|
+
const merged = mergeSessionHeader(existing, today, currentTime, sessionMetadata);
|
|
242
|
+
if (merged) {
|
|
243
|
+
updatedContent = merged;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (summary && updatedContent) {
|
|
248
|
+
const summaryBlock = buildSummaryBlock(summary);
|
|
249
|
+
if (updatedContent.includes(SUMMARY_START_MARKER) && updatedContent.includes(SUMMARY_END_MARKER)) {
|
|
250
|
+
updatedContent = updatedContent.replace(
|
|
251
|
+
new RegExp(`${escapeRegExp(SUMMARY_START_MARKER)}[\\s\\S]*?${escapeRegExp(SUMMARY_END_MARKER)}`),
|
|
252
|
+
summaryBlock
|
|
253
|
+
);
|
|
254
|
+
} else {
|
|
255
|
+
updatedContent = updatedContent.replace(
|
|
256
|
+
/## (?:Session Summary|Current State)[\s\S]*?$/,
|
|
257
|
+
`${summaryBlock}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\`\n`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (updatedContent) {
|
|
263
|
+
writeFile(sessionFile, updatedContent);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
log(`[SessionEnd] Updated session file: ${sessionFile}`);
|
|
267
|
+
} else {
|
|
268
|
+
const summarySection = summary
|
|
269
|
+
? `${buildSummaryBlock(summary)}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``
|
|
270
|
+
: `## Current State\n\n[Session context goes here]\n\n### Completed\n- [ ]\n\n### In Progress\n- [ ]\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``;
|
|
271
|
+
|
|
272
|
+
const template = `${buildSessionHeader(today, currentTime, sessionMetadata)}${SESSION_SEPARATOR}${summarySection}\n`;
|
|
273
|
+
writeFile(sessionFile, template);
|
|
274
|
+
log(`[SessionEnd] Created session file: ${sessionFile}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function buildSummarySection(summary) {
|
|
281
|
+
let section = '## Session Summary\n\n';
|
|
282
|
+
|
|
283
|
+
section += '### Tasks\n';
|
|
284
|
+
for (const msg of summary.userMessages) {
|
|
285
|
+
section += `- ${msg.replace(/\n/g, ' ').replace(/`/g, '\\`')}\n`;
|
|
286
|
+
}
|
|
287
|
+
section += '\n';
|
|
288
|
+
|
|
289
|
+
if (summary.filesModified.length > 0) {
|
|
290
|
+
section += '### Files Modified\n';
|
|
291
|
+
for (const f of summary.filesModified) {
|
|
292
|
+
section += `- ${f}\n`;
|
|
293
|
+
}
|
|
294
|
+
section += '\n';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (summary.toolsUsed.length > 0) {
|
|
298
|
+
section += `### Tools Used\n${summary.toolsUsed.join(', ')}\n\n`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
section += `### Stats\n- Total user messages: ${summary.totalMessages}\n`;
|
|
302
|
+
return section;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function buildSummaryBlock(summary) {
|
|
306
|
+
return `${SUMMARY_START_MARKER}\n${buildSummarySection(summary).trim()}\n${SUMMARY_END_MARKER}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function escapeRegExp(value) {
|
|
310
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
311
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* session-start.js — SessionStart hook for SCC.
|
|
6
|
+
*
|
|
7
|
+
* Detects Salesforce project context and prints a summary:
|
|
8
|
+
* - sfdx-project.json detection
|
|
9
|
+
* - SF CLI version
|
|
10
|
+
* - Connected scratch orgs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawnSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const CWD = process.cwd();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run a command and return stdout string, or null on failure.
|
|
21
|
+
*/
|
|
22
|
+
function run(cmd, args, options = {}) {
|
|
23
|
+
const result = spawnSync(cmd, args, {
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
timeout: 8000,
|
|
26
|
+
cwd: options.cwd || CWD,
|
|
27
|
+
env: process.env,
|
|
28
|
+
});
|
|
29
|
+
if (result.status !== 0 || result.error) return null;
|
|
30
|
+
return (result.stdout || '').trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse sfdx-project.json for project metadata.
|
|
35
|
+
*/
|
|
36
|
+
function readSfdxProject(dir) {
|
|
37
|
+
const filePath = path.join(dir, 'sfdx-project.json');
|
|
38
|
+
if (!fs.existsSync(filePath)) return null;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Walk up directories to find sfdx-project.json.
|
|
48
|
+
*/
|
|
49
|
+
function findSfdxRoot(startDir) {
|
|
50
|
+
let dir = startDir;
|
|
51
|
+
for (let i = 0; i < 5; i++) {
|
|
52
|
+
if (fs.existsSync(path.join(dir, 'sfdx-project.json'))) return dir;
|
|
53
|
+
const parent = path.dirname(dir);
|
|
54
|
+
if (parent === dir) break;
|
|
55
|
+
dir = parent;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get SF CLI version.
|
|
62
|
+
*/
|
|
63
|
+
function getSfCliVersion() {
|
|
64
|
+
// Try 'sf' first (v2), then 'sfdx' (v1)
|
|
65
|
+
const sfVersion = run('sf', ['--version']);
|
|
66
|
+
if (sfVersion) return { cli: 'sf', version: sfVersion };
|
|
67
|
+
|
|
68
|
+
const sfdxVersion = run('sfdx', ['--version']);
|
|
69
|
+
if (sfdxVersion) return { cli: 'sfdx', version: sfdxVersion, deprecated: true };
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List connected Salesforce orgs.
|
|
76
|
+
*/
|
|
77
|
+
function listOrgs() {
|
|
78
|
+
// Try sf org list
|
|
79
|
+
const sfOut = run('sf', ['org', 'list', '--json']);
|
|
80
|
+
if (sfOut) {
|
|
81
|
+
try {
|
|
82
|
+
const data = JSON.parse(sfOut);
|
|
83
|
+
const orgs = [];
|
|
84
|
+
const result = data.result || data;
|
|
85
|
+
|
|
86
|
+
// Non-scratch orgs (devhubs, sandboxes, etc.)
|
|
87
|
+
const nonScratch = Array.isArray(result.nonScratchOrgs) ? result.nonScratchOrgs : [];
|
|
88
|
+
for (const org of nonScratch) {
|
|
89
|
+
orgs.push({
|
|
90
|
+
alias: org.alias || org.username,
|
|
91
|
+
username: org.username,
|
|
92
|
+
type: org.connectedStatus === 'Connected' ? 'connected' : 'disconnected',
|
|
93
|
+
isDevHub: org.isDevHub || false,
|
|
94
|
+
orgType: 'non-scratch',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Scratch orgs
|
|
99
|
+
const scratch = Array.isArray(result.scratchOrgs) ? result.scratchOrgs : [];
|
|
100
|
+
for (const org of scratch) {
|
|
101
|
+
orgs.push({
|
|
102
|
+
alias: org.alias || org.username,
|
|
103
|
+
username: org.username,
|
|
104
|
+
type: org.connectedStatus === 'Connected' ? 'connected' : 'disconnected',
|
|
105
|
+
isDefaultOrg: org.isDefaultOrg || false,
|
|
106
|
+
expirationDate: org.expirationDate || null,
|
|
107
|
+
orgType: 'scratch',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return orgs;
|
|
112
|
+
} catch { /* fall through */ }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Fallback: sfdx force:org:list
|
|
116
|
+
const sfdxOut = run('sfdx', ['force:org:list', '--json']);
|
|
117
|
+
if (sfdxOut) {
|
|
118
|
+
try {
|
|
119
|
+
const data = JSON.parse(sfdxOut);
|
|
120
|
+
const result = data.result || {};
|
|
121
|
+
const orgs = [];
|
|
122
|
+
for (const org of [...(result.nonScratchOrgs || []), ...(result.scratchOrgs || [])]) {
|
|
123
|
+
orgs.push({
|
|
124
|
+
alias: org.alias || org.username,
|
|
125
|
+
username: org.username,
|
|
126
|
+
type: org.connectedStatus === 'Connected' ? 'connected' : 'disconnected',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return orgs;
|
|
130
|
+
} catch { /* ignore */ }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
const sfdxRoot = findSfdxRoot(CWD);
|
|
139
|
+
const isSalesforceProject = sfdxRoot !== null;
|
|
140
|
+
|
|
141
|
+
if (!isSalesforceProject) {
|
|
142
|
+
// Not a Salesforce project — exit silently
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const projectData = readSfdxProject(sfdxRoot);
|
|
147
|
+
const cliInfo = getSfCliVersion();
|
|
148
|
+
|
|
149
|
+
console.log('\n── Salesforce Dev Context ─────────────────────────────');
|
|
150
|
+
|
|
151
|
+
if (sfdxRoot !== CWD) {
|
|
152
|
+
console.log(`Project root : ${sfdxRoot}`);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(`Project : ${path.basename(sfdxRoot)}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (projectData) {
|
|
158
|
+
if (projectData.name) console.log(`Name : ${projectData.name}`);
|
|
159
|
+
if (projectData.namespace) console.log(`Namespace : ${projectData.namespace}`);
|
|
160
|
+
if (Array.isArray(projectData.packageDirectories)) {
|
|
161
|
+
const pkgDirs = projectData.packageDirectories.map(d => d.path || d).join(', ');
|
|
162
|
+
console.log(`Package dirs : ${pkgDirs}`);
|
|
163
|
+
}
|
|
164
|
+
if (projectData.sourceApiVersion) console.log(`API version : ${projectData.sourceApiVersion}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (cliInfo) {
|
|
168
|
+
if (cliInfo.deprecated) {
|
|
169
|
+
console.log(`SF CLI : ${cliInfo.version} [DEPRECATED — upgrade to sf v2]`);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`SF CLI : ${cliInfo.version}`);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`SF CLI : not found (install with: npm install -g @salesforce/cli)`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (process.env.SF_ORG_ALIAS) {
|
|
178
|
+
console.log(`Default org : ${process.env.SF_ORG_ALIAS} (SF_ORG_ALIAS)`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// List orgs (only if SF CLI is available)
|
|
182
|
+
if (cliInfo) {
|
|
183
|
+
const orgs = listOrgs();
|
|
184
|
+
if (orgs && orgs.length > 0) {
|
|
185
|
+
const connectedOrgs = orgs.filter(o => o.type === 'connected');
|
|
186
|
+
const scratchOrgs = orgs.filter(o => o.orgType === 'scratch');
|
|
187
|
+
console.log(`Connected : ${connectedOrgs.length} org(s)`);
|
|
188
|
+
if (scratchOrgs.length > 0) {
|
|
189
|
+
console.log(`Scratch orgs : ${scratchOrgs.length} (${scratchOrgs.filter(o => o.type === 'connected').length} active)`);
|
|
190
|
+
}
|
|
191
|
+
// Show default org
|
|
192
|
+
const defaultOrg = orgs.find(o => o.isDefaultOrg);
|
|
193
|
+
if (defaultOrg) {
|
|
194
|
+
const expiry = defaultOrg.expirationDate ? ` (expires: ${defaultOrg.expirationDate})` : '';
|
|
195
|
+
console.log(`Default org : ${defaultOrg.alias || defaultOrg.username}${expiry}`);
|
|
196
|
+
}
|
|
197
|
+
} else if (orgs !== null) {
|
|
198
|
+
console.log(`Orgs : none connected`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log('───────────────────────────────────────────────────────\n');
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sfdx-scanner PreBash Hook
|
|
4
|
+
*
|
|
5
|
+
* Runs sfdx-scanner (PMD) on modified Apex files before git push or sf deploy.
|
|
6
|
+
* The AI is still active when this fires, so it can read the violations and
|
|
7
|
+
* self-heal before proceeding.
|
|
8
|
+
*
|
|
9
|
+
* - Lifecycle: PreToolUse (Bash matcher)
|
|
10
|
+
* - Only fires on: git push, sf project deploy
|
|
11
|
+
* - Graceful no-op if sfdx-scanner not installed
|
|
12
|
+
* - Gated behind SCC_HOOK_PROFILE=strict
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const MAX_STDIN = 1024 * 1024;
|
|
21
|
+
|
|
22
|
+
function log(msg) {
|
|
23
|
+
process.stderr.write(`${msg}\n`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if sf scanner is available.
|
|
28
|
+
*/
|
|
29
|
+
function isScannerAvailable() {
|
|
30
|
+
try {
|
|
31
|
+
execSync('sf scanner --version', { timeout: 5000, stdio: 'pipe' });
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get modified Apex files from git diff.
|
|
40
|
+
*/
|
|
41
|
+
function getModifiedApexFiles() {
|
|
42
|
+
try {
|
|
43
|
+
const output = execSync('git diff --name-only HEAD', {
|
|
44
|
+
timeout: 5000,
|
|
45
|
+
stdio: 'pipe',
|
|
46
|
+
encoding: 'utf8',
|
|
47
|
+
});
|
|
48
|
+
return output
|
|
49
|
+
.split('\n')
|
|
50
|
+
.filter(f => f.endsWith('.cls') || f.endsWith('.trigger'))
|
|
51
|
+
.filter(f => f.trim().length > 0);
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run sfdx-scanner on the given files.
|
|
59
|
+
*/
|
|
60
|
+
function runScanner(files) {
|
|
61
|
+
if (files.length === 0) return [];
|
|
62
|
+
|
|
63
|
+
const target = files.join(',');
|
|
64
|
+
try {
|
|
65
|
+
const output = execSync(`sf scanner run --target "${target}" --format json --engine pmd`, {
|
|
66
|
+
timeout: 40000,
|
|
67
|
+
stdio: 'pipe',
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
});
|
|
70
|
+
return JSON.parse(output);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Scanner returns non-zero when violations found, output is still valid JSON
|
|
73
|
+
if (err.stdout) {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(err.stdout);
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function run(rawInput) {
|
|
85
|
+
try {
|
|
86
|
+
const input = JSON.parse(rawInput);
|
|
87
|
+
const command = String(input.tool_input?.command || '');
|
|
88
|
+
|
|
89
|
+
// Only intercept git push and sf deploy commands
|
|
90
|
+
const isGitPush = /\bgit\s+push\b/.test(command);
|
|
91
|
+
const isDeploy = /\bsf\s+project\s+deploy\s+start\b/.test(command);
|
|
92
|
+
if (!isGitPush && !isDeploy) return rawInput;
|
|
93
|
+
|
|
94
|
+
// Check if scanner is available
|
|
95
|
+
if (!isScannerAvailable()) return rawInput;
|
|
96
|
+
|
|
97
|
+
// Get modified Apex files
|
|
98
|
+
const files = getModifiedApexFiles();
|
|
99
|
+
if (files.length === 0) return rawInput;
|
|
100
|
+
|
|
101
|
+
// Run scanner
|
|
102
|
+
const violations = runScanner(files);
|
|
103
|
+
if (!Array.isArray(violations) || violations.length === 0) return rawInput;
|
|
104
|
+
|
|
105
|
+
// Count by severity
|
|
106
|
+
const critical = violations.filter(v => v.severity <= 1).length;
|
|
107
|
+
const high = violations.filter(v => v.severity === 2).length;
|
|
108
|
+
const medium = violations.filter(v => v.severity === 3).length;
|
|
109
|
+
|
|
110
|
+
if (critical + high === 0) return rawInput; // Only warn on critical/high
|
|
111
|
+
|
|
112
|
+
log(`\n[SCC Scanner] Found ${violations.length} PMD violation(s) in ${files.length} file(s):`);
|
|
113
|
+
for (const v of violations.filter(v2 => v2.severity <= 2).slice(0, 10)) {
|
|
114
|
+
const sev = v.severity === 1 ? 'CRITICAL' : 'HIGH';
|
|
115
|
+
const file = path.basename(v.fileName || '');
|
|
116
|
+
log(` [${sev}] ${file}:${v.line} — ${v.message || v.ruleName}`);
|
|
117
|
+
}
|
|
118
|
+
if (violations.filter(v2 => v2.severity <= 2).length > 10) {
|
|
119
|
+
log(` ... and ${violations.filter(v2 => v2.severity <= 2).length - 10} more`);
|
|
120
|
+
}
|
|
121
|
+
log(' Recommend fixing before push. Run /sf-quality-gate for details.\n');
|
|
122
|
+
} catch {
|
|
123
|
+
// Ignore errors — never block the user
|
|
124
|
+
}
|
|
125
|
+
return rawInput;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (require.main === module) {
|
|
129
|
+
let raw = '';
|
|
130
|
+
process.stdin.setEncoding('utf8');
|
|
131
|
+
process.stdin.on('data', chunk => {
|
|
132
|
+
if (raw.length < MAX_STDIN) {
|
|
133
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
process.stdin.on('end', () => {
|
|
137
|
+
const result = run(raw);
|
|
138
|
+
process.stdout.write(result);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { run };
|