@softspark/ai-toolkit 1.0.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/AGENTS.md +412 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +632 -0
- package/action.yml +53 -0
- package/app/.claude-plugin/plugin.json +44 -0
- package/app/ARCHITECTURE.md +306 -0
- package/app/CLAUDE.md.template +23 -0
- package/app/agents/ai-engineer.md +128 -0
- package/app/agents/backend-specialist.md +193 -0
- package/app/agents/business-intelligence.md +54 -0
- package/app/agents/chaos-monkey.md +67 -0
- package/app/agents/chief-of-staff.md +51 -0
- package/app/agents/code-archaeologist.md +127 -0
- package/app/agents/code-reviewer.md +184 -0
- package/app/agents/command-expert.md +131 -0
- package/app/agents/data-analyst.md +205 -0
- package/app/agents/data-scientist.md +151 -0
- package/app/agents/database-architect.md +317 -0
- package/app/agents/debugger.md +238 -0
- package/app/agents/devops-implementer.md +194 -0
- package/app/agents/documenter.md +364 -0
- package/app/agents/explorer-agent.md +145 -0
- package/app/agents/fact-checker.md +172 -0
- package/app/agents/frontend-specialist.md +209 -0
- package/app/agents/game-developer.md +216 -0
- package/app/agents/incident-responder.md +226 -0
- package/app/agents/infrastructure-architect.md +127 -0
- package/app/agents/infrastructure-validator.md +247 -0
- package/app/agents/llm-ops-engineer.md +237 -0
- package/app/agents/mcp-expert.md +228 -0
- package/app/agents/mcp-server-architect.md +195 -0
- package/app/agents/mcp-testing-engineer.md +292 -0
- package/app/agents/meta-architect.md +58 -0
- package/app/agents/ml-engineer.md +136 -0
- package/app/agents/mobile-developer.md +190 -0
- package/app/agents/night-watchman.md +55 -0
- package/app/agents/nlp-engineer.md +154 -0
- package/app/agents/orchestrator.md +437 -0
- package/app/agents/performance-optimizer.md +254 -0
- package/app/agents/predictive-analyst.md +57 -0
- package/app/agents/product-manager.md +194 -0
- package/app/agents/project-planner.md +287 -0
- package/app/agents/prompt-engineer.md +103 -0
- package/app/agents/qa-automation-engineer.md +182 -0
- package/app/agents/rag-engineer.md +201 -0
- package/app/agents/research-synthesizer.md +138 -0
- package/app/agents/search-specialist.md +101 -0
- package/app/agents/security-architect.md +62 -0
- package/app/agents/security-auditor.md +293 -0
- package/app/agents/seo-specialist.md +111 -0
- package/app/agents/system-governor.md +57 -0
- package/app/agents/tech-lead.md +62 -0
- package/app/agents/technical-researcher.md +103 -0
- package/app/agents/test-engineer.md +264 -0
- package/app/constitution.md +38 -0
- package/app/hooks/_profile-check.sh +11 -0
- package/app/hooks/guard-destructive.sh +74 -0
- package/app/hooks/guard-path.sh +73 -0
- package/app/hooks/post-tool-use.sh +35 -0
- package/app/hooks/pre-compact.sh +31 -0
- package/app/hooks/quality-check.sh +22 -0
- package/app/hooks/quality-gate.sh +49 -0
- package/app/hooks/save-session.sh +24 -0
- package/app/hooks/session-end.sh +37 -0
- package/app/hooks/session-start.sh +29 -0
- package/app/hooks/subagent-start.sh +16 -0
- package/app/hooks/subagent-stop.sh +16 -0
- package/app/hooks/track-usage.sh +50 -0
- package/app/hooks/user-prompt-submit.sh +25 -0
- package/app/hooks.json +178 -0
- package/app/mcp-defaults.json +23 -0
- package/app/output-styles/golden-rules.md +43 -0
- package/app/plugins/README.md +19 -0
- package/app/plugins/csharp-pack/README.md +11 -0
- package/app/plugins/csharp-pack/plugin.json +18 -0
- package/app/plugins/enterprise-pack/README.md +16 -0
- package/app/plugins/enterprise-pack/hooks/output-style.sh +6 -0
- package/app/plugins/enterprise-pack/hooks/status-line.sh +8 -0
- package/app/plugins/enterprise-pack/plugin.json +24 -0
- package/app/plugins/frontend-pack/README.md +14 -0
- package/app/plugins/frontend-pack/plugin.json +22 -0
- package/app/plugins/java-pack/README.md +11 -0
- package/app/plugins/java-pack/plugin.json +18 -0
- package/app/plugins/kotlin-pack/README.md +11 -0
- package/app/plugins/kotlin-pack/plugin.json +18 -0
- package/app/plugins/memory-pack/README.md +24 -0
- package/app/plugins/memory-pack/hooks/observation-capture.sh +67 -0
- package/app/plugins/memory-pack/hooks/session-summary.sh +71 -0
- package/app/plugins/memory-pack/plugin.json +22 -0
- package/app/plugins/memory-pack/scripts/init_db.py +81 -0
- package/app/plugins/memory-pack/scripts/strip_private.py +22 -0
- package/app/plugins/memory-pack/skills/mem-search/SKILL.md +70 -0
- package/app/plugins/research-pack/README.md +14 -0
- package/app/plugins/research-pack/plugin.json +22 -0
- package/app/plugins/ruby-pack/README.md +11 -0
- package/app/plugins/ruby-pack/plugin.json +18 -0
- package/app/plugins/rust-pack/README.md +11 -0
- package/app/plugins/rust-pack/plugin.json +18 -0
- package/app/plugins/security-pack/README.md +15 -0
- package/app/plugins/security-pack/plugin.json +23 -0
- package/app/plugins/swift-pack/README.md +11 -0
- package/app/plugins/swift-pack/plugin.json +18 -0
- package/app/rules/claude-toolkit-rules.md +21 -0
- package/app/rules/git-conventions.md +5 -0
- package/app/rules/quality-gates.md +10 -0
- package/app/skills/_lib/__init__.py +1 -0
- package/app/skills/_lib/detect_utils.py +150 -0
- package/app/skills/agent-creator/SKILL.md +82 -0
- package/app/skills/analyze/SKILL.md +92 -0
- package/app/skills/analyze/scripts/complexity.py +165 -0
- package/app/skills/api-patterns/SKILL.md +305 -0
- package/app/skills/app-builder/SKILL.md +187 -0
- package/app/skills/architecture-audit/SKILL.md +141 -0
- package/app/skills/architecture-decision/SKILL.md +55 -0
- package/app/skills/architecture-decision/templates/adr-template.md +36 -0
- package/app/skills/biz-scan/SKILL.md +30 -0
- package/app/skills/briefing/SKILL.md +27 -0
- package/app/skills/build/SKILL.md +97 -0
- package/app/skills/build/scripts/detect-build.py +151 -0
- package/app/skills/chaos/SKILL.md +32 -0
- package/app/skills/ci/SKILL.md +77 -0
- package/app/skills/ci/scripts/ci-detect.py +135 -0
- package/app/skills/ci/templates/github-actions-node.yml +38 -0
- package/app/skills/ci/templates/github-actions-python.yml +42 -0
- package/app/skills/ci-cd-patterns/SKILL.md +299 -0
- package/app/skills/clean-code/SKILL.md +110 -0
- package/app/skills/clean-code/reference/dart.md +18 -0
- package/app/skills/clean-code/reference/go.md +23 -0
- package/app/skills/clean-code/reference/php.md +32 -0
- package/app/skills/clean-code/reference/python.md +180 -0
- package/app/skills/clean-code/reference/typescript.md +26 -0
- package/app/skills/command-creator/SKILL.md +83 -0
- package/app/skills/commit/SKILL.md +98 -0
- package/app/skills/commit/scripts/pre-commit-check.py +87 -0
- package/app/skills/commit/templates/conventional-commit.md +52 -0
- package/app/skills/csharp-patterns/SKILL.md +450 -0
- package/app/skills/database-patterns/SKILL.md +297 -0
- package/app/skills/debug/SKILL.md +154 -0
- package/app/skills/debug/scripts/error-parser.py +187 -0
- package/app/skills/debugging-tactics/SKILL.md +136 -0
- package/app/skills/deploy/SKILL.md +130 -0
- package/app/skills/deploy/scripts/pre_deploy_check.py +171 -0
- package/app/skills/deploy/templates/deployment-checklist.md +31 -0
- package/app/skills/design-an-interface/SKILL.md +105 -0
- package/app/skills/design-engineering/SKILL.md +260 -0
- package/app/skills/docker-devops/SKILL.md +303 -0
- package/app/skills/docs/SKILL.md +145 -0
- package/app/skills/docs/scripts/doc-inventory.py +176 -0
- package/app/skills/docs/templates/adr-template.md +36 -0
- package/app/skills/docs/templates/readme-template.md +67 -0
- package/app/skills/documentation-standards/SKILL.md +191 -0
- package/app/skills/ecommerce-patterns/SKILL.md +209 -0
- package/app/skills/evaluate/SKILL.md +132 -0
- package/app/skills/evolve/SKILL.md +27 -0
- package/app/skills/explain/SKILL.md +54 -0
- package/app/skills/explain/scripts/dependency-graph.py +215 -0
- package/app/skills/explore/SKILL.md +112 -0
- package/app/skills/explore/scripts/visualize.py +117 -0
- package/app/skills/fix/SKILL.md +78 -0
- package/app/skills/fix/scripts/error-classifier.py +191 -0
- package/app/skills/flutter-patterns/SKILL.md +254 -0
- package/app/skills/git-mastery/SKILL.md +70 -0
- package/app/skills/grill-me/SKILL.md +38 -0
- package/app/skills/health/SKILL.md +91 -0
- package/app/skills/health/scripts/health_check.py +162 -0
- package/app/skills/hive-mind/SKILL.md +56 -0
- package/app/skills/hook-creator/SKILL.md +107 -0
- package/app/skills/index/SKILL.md +74 -0
- package/app/skills/instinct-review/SKILL.md +77 -0
- package/app/skills/java-patterns/SKILL.md +442 -0
- package/app/skills/kotlin-patterns/SKILL.md +446 -0
- package/app/skills/lint/SKILL.md +103 -0
- package/app/skills/lint/scripts/detect-linters.py +112 -0
- package/app/skills/mcp-patterns/SKILL.md +270 -0
- package/app/skills/mem-search/SKILL.md +70 -0
- package/app/skills/migrate/SKILL.md +90 -0
- package/app/skills/migrate/scripts/migration-status.py +195 -0
- package/app/skills/migration-patterns/SKILL.md +260 -0
- package/app/skills/night-watch/SKILL.md +28 -0
- package/app/skills/observability-patterns/SKILL.md +203 -0
- package/app/skills/onboard/SKILL.md +76 -0
- package/app/skills/orchestrate/SKILL.md +86 -0
- package/app/skills/panic/SKILL.md +30 -0
- package/app/skills/performance-profiling/SKILL.md +59 -0
- package/app/skills/plan/SKILL.md +110 -0
- package/app/skills/plan/templates/plan-template.md +40 -0
- package/app/skills/plan-writing/SKILL.md +201 -0
- package/app/skills/plugin-creator/SKILL.md +78 -0
- package/app/skills/pr/SKILL.md +129 -0
- package/app/skills/pr/scripts/pr-summary.py +175 -0
- package/app/skills/prd-to-issues/SKILL.md +108 -0
- package/app/skills/prd-to-plan/SKILL.md +120 -0
- package/app/skills/predict/SKILL.md +30 -0
- package/app/skills/qa-session/SKILL.md +110 -0
- package/app/skills/rag-patterns/SKILL.md +203 -0
- package/app/skills/refactor/SKILL.md +124 -0
- package/app/skills/refactor/scripts/refactor-scan.py +210 -0
- package/app/skills/refactor-plan/SKILL.md +112 -0
- package/app/skills/repeat/SKILL.md +149 -0
- package/app/skills/research-mastery/SKILL.md +56 -0
- package/app/skills/review/SKILL.md +141 -0
- package/app/skills/review/scripts/diff-analyzer.py +170 -0
- package/app/skills/rollback/SKILL.md +87 -0
- package/app/skills/rollback/scripts/rollback_info.py +149 -0
- package/app/skills/ruby-patterns/SKILL.md +454 -0
- package/app/skills/rust-patterns/SKILL.md +446 -0
- package/app/skills/search/SKILL.md +64 -0
- package/app/skills/security-patterns/SKILL.md +91 -0
- package/app/skills/security-patterns/reference/authentication.md +37 -0
- package/app/skills/security-patterns/reference/authorization.md +22 -0
- package/app/skills/security-patterns/reference/input-validation.md +30 -0
- package/app/skills/security-patterns/reference/oauth-csrf-audit.md +131 -0
- package/app/skills/skill-creator/SKILL.md +154 -0
- package/app/skills/skill-creator/templates/dashboard/index.html +130 -0
- package/app/skills/skill-creator/templates/reasoning-engine/assets/example.json +12 -0
- package/app/skills/skill-creator/templates/reasoning-engine/search.py +110 -0
- package/app/skills/subagent-development/SKILL.md +225 -0
- package/app/skills/subagent-development/reference/code-quality-reviewer-prompt.md +145 -0
- package/app/skills/subagent-development/reference/implementer-prompt.md +118 -0
- package/app/skills/subagent-development/reference/spec-reviewer-prompt.md +100 -0
- package/app/skills/swarm/SKILL.md +81 -0
- package/app/skills/swift-patterns/SKILL.md +500 -0
- package/app/skills/tdd/SKILL.md +174 -0
- package/app/skills/tdd/reference/deep-modules.md +32 -0
- package/app/skills/tdd/reference/interface-design.md +32 -0
- package/app/skills/tdd/reference/mocking.md +52 -0
- package/app/skills/tdd/reference/refactoring.md +10 -0
- package/app/skills/tdd/reference/tests.md +59 -0
- package/app/skills/teams/SKILL.md +101 -0
- package/app/skills/test/SKILL.md +107 -0
- package/app/skills/test/scripts/detect-runner.py +113 -0
- package/app/skills/testing-patterns/SKILL.md +73 -0
- package/app/skills/testing-patterns/reference/flutter-testing.md +33 -0
- package/app/skills/testing-patterns/reference/go-testing.md +52 -0
- package/app/skills/testing-patterns/reference/php-phpunit.md +39 -0
- package/app/skills/testing-patterns/reference/python-pytest.md +228 -0
- package/app/skills/testing-patterns/reference/typescript-vitest.md +50 -0
- package/app/skills/triage-issue/SKILL.md +120 -0
- package/app/skills/typescript-patterns/SKILL.md +256 -0
- package/app/skills/ubiquitous-language/SKILL.md +74 -0
- package/app/skills/verification-before-completion/SKILL.md +108 -0
- package/app/skills/workflow/SKILL.md +250 -0
- package/app/skills/write-a-prd/SKILL.md +129 -0
- package/app/skills/write-a-prd/reference/visual-companion.md +78 -0
- package/app/skills/write-a-prd/scripts/frame-template.html +111 -0
- package/app/skills/write-a-prd/scripts/visual-server.cjs +79 -0
- package/app/templates/skill/generator/SKILL.md.template +40 -0
- package/app/templates/skill/knowledge/SKILL.md.template +52 -0
- package/app/templates/skill/linter/SKILL.md.template +34 -0
- package/app/templates/skill/reviewer/SKILL.md.template +51 -0
- package/app/templates/skill/workflow/SKILL.md.template +49 -0
- package/benchmarks/README.md +111 -0
- package/benchmarks/ecosystem-dashboard.json +148 -0
- package/benchmarks/ecosystem-harvest.json +148 -0
- package/benchmarks/results.json +38 -0
- package/benchmarks/run.py +351 -0
- package/bin/ai-toolkit.js +345 -0
- package/kb/best-practices/README.md +11 -0
- package/kb/howto/README.md +11 -0
- package/kb/procedures/maintenance-sop.md +306 -0
- package/kb/reference/agents-catalog.md +124 -0
- package/kb/reference/anti-pattern-registry-format.md +221 -0
- package/kb/reference/architecture-overview.md +232 -0
- package/kb/reference/benchmark-config.md +62 -0
- package/kb/reference/ci-integration.md +66 -0
- package/kb/reference/claude-ecosystem-benchmark-snapshot.md +80 -0
- package/kb/reference/claude-ecosystem-expansion-foundations.md +102 -0
- package/kb/reference/commands-catalog.md +21 -0
- package/kb/reference/distribution-model.md +63 -0
- package/kb/reference/global-install-model.md +56 -0
- package/kb/reference/hierarchical-override-pattern.md +200 -0
- package/kb/reference/hooks-catalog.md +306 -0
- package/kb/reference/integrations.md +88 -0
- package/kb/reference/language-packs.md +52 -0
- package/kb/reference/merge-friendly-install-model.md +58 -0
- package/kb/reference/plugin-pack-conventions.md +151 -0
- package/kb/reference/quick-wins-implementation-summary.md +70 -0
- package/kb/reference/skill-templates.md +50 -0
- package/kb/reference/skills-catalog.md +215 -0
- package/kb/reference/skills-unification.md +57 -0
- package/kb/reference/stats.md +69 -0
- package/kb/reference/sync.md +76 -0
- package/kb/troubleshooting/README.md +11 -0
- package/llms-full.txt +3068 -0
- package/llms.txt +39 -0
- package/package.json +75 -0
- package/scripts/_common.py +160 -0
- package/scripts/add_rule.py +50 -0
- package/scripts/benchmark_config.py +127 -0
- package/scripts/benchmark_ecosystem.py +288 -0
- package/scripts/check_deps.py +260 -0
- package/scripts/create_skill.py +118 -0
- package/scripts/doctor.py +504 -0
- package/scripts/eject.py +113 -0
- package/scripts/emission.py +256 -0
- package/scripts/evaluate_skills.py +260 -0
- package/scripts/frontmatter.py +58 -0
- package/scripts/generate_agents_md.py +91 -0
- package/scripts/generate_aider_conf.py +51 -0
- package/scripts/generate_cline.py +35 -0
- package/scripts/generate_copilot.py +30 -0
- package/scripts/generate_cursor_rules.py +35 -0
- package/scripts/generate_gemini.py +28 -0
- package/scripts/generate_llms_txt.py +164 -0
- package/scripts/generate_roo_modes.py +80 -0
- package/scripts/generate_windsurf.py +35 -0
- package/scripts/generator_base.py +140 -0
- package/scripts/harvest_ecosystem.py +50 -0
- package/scripts/inject_rule_cli.py +101 -0
- package/scripts/inject_section_cli.py +47 -0
- package/scripts/injection.py +180 -0
- package/scripts/install.py +236 -0
- package/scripts/install_git_hooks.py +71 -0
- package/scripts/install_steps/__init__.py +5 -0
- package/scripts/install_steps/ai_tools.py +261 -0
- package/scripts/install_steps/hooks.py +90 -0
- package/scripts/install_steps/markers.py +79 -0
- package/scripts/install_steps/symlinks.py +87 -0
- package/scripts/merge-hooks.py +192 -0
- package/scripts/plugin.py +642 -0
- package/scripts/plugin_schema.py +138 -0
- package/scripts/remove_rule.py +58 -0
- package/scripts/stats.py +81 -0
- package/scripts/sync.py +215 -0
- package/scripts/uninstall.py +292 -0
- package/scripts/validate.py +700 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kotlin-patterns
|
|
3
|
+
description: "Loaded when user asks about Kotlin development patterns"
|
|
4
|
+
effort: medium
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Kotlin Patterns Skill
|
|
9
|
+
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
### Gradle KTS Multi-Module Layout
|
|
13
|
+
```
|
|
14
|
+
project-root/
|
|
15
|
+
├── build.gradle.kts
|
|
16
|
+
├── settings.gradle.kts
|
|
17
|
+
├── gradle/libs.versions.toml
|
|
18
|
+
├── app/
|
|
19
|
+
│ ├── build.gradle.kts
|
|
20
|
+
│ └── src/{main,test}/kotlin/com/example/app/
|
|
21
|
+
├── domain/
|
|
22
|
+
│ └── src/main/kotlin/com/example/domain/
|
|
23
|
+
│ ├── model/
|
|
24
|
+
│ ├── repository/
|
|
25
|
+
│ └── usecase/
|
|
26
|
+
└── infrastructure/
|
|
27
|
+
└── src/main/kotlin/com/example/infra/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### settings.gradle.kts
|
|
31
|
+
```kotlin
|
|
32
|
+
rootProject.name = "my-project"
|
|
33
|
+
dependencyResolutionManagement {
|
|
34
|
+
versionCatalogs { create("libs") { from(files("gradle/libs.versions.toml")) } }
|
|
35
|
+
}
|
|
36
|
+
include(":app", ":domain", ":infrastructure")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Module build.gradle.kts
|
|
40
|
+
```kotlin
|
|
41
|
+
plugins {
|
|
42
|
+
alias(libs.plugins.kotlin.jvm)
|
|
43
|
+
alias(libs.plugins.kotlin.serialization)
|
|
44
|
+
}
|
|
45
|
+
dependencies {
|
|
46
|
+
implementation(project(":domain"))
|
|
47
|
+
implementation(libs.kotlinx.coroutines.core)
|
|
48
|
+
testImplementation(libs.bundles.testing)
|
|
49
|
+
}
|
|
50
|
+
kotlin { jvmToolchain(21) }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Idioms / Code Style
|
|
56
|
+
|
|
57
|
+
### Data Classes + Value Classes
|
|
58
|
+
```kotlin
|
|
59
|
+
data class User(
|
|
60
|
+
val id: UserId,
|
|
61
|
+
val name: String,
|
|
62
|
+
val email: String,
|
|
63
|
+
val role: Role = Role.USER,
|
|
64
|
+
) {
|
|
65
|
+
init {
|
|
66
|
+
require(name.isNotBlank()) { "Name must not be blank" }
|
|
67
|
+
require(email.contains("@")) { "Invalid email format" }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@JvmInline
|
|
72
|
+
value class UserId(val value: String) // Zero-overhead type-safe ID
|
|
73
|
+
enum class Role { ADMIN, USER, GUEST }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Sealed Interfaces for Domain Modeling
|
|
77
|
+
```kotlin
|
|
78
|
+
sealed interface PaymentResult {
|
|
79
|
+
data class Success(val transactionId: String, val amount: Money) : PaymentResult
|
|
80
|
+
data class Declined(val reason: String) : PaymentResult
|
|
81
|
+
data class Error(val exception: Throwable) : PaymentResult
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Exhaustive when -- compiler enforces all branches
|
|
85
|
+
fun handlePayment(result: PaymentResult): String = when (result) {
|
|
86
|
+
is PaymentResult.Success -> "Paid: ${result.amount}"
|
|
87
|
+
is PaymentResult.Declined -> "Declined: ${result.reason}"
|
|
88
|
+
is PaymentResult.Error -> "Error: ${result.exception.message}"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Extension Functions
|
|
93
|
+
```kotlin
|
|
94
|
+
fun String.toSlug(): String =
|
|
95
|
+
lowercase().replace(Regex("[^a-z0-9\\s-]"), "").replace(Regex("\\s+"), "-").trim('-')
|
|
96
|
+
|
|
97
|
+
// Scoped extensions -- visible only inside containing class
|
|
98
|
+
class OrderService {
|
|
99
|
+
private fun Order.totalWithTax(): Money = total * (1 + taxRate)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Null Safety
|
|
104
|
+
```kotlin
|
|
105
|
+
fun getDisplayName(user: User?): String =
|
|
106
|
+
user?.name?.takeIf { it.isNotBlank() } ?: "Anonymous"
|
|
107
|
+
|
|
108
|
+
fun processEmail(email: String?) { email?.let { sendWelcomeEmail(it) } }
|
|
109
|
+
|
|
110
|
+
fun loadConfig(path: String?): Config {
|
|
111
|
+
val resolved = requireNotNull(path) { "Config path must not be null" }
|
|
112
|
+
return parseConfig(resolved)
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Scope Functions
|
|
117
|
+
|
|
118
|
+
| Function | Ref | Returns | Use case |
|
|
119
|
+
|----------|-----|---------|----------|
|
|
120
|
+
| `let` | `it` | Lambda result | Null check + transform |
|
|
121
|
+
| `run` | `this` | Lambda result | Config + compute |
|
|
122
|
+
| `apply` | `this` | Object itself | Object initialization |
|
|
123
|
+
| `also` | `it` | Object itself | Side effects |
|
|
124
|
+
|
|
125
|
+
```kotlin
|
|
126
|
+
val conn = Connection().apply { host = "localhost"; port = 5432 }
|
|
127
|
+
fun createUser(req: CreateUserRequest): User =
|
|
128
|
+
userRepository.save(req.toUser()).also { logger.info("Created: ${it.id}") }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Type-Safe Builders (DSL)
|
|
132
|
+
```kotlin
|
|
133
|
+
fun html(block: HtmlBuilder.() -> Unit): String = HtmlBuilder().apply(block).build()
|
|
134
|
+
|
|
135
|
+
val page = html {
|
|
136
|
+
head { title("My Page") }
|
|
137
|
+
body { p("Hello, world!") }
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Error Handling
|
|
144
|
+
|
|
145
|
+
### Result<T> and runCatching
|
|
146
|
+
```kotlin
|
|
147
|
+
fun findUser(id: UserId): Result<User> = runCatching {
|
|
148
|
+
userRepository.findById(id) ?: throw UserNotFoundException(id)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fun getUserDisplayName(id: UserId): String =
|
|
152
|
+
findUser(id).map { it.name }.recover { "Unknown User" }.getOrThrow()
|
|
153
|
+
|
|
154
|
+
fun handleLookup(id: UserId): Response = findUser(id).fold(
|
|
155
|
+
onSuccess = { Response.ok(it) },
|
|
156
|
+
onFailure = { Response.notFound(it.message) },
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Sealed Class Error Hierarchy
|
|
161
|
+
```kotlin
|
|
162
|
+
sealed class DomainError(override val message: String) : Exception(message) {
|
|
163
|
+
data class NotFound(val resource: String, val id: String) : DomainError("$resource not found: $id")
|
|
164
|
+
data class Validation(val field: String, val reason: String) : DomainError("Invalid $field: $reason")
|
|
165
|
+
data class Conflict(val detail: String) : DomainError("Conflict: $detail")
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun handleError(error: DomainError): Response = when (error) {
|
|
169
|
+
is DomainError.NotFound -> Response.status(404).body(error.message)
|
|
170
|
+
is DomainError.Validation -> Response.status(422).body(error.message)
|
|
171
|
+
is DomainError.Conflict -> Response.status(409).body(error.message)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Preconditions
|
|
176
|
+
```kotlin
|
|
177
|
+
fun transferMoney(from: Account, to: Account, amount: Money) {
|
|
178
|
+
require(amount.value > 0) { "Transfer amount must be positive" }
|
|
179
|
+
require(from.id != to.id) { "Cannot transfer to same account" }
|
|
180
|
+
check(from.balance >= amount) { "Insufficient funds: ${from.balance}" }
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Testing Patterns
|
|
187
|
+
|
|
188
|
+
### JUnit 5 + MockK
|
|
189
|
+
```kotlin
|
|
190
|
+
class UserServiceTest {
|
|
191
|
+
private val repository = mockk<UserRepository>()
|
|
192
|
+
private val notifier = mockk<NotificationService>(relaxed = true)
|
|
193
|
+
private val service = UserService(repository, notifier)
|
|
194
|
+
|
|
195
|
+
@Test
|
|
196
|
+
fun `creates user and sends welcome notification`() {
|
|
197
|
+
val expected = User(UserId("1"), "Alice", "alice@test.com")
|
|
198
|
+
every { repository.save(any()) } returns expected
|
|
199
|
+
|
|
200
|
+
val result = service.createUser(CreateUserRequest("Alice", "alice@test.com"))
|
|
201
|
+
|
|
202
|
+
assertThat(result).isEqualTo(expected)
|
|
203
|
+
verify(exactly = 1) { notifier.sendWelcome(expected) }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Test
|
|
207
|
+
fun `throws on duplicate email`() {
|
|
208
|
+
every { repository.save(any()) } throws DomainError.Conflict("Email exists")
|
|
209
|
+
assertThrows<DomainError.Conflict> {
|
|
210
|
+
service.createUser(CreateUserRequest("Bob", "dup@test.com"))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Kotest + Property-Based Testing
|
|
217
|
+
```kotlin
|
|
218
|
+
class MoneySpec : FunSpec({
|
|
219
|
+
test("addition is commutative") {
|
|
220
|
+
checkAll(Arb.positiveLong(), Arb.positiveLong()) { a, b ->
|
|
221
|
+
Money(a) + Money(b) shouldBe Money(b) + Money(a)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
test("cannot create negative money") {
|
|
225
|
+
shouldThrow<IllegalArgumentException> { Money(-1) }
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### assertSoftly + Parameterized Tests
|
|
231
|
+
```kotlin
|
|
232
|
+
@Test
|
|
233
|
+
fun `user has correct defaults`() {
|
|
234
|
+
val user = User.create("Alice", "alice@test.com")
|
|
235
|
+
assertSoftly(user) {
|
|
236
|
+
name shouldBe "Alice"
|
|
237
|
+
role shouldBe Role.USER
|
|
238
|
+
isActive shouldBe true
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@ParameterizedTest
|
|
243
|
+
@CsvSource("alice@test.com, true", "not-an-email, false", "'', false")
|
|
244
|
+
fun `validates email format`(input: String, expected: Boolean) {
|
|
245
|
+
assertThat(isValidEmail(input)).isEqualTo(expected)
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Common Frameworks
|
|
252
|
+
|
|
253
|
+
### Spring Boot with Kotlin
|
|
254
|
+
```kotlin
|
|
255
|
+
@RestController
|
|
256
|
+
@RequestMapping("/api/v1/users")
|
|
257
|
+
class UserController(private val userService: UserService) {
|
|
258
|
+
@GetMapping("/{id}")
|
|
259
|
+
fun getUser(@PathVariable id: String): ResponseEntity<UserDto> =
|
|
260
|
+
userService.findById(UserId(id))?.let { ResponseEntity.ok(it.toDto()) }
|
|
261
|
+
?: ResponseEntity.notFound().build()
|
|
262
|
+
|
|
263
|
+
@PostMapping
|
|
264
|
+
fun createUser(@Valid @RequestBody req: CreateUserRequest): ResponseEntity<UserDto> {
|
|
265
|
+
val user = userService.create(req)
|
|
266
|
+
return ResponseEntity.created(URI("/api/v1/users/${user.id.value}")).body(user.toDto())
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Ktor
|
|
272
|
+
```kotlin
|
|
273
|
+
fun Application.configureRouting() {
|
|
274
|
+
routing {
|
|
275
|
+
route("/api/v1/users") {
|
|
276
|
+
get { call.respond(userService.listAll()) }
|
|
277
|
+
get("/{id}") {
|
|
278
|
+
val id = call.parameters["id"] ?: return@get call.respond(HttpStatusCode.BadRequest)
|
|
279
|
+
val user = userService.findById(UserId(id)) ?: return@get call.respond(HttpStatusCode.NotFound)
|
|
280
|
+
call.respond(user)
|
|
281
|
+
}
|
|
282
|
+
post {
|
|
283
|
+
val req = call.receive<CreateUserRequest>()
|
|
284
|
+
call.respond(HttpStatusCode.Created, userService.create(req))
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fun Application.configurePlugins() {
|
|
291
|
+
install(ContentNegotiation) { json() }
|
|
292
|
+
install(StatusPages) {
|
|
293
|
+
exception<DomainError.NotFound> { call, e -> call.respond(HttpStatusCode.NotFound, e.message) }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Exposed (SQL DSL)
|
|
299
|
+
```kotlin
|
|
300
|
+
object Users : Table("users") {
|
|
301
|
+
val id = varchar("id", 36)
|
|
302
|
+
val name = varchar("name", 255)
|
|
303
|
+
val email = varchar("email", 255).uniqueIndex()
|
|
304
|
+
val role = enumerationByName<Role>("role", 20)
|
|
305
|
+
override val primaryKey = PrimaryKey(id)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
suspend fun findByRole(role: Role): List<User> = newSuspendedTransaction(Dispatchers.IO) {
|
|
309
|
+
Users.selectAll().where { Users.role eq role }.map { it.toUser() }
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### kotlinx.serialization
|
|
314
|
+
```kotlin
|
|
315
|
+
@Serializable
|
|
316
|
+
data class ApiResponse<T>(val data: T, val meta: Meta? = null)
|
|
317
|
+
|
|
318
|
+
@Serializable
|
|
319
|
+
data class Meta(val page: Int, val totalPages: Int, val totalItems: Long)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Koin (DI)
|
|
323
|
+
```kotlin
|
|
324
|
+
val appModule = module {
|
|
325
|
+
singleOf(::UserRepository)
|
|
326
|
+
singleOf(::UserService)
|
|
327
|
+
factoryOf(::CreateUserUseCase)
|
|
328
|
+
}
|
|
329
|
+
fun Application.configureKoin() { install(Koin) { modules(appModule) } }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Performance Tips
|
|
335
|
+
|
|
336
|
+
### Inline Functions
|
|
337
|
+
```kotlin
|
|
338
|
+
inline fun <T> measureTimeAndReturn(block: () -> T): Pair<T, Duration> {
|
|
339
|
+
val start = System.nanoTime()
|
|
340
|
+
val result = block()
|
|
341
|
+
return result to Duration.ofNanos(System.nanoTime() - start)
|
|
342
|
+
}
|
|
343
|
+
// crossinline: prevents non-local returns
|
|
344
|
+
inline fun transaction(crossinline block: () -> Unit) {
|
|
345
|
+
begin(); try { block() } catch (e: Exception) { rollback(); throw e }; commit()
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Value Classes
|
|
350
|
+
```kotlin
|
|
351
|
+
@JvmInline value class Email(val value: String) {
|
|
352
|
+
init { require(value.contains("@")) }
|
|
353
|
+
}
|
|
354
|
+
@JvmInline value class Meters(val value: Double)
|
|
355
|
+
// Compiles to raw String/Double -- zero object allocation
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Sequences vs Lists
|
|
359
|
+
```kotlin
|
|
360
|
+
// Bad: 3 intermediate lists
|
|
361
|
+
users.filter { it.isActive }.map { it.name }.take(10)
|
|
362
|
+
// Good: lazy, single pass, stops after 10
|
|
363
|
+
users.asSequence().filter { it.isActive }.map { it.name }.take(10).toList()
|
|
364
|
+
// Rule: sequences for 3+ chained ops on 1000+ elements
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Coroutines
|
|
368
|
+
```kotlin
|
|
369
|
+
suspend fun loadDashboard(userId: String): Dashboard = coroutineScope {
|
|
370
|
+
val profile = async { userService.getProfile(userId) }
|
|
371
|
+
val orders = async { orderService.getRecent(userId) }
|
|
372
|
+
val notifs = async { notificationService.getUnread(userId) }
|
|
373
|
+
Dashboard(profile.await(), orders.await(), notifs.await())
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
fun observeOrders(): Flow<Order> =
|
|
377
|
+
orderRepository.observe().map { it.toDomain() }.catch { emit(Order.EMPTY) }.flowOn(Dispatchers.IO)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Avoid Reflection
|
|
381
|
+
```kotlin
|
|
382
|
+
// Bad: Gson uses reflection -- slow, no compile-time safety
|
|
383
|
+
val json = Gson().toJson(user)
|
|
384
|
+
// Good: kotlinx.serialization uses compile-time codegen
|
|
385
|
+
@Serializable data class User(val id: String, val name: String)
|
|
386
|
+
val json = Json.encodeToString(user)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Build / Package Management
|
|
392
|
+
|
|
393
|
+
### Version Catalog (gradle/libs.versions.toml)
|
|
394
|
+
```toml
|
|
395
|
+
[versions]
|
|
396
|
+
kotlin = "2.1.0"
|
|
397
|
+
coroutines = "1.10.1"
|
|
398
|
+
ktor = "3.0.3"
|
|
399
|
+
kotest = "5.9.1"
|
|
400
|
+
mockk = "1.13.14"
|
|
401
|
+
|
|
402
|
+
[libraries]
|
|
403
|
+
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
|
404
|
+
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.7.3" }
|
|
405
|
+
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
|
|
406
|
+
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
|
407
|
+
kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
|
|
408
|
+
kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
|
|
409
|
+
|
|
410
|
+
[bundles]
|
|
411
|
+
testing = ["mockk", "kotest-runner", "kotest-assertions"]
|
|
412
|
+
|
|
413
|
+
[plugins]
|
|
414
|
+
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
|
415
|
+
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Kotlin Multiplatform
|
|
419
|
+
```kotlin
|
|
420
|
+
kotlin {
|
|
421
|
+
jvm(); iosArm64(); iosSimulatorArm64(); js(IR) { browser() }
|
|
422
|
+
sourceSets {
|
|
423
|
+
commonMain.dependencies {
|
|
424
|
+
implementation(libs.kotlinx.coroutines.core)
|
|
425
|
+
implementation(libs.kotlinx.serialization.json)
|
|
426
|
+
}
|
|
427
|
+
commonTest.dependencies { implementation(kotlin("test")) }
|
|
428
|
+
jvmMain.dependencies { implementation(libs.ktor.server.core) }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Anti-Patterns to Avoid
|
|
436
|
+
|
|
437
|
+
| Anti-Pattern | Problem | Solution |
|
|
438
|
+
|--------------|---------|----------|
|
|
439
|
+
| `!!` everywhere | NPE at runtime | Safe calls, elvis, `requireNotNull` |
|
|
440
|
+
| `var` by default | Mutability bugs | Default to `val` |
|
|
441
|
+
| Catching `Exception` | Swallows `CancellationException` | Catch specific types |
|
|
442
|
+
| Mutable data class props | Breaks hashCode/equals | Use `val` in data classes |
|
|
443
|
+
| Stringly-typed IDs | Mix up userId/orderId | Value classes |
|
|
444
|
+
| Blocking in coroutine | Thread starvation | `withContext(Dispatchers.IO)` |
|
|
445
|
+
| Ignoring `Result` failures | Silent errors | Always handle both paths |
|
|
446
|
+
| God object / util class | No cohesion | Extension functions |
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lint
|
|
3
|
+
description: "Lint code with auto-detected tools and fix suggestions"
|
|
4
|
+
effort: low
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
argument-hint: "[path]"
|
|
7
|
+
allowed-tools: Bash, Read
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Lint Runner
|
|
11
|
+
|
|
12
|
+
$ARGUMENTS
|
|
13
|
+
|
|
14
|
+
Run linting and type checking based on detected project type.
|
|
15
|
+
|
|
16
|
+
## Project context
|
|
17
|
+
|
|
18
|
+
- Project files: !`ls pyproject.toml package.json composer.json pubspec.yaml go.mod 2>/dev/null`
|
|
19
|
+
|
|
20
|
+
## Auto-Detection
|
|
21
|
+
|
|
22
|
+
Run the bundled script to detect available linters:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python3 ${CLAUDE_SKILL_DIR}/scripts/detect-linters.py .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/lint [path]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands by Project Type
|
|
35
|
+
|
|
36
|
+
| Project Type | Lint Command | Type Check |
|
|
37
|
+
|--------------|--------------|------------|
|
|
38
|
+
| **Python** | `ruff check .` | `mypy .` |
|
|
39
|
+
| **TypeScript/Node** | `npx eslint .` | `npx tsc --noEmit` |
|
|
40
|
+
| **PHP** | `./vendor/bin/phpstan analyse` | - |
|
|
41
|
+
| **Go** | `golangci-lint run` | - |
|
|
42
|
+
| **Rust** | `cargo clippy` | - |
|
|
43
|
+
| **Flutter/Dart** | `dart analyze` | - |
|
|
44
|
+
|
|
45
|
+
## Python Projects
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Linting
|
|
49
|
+
ruff check .
|
|
50
|
+
|
|
51
|
+
# Type checking
|
|
52
|
+
mypy .
|
|
53
|
+
|
|
54
|
+
# Auto-fix
|
|
55
|
+
ruff check --fix .
|
|
56
|
+
ruff format .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## TypeScript/Node Projects
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Linting
|
|
63
|
+
npx eslint .
|
|
64
|
+
|
|
65
|
+
# Type checking
|
|
66
|
+
npx tsc --noEmit
|
|
67
|
+
|
|
68
|
+
# Auto-fix
|
|
69
|
+
npx eslint --fix .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## PHP Projects
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Static analysis
|
|
76
|
+
./vendor/bin/phpstan analyse
|
|
77
|
+
|
|
78
|
+
# Code style
|
|
79
|
+
./vendor/bin/phpcs
|
|
80
|
+
./vendor/bin/phpcbf # auto-fix
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Docker Execution (if applicable)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Generic pattern - replace {container} with your app container
|
|
87
|
+
docker exec {container} make lint
|
|
88
|
+
docker exec {container} make typecheck
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Quality Gates
|
|
92
|
+
|
|
93
|
+
- Linting: 0 errors
|
|
94
|
+
- Type checking: 0 errors (for new code)
|
|
95
|
+
|
|
96
|
+
## Common Issues
|
|
97
|
+
|
|
98
|
+
| Error | Fix |
|
|
99
|
+
|-------|-----|
|
|
100
|
+
| Missing type hints | Add type annotations |
|
|
101
|
+
| Unused imports | Remove or use `# noqa: F401` |
|
|
102
|
+
| Line too long | Break line or disable for that line |
|
|
103
|
+
| Import order | Let linter fix with `--fix` |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Detect available linters and build combined lint/format commands for a project.
|
|
3
|
+
|
|
4
|
+
Scans a project directory for Python, Node.js, PHP, Go, and Dart
|
|
5
|
+
configuration files, checks which linters are installed or configured,
|
|
6
|
+
and returns a JSON object with individual linter entries, a combined
|
|
7
|
+
check command, and available format commands.
|
|
8
|
+
|
|
9
|
+
Usage::
|
|
10
|
+
|
|
11
|
+
python3 detect-linters.py [project_directory]
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
20
|
+
from _lib.detect_utils import is_installed, read_json, read_text, run_detector
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def detect(project_dir: Path) -> dict[str, Any]:
|
|
24
|
+
"""Detect linters configured or installed for the project."""
|
|
25
|
+
linters: list[dict[str, Any]] = []
|
|
26
|
+
format_commands: list[str] = []
|
|
27
|
+
|
|
28
|
+
# Python: pyproject.toml
|
|
29
|
+
pyproject = read_text(project_dir / "pyproject.toml")
|
|
30
|
+
if pyproject or (project_dir / "setup.py").exists():
|
|
31
|
+
py_linters: dict[str, dict[str, str | None]] = {
|
|
32
|
+
"ruff": {"check": "ruff check .", "fix": "ruff check --fix .", "format": "ruff format ."},
|
|
33
|
+
"flake8": {"check": "flake8 .", "fix": None, "format": None},
|
|
34
|
+
"pylint": {"check": "pylint src/", "fix": None, "format": None},
|
|
35
|
+
"mypy": {"check": "mypy .", "fix": None, "format": None},
|
|
36
|
+
"black": {"check": "black --check .", "fix": None, "format": "black ."},
|
|
37
|
+
}
|
|
38
|
+
for name, cmds in py_linters.items():
|
|
39
|
+
configured = pyproject and name in pyproject if pyproject else False
|
|
40
|
+
installed = is_installed(name)
|
|
41
|
+
if configured or installed:
|
|
42
|
+
linters.append({"name": name, "language": "python", "installed": installed,
|
|
43
|
+
"configured": configured, "check_command": cmds["check"],
|
|
44
|
+
"fix_command": cmds["fix"]})
|
|
45
|
+
if cmds.get("format"):
|
|
46
|
+
format_commands.append(cmds["format"]) # type: ignore[arg-type]
|
|
47
|
+
|
|
48
|
+
# Node.js: package.json
|
|
49
|
+
pkg = read_json(project_dir / "package.json")
|
|
50
|
+
if pkg:
|
|
51
|
+
dev_deps = {**pkg.get("devDependencies", {}), **pkg.get("dependencies", {})}
|
|
52
|
+
node_linters: dict[str, dict[str, str | None]] = {
|
|
53
|
+
"eslint": {"check": "npx eslint .", "fix": "npx eslint --fix ."},
|
|
54
|
+
"prettier": {"check": "npx prettier --check .", "format": "npx prettier --write ."},
|
|
55
|
+
"tsc": {"check": "npx tsc --noEmit", "fix": None},
|
|
56
|
+
}
|
|
57
|
+
for name, cmds in node_linters.items():
|
|
58
|
+
in_deps = name in dev_deps or (name == "tsc" and "typescript" in dev_deps)
|
|
59
|
+
installed = is_installed(f"npx {name}") if name != "tsc" else is_installed("npx tsc")
|
|
60
|
+
if in_deps:
|
|
61
|
+
linters.append({"name": name, "language": "javascript/typescript",
|
|
62
|
+
"installed": True, "configured": in_deps,
|
|
63
|
+
"check_command": cmds["check"], "fix_command": cmds.get("fix")})
|
|
64
|
+
if cmds.get("format"):
|
|
65
|
+
format_commands.append(cmds["format"]) # type: ignore[arg-type]
|
|
66
|
+
|
|
67
|
+
# PHP: composer.json
|
|
68
|
+
composer = read_json(project_dir / "composer.json")
|
|
69
|
+
if composer:
|
|
70
|
+
require_dev: dict[str, str] = composer.get("require-dev", {})
|
|
71
|
+
php_linters: dict[str, dict[str, str]] = {
|
|
72
|
+
"phpstan": {"check": "./vendor/bin/phpstan analyse", "pkg": "phpstan/phpstan"},
|
|
73
|
+
"psalm": {"check": "./vendor/bin/psalm", "pkg": "vimeo/psalm"},
|
|
74
|
+
"phpcs": {"check": "./vendor/bin/phpcs", "pkg": "squizlabs/php_codesniffer"},
|
|
75
|
+
}
|
|
76
|
+
for name, info in php_linters.items():
|
|
77
|
+
in_deps = any(info["pkg"] in k for k in require_dev)
|
|
78
|
+
if in_deps:
|
|
79
|
+
linters.append({"name": name, "language": "php", "installed": True,
|
|
80
|
+
"configured": True, "check_command": info["check"], "fix_command": None})
|
|
81
|
+
|
|
82
|
+
# Go: .golangci.yml
|
|
83
|
+
if (project_dir / "go.mod").exists():
|
|
84
|
+
go_lint: dict[str, str | None] = {"check": "golangci-lint run", "fix": None}
|
|
85
|
+
configured = (project_dir / ".golangci.yml").exists() or (project_dir / ".golangci.yaml").exists()
|
|
86
|
+
installed = is_installed("golangci-lint")
|
|
87
|
+
if configured or installed:
|
|
88
|
+
linters.append({"name": "golangci-lint", "language": "go", "installed": installed,
|
|
89
|
+
"configured": configured, "check_command": go_lint["check"], "fix_command": None})
|
|
90
|
+
linters.append({"name": "go-vet", "language": "go", "installed": True,
|
|
91
|
+
"configured": True, "check_command": "go vet ./...", "fix_command": None})
|
|
92
|
+
|
|
93
|
+
# Dart: pubspec.yaml
|
|
94
|
+
if (project_dir / "pubspec.yaml").exists():
|
|
95
|
+
linters.append({"name": "dart-analyze", "language": "dart", "installed": is_installed("dart"),
|
|
96
|
+
"configured": True, "check_command": "dart analyze", "fix_command": "dart fix --apply"})
|
|
97
|
+
|
|
98
|
+
# Build combined command
|
|
99
|
+
check_parts = [l["check_command"] for l in linters if l.get("installed") and l.get("check_command")]
|
|
100
|
+
combined = " && ".join(check_parts) if check_parts else None
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"linters": linters,
|
|
104
|
+
"combined_command": combined,
|
|
105
|
+
"format_commands": format_commands,
|
|
106
|
+
"linter_count": len(linters),
|
|
107
|
+
"installed_count": sum(1 for l in linters if l.get("installed")),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
run_detector(detect)
|