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,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* install-apply.js — Apply SCC content installation.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node install-apply.js [target] [--profile <name>] [--target <name>] [--dry-run] [--help]
|
|
9
|
+
*
|
|
10
|
+
* Targets: apex, lwc, all (shorthand for 'full' profile)
|
|
11
|
+
* Profiles: core, apex, lwc, devops, security, full
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { executeInstall, VALID_TARGETS, VALID_PROFILES } = require('../lib/install-executor');
|
|
16
|
+
const { loadInstallConfig } = require('../lib/install-config');
|
|
17
|
+
|
|
18
|
+
function showHelp(exitCode = 0) {
|
|
19
|
+
console.log(`
|
|
20
|
+
scc install — Install SCC content
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
scc install [target] [options]
|
|
24
|
+
scc install --profile <name> --target <name>
|
|
25
|
+
|
|
26
|
+
Shorthand targets:
|
|
27
|
+
apex Install Apex profile content
|
|
28
|
+
lwc Install LWC profile content
|
|
29
|
+
all Install full profile content
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--profile <name> Profile to install: core|apex|lwc|devops|security|full (default: full)
|
|
33
|
+
--target <name> Install target: ${VALID_TARGETS.join('|')} (default: claude)
|
|
34
|
+
--config <path> Load install config from scc-install.json (CLI args override config)
|
|
35
|
+
--project <dir> Project root directory (default: current directory)
|
|
36
|
+
--dry-run Show what would be installed without making changes
|
|
37
|
+
--help, -h Show this help
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
scc install apex
|
|
41
|
+
scc install all
|
|
42
|
+
scc install --config scc-install.json
|
|
43
|
+
scc install --config scc-install.json --target cursor
|
|
44
|
+
scc install --profile security --target claude
|
|
45
|
+
scc install --profile lwc --target cursor --dry-run
|
|
46
|
+
`);
|
|
47
|
+
process.exit(exitCode);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseArgs(argv) {
|
|
51
|
+
const args = argv.slice(2);
|
|
52
|
+
const opts = {
|
|
53
|
+
profile: null,
|
|
54
|
+
target: null,
|
|
55
|
+
projectRoot: process.cwd(),
|
|
56
|
+
dryRun: false,
|
|
57
|
+
help: false,
|
|
58
|
+
config: null,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < args.length; i++) {
|
|
62
|
+
const arg = args[i];
|
|
63
|
+
if (arg === '--help' || arg === '-h') {
|
|
64
|
+
opts.help = true;
|
|
65
|
+
} else if (arg === '--dry-run') {
|
|
66
|
+
opts.dryRun = true;
|
|
67
|
+
} else if (arg === '--config' && args[i + 1]) {
|
|
68
|
+
opts.config = args[++i];
|
|
69
|
+
} else if (arg === '--profile' && args[i + 1]) {
|
|
70
|
+
opts.profile = args[++i];
|
|
71
|
+
} else if (arg === '--target' && args[i + 1]) {
|
|
72
|
+
opts.target = args[++i];
|
|
73
|
+
} else if (arg === '--project' && args[i + 1]) {
|
|
74
|
+
opts.projectRoot = path.resolve(args[++i]);
|
|
75
|
+
} else if (!arg.startsWith('--')) {
|
|
76
|
+
// Positional: treat as shorthand target/profile
|
|
77
|
+
switch (arg) {
|
|
78
|
+
case 'apex':
|
|
79
|
+
opts.profile = opts.profile || 'apex';
|
|
80
|
+
break;
|
|
81
|
+
case 'lwc':
|
|
82
|
+
opts.profile = opts.profile || 'lwc';
|
|
83
|
+
break;
|
|
84
|
+
case 'all':
|
|
85
|
+
opts.profile = opts.profile || 'full';
|
|
86
|
+
break;
|
|
87
|
+
case 'core':
|
|
88
|
+
case 'devops':
|
|
89
|
+
case 'security':
|
|
90
|
+
case 'full':
|
|
91
|
+
opts.profile = opts.profile || arg;
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
// Could be a target name
|
|
95
|
+
if (VALID_TARGETS.includes(arg)) {
|
|
96
|
+
opts.target = arg;
|
|
97
|
+
} else {
|
|
98
|
+
console.error(`[ERROR] Unknown argument: ${arg}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return opts;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Merge CLI opts with config file. CLI args override config values.
|
|
110
|
+
*/
|
|
111
|
+
function mergeWithConfig(opts) {
|
|
112
|
+
if (!opts.config) {
|
|
113
|
+
// No config file — apply defaults
|
|
114
|
+
if (!opts.profile) opts.profile = 'full';
|
|
115
|
+
if (!opts.target) opts.target = 'claude';
|
|
116
|
+
return opts;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const config = loadInstallConfig(opts.config);
|
|
121
|
+
console.log(`[INFO] Loaded config from ${config.path}`);
|
|
122
|
+
|
|
123
|
+
// CLI args override config values
|
|
124
|
+
if (!opts.profile) opts.profile = config.profile || 'full';
|
|
125
|
+
if (!opts.target) opts.target = config.target || 'claude';
|
|
126
|
+
|
|
127
|
+
return opts;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error(`[ERROR] Config: ${err.message}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function validateArgs(opts) {
|
|
135
|
+
if (!VALID_PROFILES.includes(opts.profile)) {
|
|
136
|
+
console.error(`[ERROR] Invalid profile: ${opts.profile}`);
|
|
137
|
+
console.error(`Valid profiles: ${VALID_PROFILES.join(', ')}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
if (!VALID_TARGETS.includes(opts.target)) {
|
|
141
|
+
console.error(`[ERROR] Invalid target: ${opts.target}`);
|
|
142
|
+
console.error(`Valid targets: ${VALID_TARGETS.join(', ')}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const opts = mergeWithConfig(parseArgs(process.argv));
|
|
148
|
+
|
|
149
|
+
if (opts.help) {
|
|
150
|
+
showHelp(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
validateArgs(opts);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const result = executeInstall(opts.profile, opts.target, {
|
|
157
|
+
dryRun: opts.dryRun,
|
|
158
|
+
projectRoot: opts.projectRoot,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (result.fileCount === 0 && !opts.dryRun) {
|
|
162
|
+
console.log('\n[INFO] No files were installed. Check your manifests or profile definition.');
|
|
163
|
+
} else {
|
|
164
|
+
console.log('\nDone.');
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(`\n[ERROR] Installation failed: ${err.message}`);
|
|
168
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* uninstall.js — Remove SCC-managed files.
|
|
6
|
+
*
|
|
7
|
+
* Reads the state store to find all files installed by SCC and removes them.
|
|
8
|
+
* Optionally prunes empty directories.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const readline = require('readline');
|
|
14
|
+
const { loadState, clearState, removeFiles } = require('../lib/state-store');
|
|
15
|
+
const { fileExists } = require('../lib/utils');
|
|
16
|
+
|
|
17
|
+
function showHelp(exitCode = 0) {
|
|
18
|
+
console.log(`
|
|
19
|
+
scc uninstall — Remove SCC-managed files
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
scc uninstall [options]
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--target <name> Only uninstall files for a specific target
|
|
26
|
+
--dry-run Show what would be removed without making changes
|
|
27
|
+
--yes, -y Skip confirmation prompt
|
|
28
|
+
--keep-state Remove files but keep the state store
|
|
29
|
+
--json Output result as JSON
|
|
30
|
+
--help, -h Show this help
|
|
31
|
+
|
|
32
|
+
Notes:
|
|
33
|
+
Only files tracked in the SCC state store will be removed.
|
|
34
|
+
Files you manually added to .claude/ etc. will not be touched.
|
|
35
|
+
`);
|
|
36
|
+
process.exit(exitCode);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const args = argv.slice(2);
|
|
41
|
+
const opts = { target: null, dryRun: false, yes: false, keepState: false, json: false, help: false };
|
|
42
|
+
for (let i = 0; i < args.length; i++) {
|
|
43
|
+
const arg = args[i];
|
|
44
|
+
if (arg === '--help' || arg === '-h') opts.help = true;
|
|
45
|
+
else if (arg === '--dry-run') opts.dryRun = true;
|
|
46
|
+
else if (arg === '--yes' || arg === '-y') opts.yes = true;
|
|
47
|
+
else if (arg === '--keep-state') opts.keepState = true;
|
|
48
|
+
else if (arg === '--json') opts.json = true;
|
|
49
|
+
else if (arg === '--target' && args[i + 1]) opts.target = args[++i];
|
|
50
|
+
}
|
|
51
|
+
return opts;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function confirm(question) {
|
|
55
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
56
|
+
return new Promise(resolve => {
|
|
57
|
+
rl.question(question, answer => {
|
|
58
|
+
rl.close();
|
|
59
|
+
resolve(answer.trim().toLowerCase());
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function main() {
|
|
65
|
+
const opts = parseArgs(process.argv);
|
|
66
|
+
if (opts.help) showHelp(0);
|
|
67
|
+
|
|
68
|
+
const state = loadState();
|
|
69
|
+
|
|
70
|
+
if (!state.installedFiles || state.installedFiles.length === 0) {
|
|
71
|
+
const msg = 'No SCC installation found. Nothing to uninstall.';
|
|
72
|
+
if (opts.json) console.log(JSON.stringify({ status: 'not-installed', message: msg }));
|
|
73
|
+
else console.log(`[INFO] ${msg}`);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Filter by target if specified
|
|
78
|
+
let filesToRemove = state.installedFiles;
|
|
79
|
+
if (opts.target) {
|
|
80
|
+
filesToRemove = filesToRemove.filter(f => f.target === opts.target);
|
|
81
|
+
if (filesToRemove.length === 0) {
|
|
82
|
+
console.log(`[INFO] No files found for target: ${opts.target}`);
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Only consider files that actually exist
|
|
88
|
+
const existingFiles = filesToRemove.filter(f => fileExists(f.destPath));
|
|
89
|
+
const missingFiles = filesToRemove.filter(f => !fileExists(f.destPath));
|
|
90
|
+
|
|
91
|
+
if (!opts.json) {
|
|
92
|
+
console.log(`\nSCC Uninstall${opts.dryRun ? ' (DRY RUN)' : ''}`);
|
|
93
|
+
console.log(`${'─'.repeat(50)}`);
|
|
94
|
+
console.log(`Files to remove : ${existingFiles.length}`);
|
|
95
|
+
if (missingFiles.length > 0) {
|
|
96
|
+
console.log(`Already missing : ${missingFiles.length} (will clear from state)`);
|
|
97
|
+
}
|
|
98
|
+
console.log();
|
|
99
|
+
|
|
100
|
+
if (opts.dryRun || opts.json) {
|
|
101
|
+
// just list
|
|
102
|
+
} else if (existingFiles.length > 0) {
|
|
103
|
+
console.log('Files that will be deleted:');
|
|
104
|
+
for (const f of existingFiles) {
|
|
105
|
+
console.log(` ${f.destPath}`);
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (opts.dryRun) {
|
|
112
|
+
if (opts.json) {
|
|
113
|
+
console.log(JSON.stringify({ status: 'dry-run', wouldRemove: existingFiles.map(f => f.destPath), alreadyMissing: missingFiles.map(f => f.destPath) }, null, 2));
|
|
114
|
+
} else {
|
|
115
|
+
console.log(`[dry-run] Would remove ${existingFiles.length} file(s).`);
|
|
116
|
+
}
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Prompt for confirmation unless --yes
|
|
121
|
+
if (!opts.yes && !opts.json && existingFiles.length > 0) {
|
|
122
|
+
const answer = await confirm(`Remove ${existingFiles.length} file(s)? [y/N] `);
|
|
123
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
124
|
+
console.log('Aborted.');
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Remove files
|
|
130
|
+
const removed = [];
|
|
131
|
+
const errors = [];
|
|
132
|
+
|
|
133
|
+
for (const f of existingFiles) {
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(f.destPath);
|
|
136
|
+
removed.push(f.destPath);
|
|
137
|
+
if (!opts.json) console.log(` [REMOVED] ${f.destPath}`);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
errors.push({ path: f.destPath, error: err.message });
|
|
140
|
+
if (!opts.json) console.error(` [ERROR] Failed to remove ${f.destPath}: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Try to prune empty directories
|
|
145
|
+
const dirsToCheck = new Set(existingFiles.map(f => path.dirname(f.destPath)));
|
|
146
|
+
const pruned = [];
|
|
147
|
+
for (const dir of dirsToCheck) {
|
|
148
|
+
try {
|
|
149
|
+
const entries = fs.readdirSync(dir);
|
|
150
|
+
if (entries.length === 0) {
|
|
151
|
+
fs.rmdirSync(dir);
|
|
152
|
+
pruned.push(dir);
|
|
153
|
+
if (!opts.json) console.log(` [PRUNED] empty dir: ${dir}`);
|
|
154
|
+
}
|
|
155
|
+
} catch { /* ignore */ }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update state
|
|
159
|
+
if (!opts.keepState) {
|
|
160
|
+
if (opts.target) {
|
|
161
|
+
// Only remove files for this target from state
|
|
162
|
+
removeFiles(filesToRemove.map(f => f.destPath));
|
|
163
|
+
} else {
|
|
164
|
+
clearState();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = {
|
|
169
|
+
status: errors.length > 0 ? 'partial' : 'ok',
|
|
170
|
+
removed: removed.length,
|
|
171
|
+
failed: errors.length,
|
|
172
|
+
prunedDirs: pruned.length,
|
|
173
|
+
removedFiles: removed,
|
|
174
|
+
failedFiles: errors,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (opts.json) {
|
|
178
|
+
console.log(JSON.stringify(result, null, 2));
|
|
179
|
+
} else {
|
|
180
|
+
console.log();
|
|
181
|
+
console.log(`Removed: ${removed.length} file(s).`);
|
|
182
|
+
if (pruned.length > 0) console.log(`Pruned : ${pruned.length} empty director(ies).`);
|
|
183
|
+
if (errors.length > 0) console.log(`Errors : ${errors.length} file(s) could not be removed.`);
|
|
184
|
+
if (!opts.keepState) console.log('State : cleared.');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
main().catch(err => {
|
|
191
|
+
console.error(`[FATAL] ${err.message}`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stop Hook: Check for console.log statements in modified LWC/JS files
|
|
5
|
+
*
|
|
6
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
7
|
+
*
|
|
8
|
+
* Runs after each response and checks if any modified JavaScript/TypeScript
|
|
9
|
+
* files contain console.log statements. Particularly useful for LWC development
|
|
10
|
+
* where console.log should be removed before deployment.
|
|
11
|
+
*
|
|
12
|
+
* Exclusions: test files, config files, and scripts/ directory.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
|
|
17
|
+
// Files where console.log is expected and should not trigger warnings
|
|
18
|
+
const EXCLUDED_PATTERNS = [
|
|
19
|
+
/\.test\.[jt]sx?$/,
|
|
20
|
+
/\.spec\.[jt]sx?$/,
|
|
21
|
+
/\.config\.[jt]s$/,
|
|
22
|
+
/scripts\//,
|
|
23
|
+
/__tests__\//,
|
|
24
|
+
/__mocks__\//,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function isGitRepo() {
|
|
28
|
+
try {
|
|
29
|
+
const { execSync } = require('child_process');
|
|
30
|
+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' });
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getGitModifiedFiles(patterns) {
|
|
38
|
+
try {
|
|
39
|
+
const { execSync } = require('child_process');
|
|
40
|
+
const output = execSync('git diff --name-only HEAD 2>/dev/null || git diff --name-only', {
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
43
|
+
}).trim();
|
|
44
|
+
if (!output) return [];
|
|
45
|
+
const files = output.split('\n').filter(Boolean);
|
|
46
|
+
if (patterns && patterns.length > 0) {
|
|
47
|
+
return files.filter(f => patterns.some(p => new RegExp(p).test(f)));
|
|
48
|
+
}
|
|
49
|
+
return files;
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const MAX_STDIN = 1024 * 1024; // 1MB limit
|
|
56
|
+
let data = '';
|
|
57
|
+
process.stdin.setEncoding('utf8');
|
|
58
|
+
|
|
59
|
+
process.stdin.on('data', chunk => {
|
|
60
|
+
if (data.length < MAX_STDIN) {
|
|
61
|
+
const remaining = MAX_STDIN - data.length;
|
|
62
|
+
data += chunk.substring(0, remaining);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
process.stdin.on('end', () => {
|
|
67
|
+
try {
|
|
68
|
+
if (!isGitRepo()) {
|
|
69
|
+
process.stdout.write(data);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const files = getGitModifiedFiles(['\\.tsx?$', '\\.jsx?$'])
|
|
74
|
+
.filter(f => fs.existsSync(f))
|
|
75
|
+
.filter(f => !EXCLUDED_PATTERNS.some(pattern => pattern.test(f)));
|
|
76
|
+
|
|
77
|
+
let hasConsole = false;
|
|
78
|
+
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
try {
|
|
81
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
82
|
+
if (content && content.includes('console.log')) {
|
|
83
|
+
console.error(`[Hook] WARNING: console.log found in ${file}`);
|
|
84
|
+
hasConsole = true;
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// Skip unreadable files
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (hasConsole) {
|
|
92
|
+
console.error('[Hook] Remove console.log statements before committing (especially in LWC)');
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error(`[Hook] check-console-log error: ${err.message}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Always output the original data
|
|
99
|
+
process.stdout.write(data);
|
|
100
|
+
process.exit(0);
|
|
101
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helper: Check if a hook is enabled based on profile and disabled list.
|
|
6
|
+
* Returns "yes" or "no" based on SCC_HOOK_PROFILE and SCC_DISABLED_HOOKS.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { isHookEnabled } = require('../lib/hook-flags');
|
|
10
|
+
|
|
11
|
+
const [, , hookId, profilesCsv] = process.argv;
|
|
12
|
+
if (!hookId) {
|
|
13
|
+
process.stdout.write('yes');
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
process.stdout.write(isHookEnabled(hookId, { profiles: profilesCsv }) ? 'yes' : 'no');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SessionStart hook: checks if platform reference docs are outdated.
|
|
6
|
+
* Warns the user to run /update-platform-docs if files haven't been
|
|
7
|
+
* verified in more than SCC_PLATFORM_DOCS_AGE_MONTHS months (default: 4).
|
|
8
|
+
*
|
|
9
|
+
* Uses CLAUDE_PLUGIN_ROOT / SCC_PLUGIN_ROOT env vars — no hardcoded paths.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT
|
|
16
|
+
|| process.env.SCC_PLUGIN_ROOT
|
|
17
|
+
|| path.resolve(__dirname, '../..');
|
|
18
|
+
|
|
19
|
+
const MONTHS_THRESHOLD = parseInt(
|
|
20
|
+
process.env.SCC_PLATFORM_DOCS_AGE_MONTHS || '4',
|
|
21
|
+
10
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const FILES_TO_CHECK = [
|
|
25
|
+
path.join(PLUGIN_ROOT, 'skills', '_reference', 'DEPRECATIONS.md'),
|
|
26
|
+
path.join(PLUGIN_ROOT, 'skills', '_reference', 'API_VERSIONS.md')
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const filePath of FILES_TO_CHECK) {
|
|
30
|
+
if (!fs.existsSync(filePath)) continue;
|
|
31
|
+
|
|
32
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
33
|
+
const match = content.match(/Last verified:\s*(\d{4}-\d{2}-\d{2})/);
|
|
34
|
+
if (!match) continue;
|
|
35
|
+
|
|
36
|
+
const verifiedDate = new Date(match[1]);
|
|
37
|
+
const threshold = new Date();
|
|
38
|
+
threshold.setMonth(threshold.getMonth() - MONTHS_THRESHOLD);
|
|
39
|
+
|
|
40
|
+
if (verifiedDate < threshold) {
|
|
41
|
+
const name = path.basename(filePath);
|
|
42
|
+
const age = Math.round((Date.now() - verifiedDate.getTime()) / (1000 * 60 * 60 * 24 * 30));
|
|
43
|
+
console.log(
|
|
44
|
+
`Platform docs outdated: ${name} was last verified ${match[1]} (${age} months ago). ` +
|
|
45
|
+
`Run /update-platform-docs to refresh with latest Salesforce release info.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cost Tracker Hook
|
|
4
|
+
*
|
|
5
|
+
* Appends lightweight session usage metrics to ~/.claude/metrics/costs.jsonl.
|
|
6
|
+
* Runs on Stop event to log token usage and estimated costs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { ensureDir } = require('../lib/utils');
|
|
14
|
+
|
|
15
|
+
const MAX_STDIN = 1024 * 1024;
|
|
16
|
+
|
|
17
|
+
function getClaudeDir() {
|
|
18
|
+
return path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.claude');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toNumber(value) {
|
|
22
|
+
const n = Number(value);
|
|
23
|
+
return Number.isFinite(n) ? n : 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
27
|
+
const table = {
|
|
28
|
+
'haiku': { in: 0.8, out: 4.0 },
|
|
29
|
+
'sonnet': { in: 3.0, out: 15.0 },
|
|
30
|
+
'opus': { in: 15.0, out: 75.0 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const normalized = String(model || '').toLowerCase();
|
|
34
|
+
let rates = table.sonnet;
|
|
35
|
+
if (normalized.includes('haiku')) rates = table.haiku;
|
|
36
|
+
if (normalized.includes('opus')) rates = table.opus;
|
|
37
|
+
|
|
38
|
+
const cost = (inputTokens / 1_000_000) * rates.in + (outputTokens / 1_000_000) * rates.out;
|
|
39
|
+
return Math.round(cost * 1e6) / 1e6;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let raw = '';
|
|
43
|
+
process.stdin.setEncoding('utf8');
|
|
44
|
+
process.stdin.on('data', chunk => {
|
|
45
|
+
if (raw.length < MAX_STDIN) {
|
|
46
|
+
raw += chunk.substring(0, MAX_STDIN - raw.length);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
process.stdin.on('end', () => {
|
|
51
|
+
try {
|
|
52
|
+
const input = raw.trim() ? JSON.parse(raw) : {};
|
|
53
|
+
const usage = input.usage || input.token_usage || {};
|
|
54
|
+
const inputTokens = toNumber(usage.input_tokens || usage.prompt_tokens || 0);
|
|
55
|
+
const outputTokens = toNumber(usage.output_tokens || usage.completion_tokens || 0);
|
|
56
|
+
|
|
57
|
+
const model = String(input.model || process.env.CLAUDE_MODEL || 'unknown');
|
|
58
|
+
const sessionId = String(process.env.CLAUDE_SESSION_ID || 'default');
|
|
59
|
+
|
|
60
|
+
const metricsDir = path.join(getClaudeDir(), 'metrics');
|
|
61
|
+
ensureDir(metricsDir);
|
|
62
|
+
|
|
63
|
+
const row = {
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
session_id: sessionId,
|
|
66
|
+
model,
|
|
67
|
+
input_tokens: inputTokens,
|
|
68
|
+
output_tokens: outputTokens,
|
|
69
|
+
estimated_cost_usd: estimateCost(model, inputTokens, outputTokens),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
fs.appendFileSync(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
|
|
73
|
+
} catch {
|
|
74
|
+
// Keep hook non-blocking
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.stdout.write(raw);
|
|
78
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Doc file warning hook (PreToolUse - Write)
|
|
4
|
+
* Warns about non-standard documentation files.
|
|
5
|
+
* Exit code 0 always (warns only, never blocks).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const MAX_STDIN = 1024 * 1024;
|
|
13
|
+
let data = '';
|
|
14
|
+
|
|
15
|
+
function isAllowedDocPath(filePath) {
|
|
16
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
17
|
+
const basename = path.basename(filePath);
|
|
18
|
+
|
|
19
|
+
if (!/\.(md|txt)$/i.test(filePath)) return true;
|
|
20
|
+
|
|
21
|
+
if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL|MEMORY|WORKLOG)\.md$/i.test(basename)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (/\.claude\/(commands|plans|projects)\//.test(normalized)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (/(^|\/)(docs|skills|\.history|memory)\//.test(normalized)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (/\.plan\.md$/i.test(basename)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.stdin.setEncoding('utf8');
|
|
41
|
+
process.stdin.on('data', c => {
|
|
42
|
+
if (data.length < MAX_STDIN) {
|
|
43
|
+
const remaining = MAX_STDIN - data.length;
|
|
44
|
+
data += c.substring(0, remaining);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
process.stdin.on('end', () => {
|
|
49
|
+
try {
|
|
50
|
+
const input = JSON.parse(data);
|
|
51
|
+
const filePath = String((input.tool_input && input.tool_input.file_path) || '');
|
|
52
|
+
|
|
53
|
+
if (filePath && !isAllowedDocPath(filePath)) {
|
|
54
|
+
console.error('[Hook] WARNING: Non-standard documentation file detected');
|
|
55
|
+
console.error(`[Hook] File: ${filePath}`);
|
|
56
|
+
console.error('[Hook] Consider consolidating into README.md or docs/ directory');
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// ignore parse errors
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.stdout.write(data);
|
|
63
|
+
});
|