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,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* pre-tool-use.js — PreToolUse hook for SCC.
|
|
6
|
+
*
|
|
7
|
+
* Reads the tool use event from stdin (JSON) and:
|
|
8
|
+
* 1. Warns if deprecated `sfdx` commands are used (suggest `sf` equivalent)
|
|
9
|
+
* 2. Warns about dangerous org operations (delete, reset, deploy to prod)
|
|
10
|
+
*
|
|
11
|
+
* Claude Code sends hook input as JSON on stdin:
|
|
12
|
+
* { tool_name: "Bash", tool_input: { command: "..." } }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
|
|
17
|
+
// Map of deprecated sfdx commands to their sf equivalents
|
|
18
|
+
const SFDX_MIGRATION_MAP = {
|
|
19
|
+
'sfdx force:apex:execute': 'sf apex run',
|
|
20
|
+
'sfdx force:apex:test:run': 'sf apex run test',
|
|
21
|
+
'sfdx force:apex:class:create': 'sf apex generate class',
|
|
22
|
+
'sfdx force:lightning:component:create': 'sf lightning generate component',
|
|
23
|
+
'sfdx force:source:deploy': 'sf project deploy start',
|
|
24
|
+
'sfdx force:source:retrieve': 'sf project retrieve start',
|
|
25
|
+
'sfdx force:source:push': 'sf project deploy start',
|
|
26
|
+
'sfdx force:source:pull': 'sf project retrieve start',
|
|
27
|
+
'sfdx force:org:create': 'sf org create scratch',
|
|
28
|
+
'sfdx force:org:delete': 'sf org delete scratch',
|
|
29
|
+
'sfdx force:org:list': 'sf org list',
|
|
30
|
+
'sfdx force:org:open': 'sf org open',
|
|
31
|
+
'sfdx force:data:query': 'sf data query',
|
|
32
|
+
'sfdx force:data:record:create': 'sf data create record',
|
|
33
|
+
'sfdx force:data:record:update': 'sf data update record',
|
|
34
|
+
'sfdx force:data:record:delete': 'sf data delete record',
|
|
35
|
+
'sfdx force:data:bulk:upsert': 'sf data upsert bulk',
|
|
36
|
+
'sfdx force:package:create': 'sf package create',
|
|
37
|
+
'sfdx force:package:version:create': 'sf package version create',
|
|
38
|
+
'sfdx force:user:create': 'sf org create user',
|
|
39
|
+
'sfdx force:user:password:generate': 'sf org generate password',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Patterns for dangerous operations
|
|
43
|
+
const DANGEROUS_PATTERNS = [
|
|
44
|
+
{
|
|
45
|
+
pattern: /sfdx force:org:delete|sf org delete/,
|
|
46
|
+
warning: 'This will permanently delete a Salesforce org. Ensure you have selected the correct org.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pattern: /--target-org\s+\S*(prod|production|prd)\S*/i,
|
|
50
|
+
warning: 'This command targets a PRODUCTION org. Double-check before proceeding.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pattern: /-u\s+\S*(prod|production|prd)\S*/i,
|
|
54
|
+
warning: 'This command targets what appears to be a PRODUCTION org alias.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
pattern: /sf project deploy start.*--target-org\s+\S*(prod|production|prd)\S*/i,
|
|
58
|
+
warning: 'Deploying to PRODUCTION. Ensure all tests pass and you have approval.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: /sfdx force:data:bulk:delete|sf data delete bulk/,
|
|
62
|
+
warning: 'Bulk delete operation detected. This will permanently delete records.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: /sf org reset|sfdx force:org:reset/,
|
|
66
|
+
warning: 'Org reset will remove all source tracking history.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
pattern: /--no-track-source/,
|
|
70
|
+
warning: 'Deploying without source tracking. Changes may not be reflected in local source.',
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
function checkSfdxDeprecation(command) {
|
|
75
|
+
const warnings = [];
|
|
76
|
+
for (const [deprecated, replacement] of Object.entries(SFDX_MIGRATION_MAP)) {
|
|
77
|
+
if (command.includes(deprecated)) {
|
|
78
|
+
warnings.push({
|
|
79
|
+
type: 'deprecation',
|
|
80
|
+
message: `Deprecated command detected: \`${deprecated}\``,
|
|
81
|
+
suggestion: `Use the new SF CLI equivalent: \`${replacement}\``,
|
|
82
|
+
docs: 'https://developer.salesforce.com/tools/salesforcecli/migration',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return warnings;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function checkDangerousOps(command) {
|
|
90
|
+
const warnings = [];
|
|
91
|
+
for (const { pattern, warning } of DANGEROUS_PATTERNS) {
|
|
92
|
+
if (pattern.test(command)) {
|
|
93
|
+
warnings.push({
|
|
94
|
+
type: 'danger',
|
|
95
|
+
message: warning,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return warnings;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function processInput(input) {
|
|
103
|
+
// Only process Bash tool
|
|
104
|
+
if (input.tool_name !== 'Bash' && input.tool_name !== 'bash') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const command = (input.tool_input && input.tool_input.command) || '';
|
|
109
|
+
if (!command) return null;
|
|
110
|
+
|
|
111
|
+
// Skip if command doesn't involve SF/SFDX
|
|
112
|
+
if (!command.includes('sfdx') && !command.includes('sf ') && !command.includes('sf\n')) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const deprecationWarnings = checkSfdxDeprecation(command);
|
|
117
|
+
const dangerWarnings = checkDangerousOps(command);
|
|
118
|
+
const allWarnings = [...deprecationWarnings, ...dangerWarnings];
|
|
119
|
+
|
|
120
|
+
if (allWarnings.length === 0) return null;
|
|
121
|
+
|
|
122
|
+
return allWarnings;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
// Read JSON from stdin
|
|
128
|
+
let rawInput = '';
|
|
129
|
+
|
|
130
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
131
|
+
rl.on('line', line => { rawInput += line + '\n'; });
|
|
132
|
+
|
|
133
|
+
rl.on('close', () => {
|
|
134
|
+
let input = {};
|
|
135
|
+
try {
|
|
136
|
+
input = JSON.parse(rawInput.trim() || '{}');
|
|
137
|
+
} catch {
|
|
138
|
+
// Not valid JSON — exit gracefully
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const warnings = processInput(input);
|
|
143
|
+
|
|
144
|
+
if (!warnings || warnings.length === 0) {
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Print warnings to stderr (visible in Claude Code output)
|
|
149
|
+
console.error('\n[SCC Hook] Salesforce CLI Warnings:');
|
|
150
|
+
for (const w of warnings) {
|
|
151
|
+
if (w.type === 'deprecation') {
|
|
152
|
+
console.error(` DEPRECATED : ${w.message}`);
|
|
153
|
+
console.error(` MIGRATE TO : ${w.suggestion}`);
|
|
154
|
+
if (w.docs) console.error(` DOCS : ${w.docs}`);
|
|
155
|
+
} else if (w.type === 'danger') {
|
|
156
|
+
console.error(` WARNING : ${w.message}`);
|
|
157
|
+
}
|
|
158
|
+
console.error();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Exit 0 to allow the tool use to proceed (we're just warning, not blocking)
|
|
162
|
+
process.exit(0);
|
|
163
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Quality Gate Hook
|
|
4
|
+
*
|
|
5
|
+
* Runs lightweight quality checks after file edits.
|
|
6
|
+
* For Apex files: checks for common anti-patterns using shared apex-analysis module.
|
|
7
|
+
* For LWC files: checks for common issues.
|
|
8
|
+
* Falls back to no-op when file type is unrecognized.
|
|
9
|
+
*
|
|
10
|
+
* Uses apex-analysis.js for:
|
|
11
|
+
* - Comment/string stripping (eliminates false positives)
|
|
12
|
+
* - Loop depth tracking (activeLoopDepths stack + globalBraceDepth)
|
|
13
|
+
* - Test class detection
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const { preprocessApex, isTestClass, trackLoopDepth } = require('../lib/apex-analysis');
|
|
22
|
+
|
|
23
|
+
const MAX_STDIN = 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
function log(msg) {
|
|
26
|
+
process.stderr.write(`${msg}\n`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check Apex file for common anti-patterns.
|
|
31
|
+
*/
|
|
32
|
+
function checkApex(filePath, content) {
|
|
33
|
+
const issues = [];
|
|
34
|
+
|
|
35
|
+
// Skip test classes for security/quality checks (they don't run in production)
|
|
36
|
+
const testClass = isTestClass(content);
|
|
37
|
+
|
|
38
|
+
// Preprocess: strip comments and string literals to avoid false positives
|
|
39
|
+
const processed = preprocessApex(content);
|
|
40
|
+
const processedLines = processed.split('\n');
|
|
41
|
+
const depths = trackLoopDepth(processedLines);
|
|
42
|
+
|
|
43
|
+
// Check for SOQL/DML inside loops
|
|
44
|
+
for (let i = 0; i < processedLines.length; i++) {
|
|
45
|
+
const line = processedLines[i];
|
|
46
|
+
if (depths[i] > 0 && /\[\s*SELECT\s/i.test(line)) {
|
|
47
|
+
issues.push(`Line ${i + 1}: SOQL query inside loop — potential governor limit violation`);
|
|
48
|
+
}
|
|
49
|
+
if (depths[i] > 0) {
|
|
50
|
+
const dmlPattern = /\b(insert|update|delete|upsert)\s+/i;
|
|
51
|
+
const dbPattern = /Database\.(insert|update|delete|upsert)/i;
|
|
52
|
+
if (dmlPattern.test(line) || dbPattern.test(line)) {
|
|
53
|
+
issues.push(`Line ${i + 1}: DML operation inside loop — potential governor limit violation`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for hardcoded IDs (use raw content — IDs could be in strings)
|
|
59
|
+
if (/['"][a-zA-Z0-9]{15,18}['"]/.test(content) && /00[0-9a-zA-Z]{13,16}/.test(content)) {
|
|
60
|
+
issues.push('Hardcoded Salesforce record ID detected — use Custom Settings, Custom Metadata, or labels instead');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for System.debug (configurable threshold)
|
|
64
|
+
const debugThreshold = parseInt(process.env.SCC_DEBUG_THRESHOLD, 10) || 5;
|
|
65
|
+
const debugCount = (processed.match(/System\.debug/g) || []).length;
|
|
66
|
+
if (debugCount > debugThreshold) {
|
|
67
|
+
issues.push(`${debugCount} System.debug statements found — consider removing before deployment`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Security checks (skip for test classes) ---
|
|
71
|
+
if (!testClass) {
|
|
72
|
+
// Sharing model detection
|
|
73
|
+
const beforeFirstBrace = content.split('{')[0] || '';
|
|
74
|
+
const hasSharing = /\b(with\s+sharing|without\s+sharing|inherited\s+sharing)\b/i.test(beforeFirstBrace);
|
|
75
|
+
const hasSoqlOrDml = /\[\s*SELECT\s/i.test(processed) ||
|
|
76
|
+
/\b(insert|update|delete|upsert)\s+/i.test(processed) ||
|
|
77
|
+
/Database\.(insert|update|delete|upsert|query)/i.test(processed);
|
|
78
|
+
|
|
79
|
+
if (!hasSharing && hasSoqlOrDml) {
|
|
80
|
+
issues.push('[HIGH] No sharing declaration (with sharing/without sharing/inherited sharing) on class that performs SOQL/DML');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Privilege escalation: without sharing + @AuraEnabled/@RemoteAction
|
|
84
|
+
if (/\bwithout\s+sharing\b/i.test(beforeFirstBrace)) {
|
|
85
|
+
if (/@AuraEnabled/i.test(content) || /@RemoteAction/i.test(content)) {
|
|
86
|
+
issues.push('[HIGH] "without sharing" class exposes @AuraEnabled/@RemoteAction methods — potential privilege escalation');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// CRUD/FLS: SOQL without WITH USER_MODE or WITH SECURITY_ENFORCED
|
|
91
|
+
for (let i = 0; i < processedLines.length; i++) {
|
|
92
|
+
const line = processedLines[i];
|
|
93
|
+
if (/\[\s*SELECT\s/i.test(line) && !/WITH\s+(USER_MODE|SECURITY_ENFORCED)/i.test(line)) {
|
|
94
|
+
issues.push(`Line ${i + 1}: [LOW] SOQL query without WITH USER_MODE or WITH SECURITY_ENFORCED`);
|
|
95
|
+
break; // Only warn once to avoid noise
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// DML without AccessLevel.USER_MODE
|
|
100
|
+
for (let i = 0; i < processedLines.length; i++) {
|
|
101
|
+
const line = processedLines[i];
|
|
102
|
+
if (/Database\.(insert|update|delete|upsert)\s*\(/i.test(line) &&
|
|
103
|
+
!/AccessLevel\.USER_MODE/i.test(line)) {
|
|
104
|
+
issues.push(`Line ${i + 1}: [LOW] Database DML without AccessLevel.USER_MODE`);
|
|
105
|
+
break; // Only warn once
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Dynamic SOQL warning
|
|
110
|
+
if (/Database\.(query|countQuery)\s*\(/i.test(processed)) {
|
|
111
|
+
issues.push('[LOW] Dynamic SOQL detected — ensure user-supplied values use String.escapeSingleQuotes()');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return issues;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check LWC JavaScript for common issues.
|
|
120
|
+
*/
|
|
121
|
+
function checkLwc(filePath, content) {
|
|
122
|
+
const issues = [];
|
|
123
|
+
|
|
124
|
+
// Check for console.log
|
|
125
|
+
const consoleCount = (content.match(/console\.(log|warn|error|info)/g) || []).length;
|
|
126
|
+
if (consoleCount > 0) {
|
|
127
|
+
issues.push(`${consoleCount} console statement(s) found — remove before deployment`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check for imperative Apex calls without error handling
|
|
131
|
+
if (/import\s+\w+\s+from\s+['"]@salesforce\/apex\//.test(content)) {
|
|
132
|
+
if (!/\.catch\(|try\s*\{|catch\s*\(/.test(content)) {
|
|
133
|
+
issues.push('Imperative Apex call detected without error handling — add .catch() or try/catch');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return issues;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Run sf scanner on a single Apex file (standard+ profile).
|
|
142
|
+
* Returns array of issue strings. Graceful no-op if scanner not installed.
|
|
143
|
+
*/
|
|
144
|
+
function runScannerOnFile(filePath) {
|
|
145
|
+
const profile = (process.env.SCC_HOOK_PROFILE || 'standard').toLowerCase();
|
|
146
|
+
if (profile === 'minimal') return [];
|
|
147
|
+
|
|
148
|
+
const { execSync } = require('child_process');
|
|
149
|
+
try {
|
|
150
|
+
execSync('sf scanner --version', { timeout: 5000, stdio: 'pipe' });
|
|
151
|
+
} catch {
|
|
152
|
+
return []; // Scanner not installed — skip gracefully
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const output = execSync(`sf scanner run --target "${filePath}" --format json --engine pmd`, {
|
|
157
|
+
timeout: 30000,
|
|
158
|
+
stdio: 'pipe',
|
|
159
|
+
encoding: 'utf8',
|
|
160
|
+
});
|
|
161
|
+
const violations = JSON.parse(output);
|
|
162
|
+
if (!Array.isArray(violations)) return [];
|
|
163
|
+
return violations
|
|
164
|
+
.filter(v => v.severity <= 2)
|
|
165
|
+
.slice(0, 5)
|
|
166
|
+
.map(v => {
|
|
167
|
+
const sev = v.severity === 1 ? 'CRITICAL' : 'HIGH';
|
|
168
|
+
return `[${sev}] PMD: ${v.message || v.ruleName} (line ${v.line})`;
|
|
169
|
+
});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err.stdout) {
|
|
172
|
+
try {
|
|
173
|
+
const violations = JSON.parse(err.stdout);
|
|
174
|
+
if (!Array.isArray(violations)) return [];
|
|
175
|
+
return violations
|
|
176
|
+
.filter(v => v.severity <= 2)
|
|
177
|
+
.slice(0, 5)
|
|
178
|
+
.map(v => {
|
|
179
|
+
const sev = v.severity === 1 ? 'CRITICAL' : 'HIGH';
|
|
180
|
+
return `[${sev}] PMD: ${v.message || v.ruleName} (line ${v.line})`;
|
|
181
|
+
});
|
|
182
|
+
} catch {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function maybeRunQualityGate(filePath) {
|
|
191
|
+
if (!filePath || !fs.existsSync(filePath)) return;
|
|
192
|
+
|
|
193
|
+
filePath = path.resolve(filePath);
|
|
194
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
195
|
+
|
|
196
|
+
let content;
|
|
197
|
+
try {
|
|
198
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
199
|
+
} catch {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let issues = [];
|
|
204
|
+
|
|
205
|
+
if (ext === '.cls' || ext === '.trigger') {
|
|
206
|
+
issues = checkApex(filePath, content);
|
|
207
|
+
// Run sf scanner for deeper PMD analysis (standard+ profile, graceful no-op)
|
|
208
|
+
const scannerIssues = runScannerOnFile(filePath);
|
|
209
|
+
issues = issues.concat(scannerIssues);
|
|
210
|
+
} else if (ext === '.js' && filePath.includes('/lwc/')) {
|
|
211
|
+
issues = checkLwc(filePath, content);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (issues.length > 0) {
|
|
215
|
+
log(`\n[SCC QualityGate] ${path.basename(filePath)}:`);
|
|
216
|
+
for (const issue of issues) {
|
|
217
|
+
log(` - ${issue}`);
|
|
218
|
+
}
|
|
219
|
+
if (issues.some(i => i.includes('PMD:'))) {
|
|
220
|
+
log(' Install Salesforce Code Analyzer for enhanced checks: sf plugins install @salesforce/sfdx-scanner');
|
|
221
|
+
}
|
|
222
|
+
log('');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function run(rawInput) {
|
|
227
|
+
try {
|
|
228
|
+
const input = JSON.parse(rawInput);
|
|
229
|
+
const filePath = String(input.tool_input?.file_path || '');
|
|
230
|
+
maybeRunQualityGate(filePath);
|
|
231
|
+
} catch {
|
|
232
|
+
// Ignore parse errors
|
|
233
|
+
}
|
|
234
|
+
return rawInput;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (require.main === module) {
|
|
238
|
+
let raw = '';
|
|
239
|
+
process.stdin.setEncoding('utf8');
|
|
240
|
+
process.stdin.on('data', chunk => {
|
|
241
|
+
if (raw.length < MAX_STDIN) {
|
|
242
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
process.stdin.on('end', () => {
|
|
246
|
+
const result = run(raw);
|
|
247
|
+
process.stdout.write(result);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = { run };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
HOOK_ID="${1:-}"
|
|
5
|
+
REL_SCRIPT_PATH="${2:-}"
|
|
6
|
+
PROFILES_CSV="${3:-standard,strict}"
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
PLUGIN_ROOT="${SCC_PLUGIN_ROOT:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
|
|
9
|
+
|
|
10
|
+
# Preserve stdin for passthrough or script execution
|
|
11
|
+
INPUT="$(cat)"
|
|
12
|
+
|
|
13
|
+
if [[ -z "$HOOK_ID" || -z "$REL_SCRIPT_PATH" ]]; then
|
|
14
|
+
printf '%s' "$INPUT"
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Ask Node helper if this hook is enabled
|
|
19
|
+
ENABLED="$(node "${PLUGIN_ROOT}/scripts/hooks/check-hook-enabled.js" "$HOOK_ID" "$PROFILES_CSV" 2>/dev/null || echo yes)"
|
|
20
|
+
if [[ "$ENABLED" != "yes" ]]; then
|
|
21
|
+
printf '%s' "$INPUT"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
SCRIPT_PATH="${PLUGIN_ROOT}/${REL_SCRIPT_PATH}"
|
|
26
|
+
if [[ ! -f "$SCRIPT_PATH" ]]; then
|
|
27
|
+
echo "[Hook] Script not found for ${HOOK_ID}: ${SCRIPT_PATH}" >&2
|
|
28
|
+
printf '%s' "$INPUT"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
printf '%s' "$INPUT" | "$SCRIPT_PATH"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* run-with-flags.js — Profile-based hook gating for SCC.
|
|
6
|
+
*
|
|
7
|
+
* Reads stdin, checks SCC_HOOK_PROFILE and SCC_DISABLED_HOOKS before running
|
|
8
|
+
* a hook script. Passes stdin through to stdout when the hook is skipped.
|
|
9
|
+
*
|
|
10
|
+
* Usage: node run-with-flags.js <hook-name> <min-profile> <script-path>
|
|
11
|
+
* Profiles: minimal < standard < strict
|
|
12
|
+
*
|
|
13
|
+
* Hook execution:
|
|
14
|
+
* - If module exports run(input): calls run(stdin) directly (no child process)
|
|
15
|
+
* - Otherwise: spawns 'node <script-path>' with stdin piped (legacy hooks)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const { spawnSync } = require('child_process');
|
|
20
|
+
|
|
21
|
+
const PROFILE_LEVELS = { minimal: 1, standard: 2, strict: 3 };
|
|
22
|
+
const MAX_STDIN = 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
function readStdinRaw() {
|
|
25
|
+
return new Promise(resolve => {
|
|
26
|
+
let raw = '';
|
|
27
|
+
process.stdin.setEncoding('utf8');
|
|
28
|
+
process.stdin.on('data', chunk => {
|
|
29
|
+
if (raw.length < MAX_STDIN) {
|
|
30
|
+
const remaining = MAX_STDIN - raw.length;
|
|
31
|
+
raw += chunk.substring(0, remaining);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
process.stdin.on('end', () => resolve(raw));
|
|
35
|
+
process.stdin.on('error', () => resolve(raw));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const hookName = process.argv[2];
|
|
41
|
+
const minProfile = process.argv[3] || 'standard';
|
|
42
|
+
const scriptPath = process.argv[4];
|
|
43
|
+
const timeoutMs = parseInt(process.argv[5], 10) || 30000;
|
|
44
|
+
|
|
45
|
+
const raw = await readStdinRaw();
|
|
46
|
+
|
|
47
|
+
// Guard: missing args — pass through transparently
|
|
48
|
+
if (!hookName || !scriptPath) {
|
|
49
|
+
process.stdout.write(raw);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const currentProfile = process.env.SCC_HOOK_PROFILE || 'standard';
|
|
54
|
+
const disabledHooks = (process.env.SCC_DISABLED_HOOKS || '').split(',').map(h => h.trim().toLowerCase()).filter(Boolean);
|
|
55
|
+
|
|
56
|
+
// Skip if hook is explicitly disabled
|
|
57
|
+
if (disabledHooks.includes(hookName.toLowerCase())) {
|
|
58
|
+
process.stdout.write(raw);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Skip if current profile is below minimum required level
|
|
63
|
+
const currentLevel = PROFILE_LEVELS[currentProfile] || 2;
|
|
64
|
+
const minLevel = PROFILE_LEVELS[minProfile] || 2;
|
|
65
|
+
if (currentLevel < minLevel) {
|
|
66
|
+
process.stdout.write(raw);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Guard: script must exist and be a .js file
|
|
71
|
+
if (!scriptPath.endsWith('.js') || !fs.existsSync(scriptPath)) {
|
|
72
|
+
process.stderr.write(`[Hook] Script not found or invalid: ${scriptPath}\n`);
|
|
73
|
+
process.stdout.write(raw);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Source pre-check: only require() hooks that export run().
|
|
78
|
+
// Prevents executing legacy hooks' module-scope side effects (stdin listeners,
|
|
79
|
+
// process.exit calls, main() invocations) when called via require().
|
|
80
|
+
const src = fs.readFileSync(scriptPath, 'utf8');
|
|
81
|
+
const hasRunExport = /\bmodule\.exports\b/.test(src) && /\brun\b/.test(src);
|
|
82
|
+
|
|
83
|
+
let hookModule;
|
|
84
|
+
if (hasRunExport) {
|
|
85
|
+
try {
|
|
86
|
+
hookModule = require(scriptPath);
|
|
87
|
+
} catch (requireErr) {
|
|
88
|
+
process.stderr.write(`[Hook] require() failed for ${hookName}: ${requireErr.message}\n`);
|
|
89
|
+
// Fall through to spawnSync
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (hookModule && typeof hookModule.run === 'function') {
|
|
94
|
+
try {
|
|
95
|
+
const output = hookModule.run(raw);
|
|
96
|
+
if (output !== null && output !== undefined) process.stdout.write(output);
|
|
97
|
+
} catch (runErr) {
|
|
98
|
+
process.stderr.write(`[Hook] run() error for ${hookName}: ${runErr.message}\n`);
|
|
99
|
+
process.stdout.write(raw);
|
|
100
|
+
}
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Legacy path: spawn a child Node process for hooks without run() export
|
|
105
|
+
const result = spawnSync('node', [scriptPath], {
|
|
106
|
+
input: raw,
|
|
107
|
+
encoding: 'utf8',
|
|
108
|
+
env: process.env,
|
|
109
|
+
cwd: process.cwd(),
|
|
110
|
+
timeout: timeoutMs
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (result.error) {
|
|
114
|
+
process.stderr.write(`[Hook] Spawn error for ${hookName}: ${result.error.message}\n`);
|
|
115
|
+
process.stdout.write(raw);
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (result.signal) {
|
|
120
|
+
process.stderr.write(`[Hook] ${hookName} killed by ${result.signal} (timeout: ${timeoutMs}ms)\n`);
|
|
121
|
+
process.stdout.write(raw);
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
126
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
127
|
+
|
|
128
|
+
const code = Number.isInteger(result.status) ? result.status : 0;
|
|
129
|
+
process.exit(code);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main().catch(err => {
|
|
133
|
+
process.stderr.write(`[Hook] run-with-flags error: ${err.message}\n`);
|
|
134
|
+
process.exit(0);
|
|
135
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Session end marker hook - outputs stdin to stdout unchanged.
|
|
6
|
+
* Exports run() for in-process execution (avoids spawnSync issues on Windows).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function run(rawInput) {
|
|
10
|
+
return rawInput || '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Legacy CLI execution (when run directly)
|
|
14
|
+
if (require.main === module) {
|
|
15
|
+
const MAX_STDIN = 1024 * 1024;
|
|
16
|
+
let raw = '';
|
|
17
|
+
process.stdin.setEncoding('utf8');
|
|
18
|
+
process.stdin.on('data', chunk => {
|
|
19
|
+
if (raw.length < MAX_STDIN) {
|
|
20
|
+
const remaining = MAX_STDIN - raw.length;
|
|
21
|
+
raw += chunk.substring(0, remaining);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
process.stdin.on('end', () => {
|
|
25
|
+
process.stdout.write(raw);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { run };
|