claude-code-superkit 0.0.0-stage → 1.5.3
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/LICENSE +21 -0
- package/README.md +374 -2
- package/bin/cli.js +153 -0
- package/bin/inject-tokens.js +224 -0
- package/bin/measure-tokens.js +17 -0
- package/bin/superkit-counts-verify.sh +30 -0
- package/lib/codex.js +246 -0
- package/lib/docs-scaffold.js +147 -0
- package/lib/installer.js +499 -0
- package/lib/prompts.js +51 -0
- package/lib/settings-builder.js +51 -0
- package/lib/superpowers.js +99 -0
- package/lib/utils.js +117 -0
- package/lib/validator.js +84 -0
- package/package.json +49 -4
- package/packages/codex/AGENTS.md +396 -0
- package/packages/codex/INSTALL.md +342 -0
- package/packages/codex/config.toml +22 -0
- package/packages/codex/hooks/superkit.mjs +203 -0
- package/packages/codex/hooks.json +30 -0
- package/packages/codex/legacy-baseline.json +88 -0
- package/packages/codex/native-skills.txt +65 -0
- package/packages/codex/rules/default.rules +565 -0
- package/packages/codex/skills/ai-slop-cleaner/SKILL.md +44 -0
- package/packages/codex/skills/api-contract-sync/SKILL.md +96 -0
- package/packages/codex/skills/architect/SKILL.md +49 -0
- package/packages/codex/skills/audit-backend/SKILL.md +80 -0
- package/packages/codex/skills/audit-frontend/SKILL.md +82 -0
- package/packages/codex/skills/audit-infra/SKILL.md +87 -0
- package/packages/codex/skills/audit-orchestrator/SKILL.md +114 -0
- package/packages/codex/skills/behavioral-nudge-engine/SKILL.md +110 -0
- package/packages/codex/skills/benchmark/SKILL.md +49 -0
- package/packages/codex/skills/bot-reviewer/SKILL.md +94 -0
- package/packages/codex/skills/code-reviewer/SKILL.md +67 -0
- package/packages/codex/skills/codebase-onboarding-engineer/SKILL.md +46 -0
- package/packages/codex/skills/comment-rot-analyzer/SKILL.md +75 -0
- package/packages/codex/skills/commit-helper/SKILL.md +101 -0
- package/packages/codex/skills/critic/SKILL.md +56 -0
- package/packages/codex/skills/database-reviewer/SKILL.md +79 -0
- package/packages/codex/skills/debug-observer/SKILL.md +107 -0
- package/packages/codex/skills/dependency-checker/SKILL.md +69 -0
- package/packages/codex/skills/design-system-reviewer/SKILL.md +121 -0
- package/packages/codex/skills/dev-orchestrator/SKILL.md +304 -0
- package/packages/codex/skills/docs-reviewer/SKILL.md +86 -0
- package/packages/codex/skills/drizzle-orm-expert/SKILL.md +246 -0
- package/packages/codex/skills/e2e-test-generator/SKILL.md +95 -0
- package/packages/codex/skills/evaluator/SKILL.md +63 -0
- package/packages/codex/skills/frontend-perf-reviewer/SKILL.md +95 -0
- package/packages/codex/skills/frontend-ui-color-reviewer/SKILL.md +68 -0
- package/packages/codex/skills/frontend-ui-design-critic/SKILL.md +99 -0
- package/packages/codex/skills/frontend-ui-interaction-reviewer/SKILL.md +102 -0
- package/packages/codex/skills/frontend-ui-motion-reviewer/SKILL.md +85 -0
- package/packages/codex/skills/frontend-ui-reviewer/SKILL.md +81 -0
- package/packages/codex/skills/frontend-ui-typography-reviewer/SKILL.md +62 -0
- package/packages/codex/skills/gltf-debugging/SKILL.md +128 -0
- package/packages/codex/skills/go-benchmark/SKILL.md +217 -0
- package/packages/codex/skills/go-concurrency-reviewer/SKILL.md +113 -0
- package/packages/codex/skills/go-error-reviewer/SKILL.md +107 -0
- package/packages/codex/skills/go-grpc-patterns/SKILL.md +285 -0
- package/packages/codex/skills/go-modernizer/SKILL.md +111 -0
- package/packages/codex/skills/go-observability-reviewer/SKILL.md +121 -0
- package/packages/codex/skills/go-performance-reviewer/SKILL.md +112 -0
- package/packages/codex/skills/go-reviewer/SKILL.md +109 -0
- package/packages/codex/skills/go-samber-do/SKILL.md +158 -0
- package/packages/codex/skills/go-samber-lo/SKILL.md +178 -0
- package/packages/codex/skills/go-samber-oops/SKILL.md +174 -0
- package/packages/codex/skills/goal-verifier/SKILL.md +53 -0
- package/packages/codex/skills/health-checker/SKILL.md +109 -0
- package/packages/codex/skills/html-to-3d-texture/SKILL.md +153 -0
- package/packages/codex/skills/impeccable-craft/SKILL.md +101 -0
- package/packages/codex/skills/lint-runner/SKILL.md +85 -0
- package/packages/codex/skills/migrate/SKILL.md +60 -0
- package/packages/codex/skills/migration-reviewer/SKILL.md +88 -0
- package/packages/codex/skills/minimal-change-engineer/SKILL.md +44 -0
- package/packages/codex/skills/new-migration/SKILL.md +59 -0
- package/packages/codex/skills/nextjs-supabase-auth/SKILL.md +227 -0
- package/packages/codex/skills/output-enforcement/SKILL.md +71 -0
- package/packages/codex/skills/plan-checker/SKILL.md +53 -0
- package/packages/codex/skills/postgresql-optimization/SKILL.md +159 -0
- package/packages/codex/skills/pre-deploy-validator/SKILL.md +111 -0
- package/packages/codex/skills/presentation-reviewer/SKILL.md +88 -0
- package/packages/codex/skills/product-3d-lighting/SKILL.md +137 -0
- package/packages/codex/skills/project-architecture/SKILL.md +52 -0
- package/packages/codex/skills/py-reviewer/SKILL.md +100 -0
- package/packages/codex/skills/r3f-scene-reviewer/SKILL.md +88 -0
- package/packages/codex/skills/r3f-scroll-driven-3d/SKILL.md +162 -0
- package/packages/codex/skills/reality-checker/SKILL.md +61 -0
- package/packages/codex/skills/redis-patterns/SKILL.md +260 -0
- package/packages/codex/skills/review-orchestrator/SKILL.md +151 -0
- package/packages/codex/skills/rs-reviewer/SKILL.md +98 -0
- package/packages/codex/skills/ru-text/SKILL.md +132 -0
- package/packages/codex/skills/scaffold-endpoint/SKILL.md +44 -0
- package/packages/codex/skills/security-scanner/SKILL.md +94 -0
- package/packages/codex/skills/silent-failure-hunter/SKILL.md +176 -0
- package/packages/codex/skills/telegram-bot-builder/SKILL.md +120 -0
- package/packages/codex/skills/test-generator/SKILL.md +79 -0
- package/packages/codex/skills/test-runner/SKILL.md +101 -0
- package/packages/codex/skills/threejs-color-management/SKILL.md +97 -0
- package/packages/codex/skills/tree-generator/SKILL.md +63 -0
- package/packages/codex/skills/ts-reviewer/SKILL.md +90 -0
- package/packages/codex/skills/ui-design-reviewer/SKILL.md +89 -0
- package/packages/codex/skills/ui-reviewer/SKILL.md +103 -0
- package/packages/codex/skills/visual-reviewer/SKILL.md +85 -0
- package/packages/codex/skills/writing-agents/SKILL.md +165 -0
- package/packages/codex/skills/writing-commands/SKILL.md +173 -0
- package/packages/core/CLAUDE.md +118 -0
- package/packages/core/INTERNAL-FILES +16 -0
- package/packages/core/agents/ai-slop-cleaner.md +187 -0
- package/packages/core/agents/api-contract-sync.md +127 -0
- package/packages/core/agents/architect.md +205 -0
- package/packages/core/agents/audit-backend.md +137 -0
- package/packages/core/agents/audit-frontend.md +129 -0
- package/packages/core/agents/audit-infra.md +182 -0
- package/packages/core/agents/behavioral-nudge-engine.md +170 -0
- package/packages/core/agents/code-reviewer.md +195 -0
- package/packages/core/agents/codebase-onboarding-engineer.md +172 -0
- package/packages/core/agents/comment-rot-analyzer.md +115 -0
- package/packages/core/agents/critic.md +172 -0
- package/packages/core/agents/database-reviewer.md +201 -0
- package/packages/core/agents/debug-observer.md +261 -0
- package/packages/core/agents/dependency-checker.md +199 -0
- package/packages/core/agents/docs-reviewer.md +206 -0
- package/packages/core/agents/e2e-test-generator.md +261 -0
- package/packages/core/agents/evaluator.md +141 -0
- package/packages/core/agents/goal-verifier.md +130 -0
- package/packages/core/agents/health-checker.md +194 -0
- package/packages/core/agents/migration-reviewer.md +161 -0
- package/packages/core/agents/minimal-change-engineer.md +219 -0
- package/packages/core/agents/plan-checker.md +139 -0
- package/packages/core/agents/pre-deploy-validator.md +189 -0
- package/packages/core/agents/reality-checker.md +175 -0
- package/packages/core/agents/scaffold-endpoint.md +162 -0
- package/packages/core/agents/security-scanner.md +236 -0
- package/packages/core/agents/silent-failure-hunter.md +276 -0
- package/packages/core/agents/test-generator.md +146 -0
- package/packages/core/agents/tree-generator.md +141 -0
- package/packages/core/agents/ui-reviewer.md +172 -0
- package/packages/core/agents/visual-reviewer.md +211 -0
- package/packages/core/commands/audit.md +166 -0
- package/packages/core/commands/benchmark.md +130 -0
- package/packages/core/commands/commit.md +148 -0
- package/packages/core/commands/dev.md +302 -0
- package/packages/core/commands/docs-init.md +75 -0
- package/packages/core/commands/lint.md +137 -0
- package/packages/core/commands/migrate.md +146 -0
- package/packages/core/commands/new-migration.md +182 -0
- package/packages/core/commands/pair.md +209 -0
- package/packages/core/commands/review.md +195 -0
- package/packages/core/commands/security-scan.md +142 -0
- package/packages/core/commands/superkit-evolve.md +231 -0
- package/packages/core/commands/superkit-init.md +274 -0
- package/packages/core/commands/test.md +108 -0
- package/packages/core/commands/workflow.md +150 -0
- package/packages/core/docs/guide/01-getting-started.md +41 -0
- package/packages/core/docs-templates/README.md +33 -0
- package/packages/core/docs-templates/adr-template.md +62 -0
- package/packages/core/docs-templates/architecture/api-contracts.md +26 -0
- package/packages/core/docs-templates/architecture/api-reference.md +48 -0
- package/packages/core/docs-templates/architecture/auth-and-sessions.md +38 -0
- package/packages/core/docs-templates/architecture/backend-layers.md +42 -0
- package/packages/core/docs-templates/architecture/data-flow.md +26 -0
- package/packages/core/docs-templates/architecture/database-schema.md +51 -0
- package/packages/core/docs-templates/architecture/deployment.md +41 -0
- package/packages/core/docs-templates/architecture/frontend-state.md +46 -0
- package/packages/core/helpers/statusline.cjs +370 -0
- package/packages/core/hooks/CHAIN-SEMANTICS.md +84 -0
- package/packages/core/hooks/audit-settings-source.sh +98 -0
- package/packages/core/hooks/audit-trail.sh +120 -0
- package/packages/core/hooks/block-dangerous-git.sh +189 -0
- package/packages/core/hooks/bundle-import-check.sh +64 -0
- package/packages/core/hooks/compact-state-inject.sh +84 -0
- package/packages/core/hooks/config-protection.sh +68 -0
- package/packages/core/hooks/console-log-warning.sh +28 -0
- package/packages/core/hooks/context-monitor.sh +35 -0
- package/packages/core/hooks/dev-edit-counter.sh +59 -0
- package/packages/core/hooks/dev-marker-set.sh +55 -0
- package/packages/core/hooks/dev-required-on-commit.sh +426 -0
- package/packages/core/hooks/doc-check-on-commit.sh +396 -0
- package/packages/core/hooks/edit-streak-check.sh +63 -0
- package/packages/core/hooks/evolve-check.sh +78 -0
- package/packages/core/hooks/gateguard-pre-edit.sh +154 -0
- package/packages/core/hooks/gateguard-record-facts.sh +37 -0
- package/packages/core/hooks/intake-classifier.py +449 -0
- package/packages/core/hooks/lib/profile.sh +141 -0
- package/packages/core/hooks/loop-guard.sh +75 -0
- package/packages/core/hooks/migration-safety.sh +56 -0
- package/packages/core/hooks/plan-completion-gate.sh +61 -0
- package/packages/core/hooks/pre-compact-save.sh +89 -0
- package/packages/core/hooks/security-patterns.sh +119 -0
- package/packages/core/hooks/session-context-restore.sh +76 -0
- package/packages/core/hooks/subagent-stop-validate.sh +73 -0
- package/packages/core/hooks/superkit-counts-verify.sh +346 -0
- package/packages/core/hooks/superkit-update.sh +367 -0
- package/packages/core/hooks/tests/README.md +42 -0
- package/packages/core/hooks/tests/audit-trail-chain_test.sh +62 -0
- package/packages/core/hooks/tests/block-dangerous-git_test.sh +144 -0
- package/packages/core/hooks/tests/chain-semantics/h1.sh +11 -0
- package/packages/core/hooks/tests/chain-semantics/h2.sh +9 -0
- package/packages/core/hooks/tests/chain-semantics/h3.sh +11 -0
- package/packages/core/hooks/tests/chain-semantics/run-experiment.sh +65 -0
- package/packages/core/hooks/tests/chain-semantics/settings-experiment.json +15 -0
- package/packages/core/hooks/tests/counts-verify_test.sh +118 -0
- package/packages/core/hooks/tests/dev-required-on-commit_test.sh +207 -0
- package/packages/core/hooks/tests/dev-required-wip-on-main_test.sh +20 -0
- package/packages/core/hooks/tests/doc-check-on-commit_test.sh +122 -0
- package/packages/core/hooks/tests/edit-streak-check_test.sh +106 -0
- package/packages/core/hooks/tests/internal-manifest_test.sh +130 -0
- package/packages/core/hooks/tests/json-path_test.sh +108 -0
- package/packages/core/hooks/tests/mirror-invariants_test.sh +139 -0
- package/packages/core/hooks/tests/nudge-throttle_test.sh +95 -0
- package/packages/core/hooks/tests/profile-helper_test.sh +21 -0
- package/packages/core/hooks/tests/run-all.sh +47 -0
- package/packages/core/hooks/tests/session-key-helper_test.sh +17 -0
- package/packages/core/hooks/tests/superkit-update_test.sh +311 -0
- package/packages/core/hooks/tests/warn-visibility_test.sh +59 -0
- package/packages/core/hooks/user-intent-detect.sh +44 -0
- package/packages/core/hooks/verify-hooks.sh +69 -0
- package/packages/core/rules/auto-commands.md +124 -0
- package/packages/core/rules/coding-style.md +68 -0
- package/packages/core/rules/dev-workflow.md +77 -0
- package/packages/core/rules/documentation.md +106 -0
- package/packages/core/rules/frontend-aesthetics.md +53 -0
- package/packages/core/rules/git-workflow.md +14 -0
- package/packages/core/rules/security.md +29 -0
- package/packages/core/rules/superkit-integrity.md +114 -0
- package/packages/core/settings.json +229 -0
- package/packages/core/skills/drizzle-orm-expert/SKILL.md +260 -0
- package/packages/core/skills/nextjs-supabase-auth/SKILL.md +259 -0
- package/packages/core/skills/postgresql-optimization/SKILL.md +150 -0
- package/packages/core/skills/project-architecture/SKILL.md +31 -0
- package/packages/core/skills/project-scanner/SKILL.md +132 -0
- package/packages/core/skills/redis-patterns/SKILL.md +261 -0
- package/packages/core/skills/ru-text/SKILL.md +156 -0
- package/packages/core/skills/telegram-bot-builder/SKILL.md +145 -0
- package/packages/core/skills/writing-agents/SKILL.md +220 -0
- package/packages/core/skills/writing-commands/SKILL.md +176 -0
- package/packages/core/skills/writing-hooks/SKILL.md +335 -0
- package/packages/extras/bot-reviewer.md +279 -0
- package/packages/extras/design-system-reviewer.md +234 -0
- package/packages/extras/red-blue-auditor/README.md +117 -0
- package/packages/extras/red-blue-auditor/agent.md +128 -0
- package/packages/extras/red-blue-auditor/patterns/agent-config.txt +4 -0
- package/packages/extras/red-blue-auditor/patterns/hook-injection.txt +8 -0
- package/packages/extras/red-blue-auditor/patterns/mcp-risk.txt +5 -0
- package/packages/extras/red-blue-auditor/patterns/permission-abuse.txt +6 -0
- package/packages/extras/red-blue-auditor/patterns/secrets.txt +17 -0
- package/packages/extras/red-blue-auditor/scan.sh +88 -0
- package/packages/extras/skillsmp-search/SKILL.md +134 -0
- package/packages/frontend-3d/README.md +62 -0
- package/packages/frontend-3d/agents/frontend-perf-reviewer.md +164 -0
- package/packages/frontend-3d/agents/presentation-reviewer.md +146 -0
- package/packages/frontend-3d/agents/r3f-scene-reviewer.md +128 -0
- package/packages/frontend-3d/agents/ui-design-reviewer.md +169 -0
- package/packages/frontend-3d/commands/capture-screen.md +166 -0
- package/packages/frontend-3d/hooks/bundle-size-warn.sh +93 -0
- package/packages/frontend-3d/hooks/gsap-pattern-check.sh +74 -0
- package/packages/frontend-3d/hooks/r3f-color-check.sh +76 -0
- package/packages/frontend-3d/hooks/tailwind-version-guard.sh +98 -0
- package/packages/frontend-3d/rules/frontend-aesthetics-3d.md +81 -0
- package/packages/frontend-3d/rules/gsap-conventions.md +129 -0
- package/packages/frontend-3d/rules/threejs-conventions.md +116 -0
- package/packages/frontend-3d/skills/gltf-debugging/SKILL.md +145 -0
- package/packages/frontend-3d/skills/html-to-3d-texture/SKILL.md +183 -0
- package/packages/frontend-3d/skills/output-enforcement/SKILL.md +79 -0
- package/packages/frontend-3d/skills/product-3d-lighting/SKILL.md +166 -0
- package/packages/frontend-3d/skills/r3f-scroll-driven-3d/SKILL.md +209 -0
- package/packages/frontend-3d/skills/threejs-color-management/SKILL.md +111 -0
- package/packages/frontend-ui/NOTICE.md +99 -0
- package/packages/frontend-ui/README.md +59 -0
- package/packages/frontend-ui/agents/ui-color-reviewer.md +174 -0
- package/packages/frontend-ui/agents/ui-design-critic.md +198 -0
- package/packages/frontend-ui/agents/ui-interaction-reviewer.md +259 -0
- package/packages/frontend-ui/agents/ui-motion-reviewer.md +266 -0
- package/packages/frontend-ui/agents/ui-reviewer.md +251 -0
- package/packages/frontend-ui/agents/ui-typography-reviewer.md +222 -0
- package/packages/frontend-ui/hooks/tests/ui-animation-easing-check_test.sh +78 -0
- package/packages/frontend-ui/hooks/tests/ui-banned-fonts-check_test.sh +77 -0
- package/packages/frontend-ui/hooks/tests/ui-color-check_test.sh +75 -0
- package/packages/frontend-ui/hooks/ui-animation-easing-check.sh +86 -0
- package/packages/frontend-ui/hooks/ui-banned-fonts-check.sh +90 -0
- package/packages/frontend-ui/hooks/ui-color-check.sh +102 -0
- package/packages/frontend-ui/rules/color-and-contrast.md +230 -0
- package/packages/frontend-ui/rules/frontend-design-aesthetics.md +124 -0
- package/packages/frontend-ui/rules/interaction-polish.md +335 -0
- package/packages/frontend-ui/rules/motion-and-animation.md +283 -0
- package/packages/frontend-ui/rules/spatial-and-layout.md +212 -0
- package/packages/frontend-ui/rules/typography-guidelines.md +185 -0
- package/packages/frontend-ui/rules/ui-anti-patterns.md +309 -0
- package/packages/frontend-ui/settings.json +24 -0
- package/packages/frontend-ui/skills/impeccable-craft/SKILL.md +187 -0
- package/packages/gan/README.md +232 -0
- package/packages/gan/agents/gan-evaluator.md +214 -0
- package/packages/gan/agents/gan-generator.md +171 -0
- package/packages/gan/agents/gan-planner.md +206 -0
- package/packages/gan/rubrics/functionality.md +66 -0
- package/packages/gan/rubrics/ui-quality.md +77 -0
- package/packages/gan/skills/gan-evaluator/SKILL.md +91 -0
- package/packages/gan/skills/gan-generator/SKILL.md +72 -0
- package/packages/gan/skills/gan-planner/SKILL.md +93 -0
- package/packages/stack-agents/go/go-concurrency-reviewer.md +210 -0
- package/packages/stack-agents/go/go-error-reviewer.md +187 -0
- package/packages/stack-agents/go/go-modernizer.md +160 -0
- package/packages/stack-agents/go/go-observability-reviewer.md +185 -0
- package/packages/stack-agents/go/go-performance-reviewer.md +160 -0
- package/packages/stack-agents/go/go-reviewer.md +277 -0
- package/packages/stack-agents/go/references/benchmark-methodology.md +280 -0
- package/packages/stack-agents/go/references/channel-patterns.md +384 -0
- package/packages/stack-agents/go/references/cli-cobra-viper.md +190 -0
- package/packages/stack-agents/go/references/code-style.md +262 -0
- package/packages/stack-agents/go/references/context-propagation.md +317 -0
- package/packages/stack-agents/go/references/data-structures.md +315 -0
- package/packages/stack-agents/go/references/database-patterns.md +334 -0
- package/packages/stack-agents/go/references/design-patterns.md +411 -0
- package/packages/stack-agents/go/references/di-frameworks.md +146 -0
- package/packages/stack-agents/go/references/error-creation.md +251 -0
- package/packages/stack-agents/go/references/error-inspection.md +348 -0
- package/packages/stack-agents/go/references/error-wrapping.md +230 -0
- package/packages/stack-agents/go/references/gopls-driving.md +62 -0
- package/packages/stack-agents/go/references/goroutine-lifecycle.md +351 -0
- package/packages/stack-agents/go/references/graphql-patterns.md +221 -0
- package/packages/stack-agents/go/references/grpc-patterns.md +373 -0
- package/packages/stack-agents/go/references/modernize-guide.md +522 -0
- package/packages/stack-agents/go/references/module-management.md +191 -0
- package/packages/stack-agents/go/references/naming-conventions.md +176 -0
- package/packages/stack-agents/go/references/observability-pipeline.md +352 -0
- package/packages/stack-agents/go/references/performance-profiling.md +250 -0
- package/packages/stack-agents/go/references/refactoring-mechanics.md +127 -0
- package/packages/stack-agents/go/references/rest-openapi-patterns.md +160 -0
- package/packages/stack-agents/go/references/samber-do.md +201 -0
- package/packages/stack-agents/go/references/samber-libraries.md +363 -0
- package/packages/stack-agents/go/references/samber-lo.md +260 -0
- package/packages/stack-agents/go/references/samber-oops.md +200 -0
- package/packages/stack-agents/go/references/security-checklist.md +441 -0
- package/packages/stack-agents/go/references/standard-stdlib-now.md +181 -0
- package/packages/stack-agents/go/references/stay-updated.md +147 -0
- package/packages/stack-agents/go/references/structs-interfaces.md +312 -0
- package/packages/stack-agents/go/references/sync-primitives.md +395 -0
- package/packages/stack-agents/go/references/testing-patterns.md +483 -0
- package/packages/stack-agents/go/references/troubleshooting.md +98 -0
- package/packages/stack-agents/python/py-reviewer.md +194 -0
- package/packages/stack-agents/rust/rs-reviewer.md +188 -0
- package/packages/stack-agents/typescript/ts-reviewer.md +135 -0
- package/packages/stack-hooks/go/go-context-check-on-edit.sh +56 -0
- package/packages/stack-hooks/go/go-error-check-on-edit.sh +54 -0
- package/packages/stack-hooks/go/go-format-on-edit.sh +32 -0
- package/packages/stack-hooks/go/go-safety-check-on-edit.sh +54 -0
- package/packages/stack-hooks/go/go-vet-on-edit.sh +61 -0
- package/packages/stack-hooks/go/golangci-lint-on-edit.sh +54 -0
- package/packages/stack-hooks/python/ruff-on-edit.sh +67 -0
- package/packages/stack-hooks/rust/cargo-check-on-edit.sh +64 -0
- package/packages/stack-hooks/typescript/typecheck-on-edit.sh +76 -0
- package/packages/stack-rules/go/go-conventions.md +44 -0
- package/packages/stack-rules/go/go-safety.md +35 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Benchmark Methodology
|
|
2
|
+
|
|
3
|
+
> Reference document for go-reviewer, go-performance-reviewer. Loaded on demand via Read tool.
|
|
4
|
+
> Upstream: https://pkg.go.dev/testing#hdr-Benchmarks · https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
|
|
5
|
+
|
|
6
|
+
## Why Methodology Matters
|
|
7
|
+
|
|
8
|
+
A single benchmark run is a measurement, not an answer. CPUs, filesystems, and the Go scheduler introduce noise that dwarfs changes under ~10%. Without a repeatable methodology, "my change is 30% faster" often means "my last run happened to be fast."
|
|
9
|
+
|
|
10
|
+
Rules of the game:
|
|
11
|
+
|
|
12
|
+
1. Run every benchmark with `-count=6` (or more) — one run is noise.
|
|
13
|
+
2. Compare two versions with `benchstat` — it reports p-values, not just percentage deltas.
|
|
14
|
+
3. Quiet machine: close the browser, turn off other builds, plug in the laptop.
|
|
15
|
+
4. Use `-benchmem` always. Allocations are usually the thing that changed.
|
|
16
|
+
|
|
17
|
+
## `testing.B` Basics
|
|
18
|
+
|
|
19
|
+
```go
|
|
20
|
+
func BenchmarkUserLookup(b *testing.B) {
|
|
21
|
+
repo := newRepo(b)
|
|
22
|
+
|
|
23
|
+
b.ReportAllocs() // always; force B/op + allocs/op output
|
|
24
|
+
b.ResetTimer() // drop setup cost from the measurement
|
|
25
|
+
|
|
26
|
+
for i := 0; i < b.N; i++ {
|
|
27
|
+
_, err := repo.FindByID(context.Background(), 42)
|
|
28
|
+
if err != nil { b.Fatal(err) }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `b.N` — the framework adjusts this until the benchmark runs for `-benchtime` (default 1s).
|
|
34
|
+
- Write the body so that calling it `b.N` times is meaningful — don't do setup inside the loop.
|
|
35
|
+
- `b.Fatal` / `b.Fatalf` marks the whole benchmark as failed; don't swallow errors.
|
|
36
|
+
|
|
37
|
+
### Timing Control
|
|
38
|
+
|
|
39
|
+
```go
|
|
40
|
+
func BenchmarkWithSetup(b *testing.B) {
|
|
41
|
+
for i := 0; i < b.N; i++ {
|
|
42
|
+
data := buildHeavyInput(i) // setup cost we don't want to measure
|
|
43
|
+
b.StartTimer() // rare — default is running
|
|
44
|
+
_ = process(data)
|
|
45
|
+
b.StopTimer() // stop measuring, keep the loop going
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Prefer `b.ResetTimer` right before the loop and avoid per-iteration StopTimer/StartTimer — the per-call overhead skews results.
|
|
51
|
+
|
|
52
|
+
### Throughput Reporting
|
|
53
|
+
|
|
54
|
+
`b.SetBytes(n)` makes the framework report `MB/s`:
|
|
55
|
+
|
|
56
|
+
```go
|
|
57
|
+
func BenchmarkJSON(b *testing.B) {
|
|
58
|
+
payload := make([]byte, 4096)
|
|
59
|
+
b.SetBytes(int64(len(payload)))
|
|
60
|
+
b.ResetTimer()
|
|
61
|
+
for i := 0; i < b.N; i++ {
|
|
62
|
+
_ = json.Unmarshal(payload, &out)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Output: ... 250 MB/s
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use `SetBytes` for I/O, parsing, compression — anything where "X MB/s" is the natural unit.
|
|
69
|
+
|
|
70
|
+
## Sub-Benchmarks
|
|
71
|
+
|
|
72
|
+
`b.Run` for parameter sweeps — input sizes, algorithm variants:
|
|
73
|
+
|
|
74
|
+
```go
|
|
75
|
+
func BenchmarkSort(b *testing.B) {
|
|
76
|
+
for _, n := range []int{100, 1_000, 10_000, 100_000} {
|
|
77
|
+
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
|
|
78
|
+
data := randInts(n)
|
|
79
|
+
b.ResetTimer()
|
|
80
|
+
for i := 0; i < b.N; i++ {
|
|
81
|
+
cp := slices.Clone(data)
|
|
82
|
+
slices.Sort(cp)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Output:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
BenchmarkSort/n=100-8 500000 2643 ns/op
|
|
93
|
+
BenchmarkSort/n=1000-8 30000 38120 ns/op
|
|
94
|
+
BenchmarkSort/n=10000-8 2500 472900 ns/op
|
|
95
|
+
BenchmarkSort/n=100000-8 200 5.82e+06 ns/op
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use the `/` pattern in `-bench`: `go test -bench=BenchmarkSort/n=1000`.
|
|
99
|
+
|
|
100
|
+
## Running Benchmarks
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Basic — run every Benchmark*, capture memory, 6 samples
|
|
104
|
+
go test -bench=. -benchmem -count=6 ./...
|
|
105
|
+
|
|
106
|
+
# Focus a single benchmark, longer runtime for smaller-variance numbers
|
|
107
|
+
go test -bench=BenchmarkJSON -benchtime=3s -count=10
|
|
108
|
+
|
|
109
|
+
# Fixed iteration count (debugging flaky benchmarks)
|
|
110
|
+
go test -bench=. -benchtime=1000x
|
|
111
|
+
|
|
112
|
+
# CPU sweep — measure at 1/2/4/8 cores
|
|
113
|
+
go test -bench=. -cpu=1,2,4,8
|
|
114
|
+
|
|
115
|
+
# Alternate package
|
|
116
|
+
go test -bench=. ./pkg/cache/...
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Key flags:
|
|
120
|
+
|
|
121
|
+
| Flag | Purpose |
|
|
122
|
+
|------|---------|
|
|
123
|
+
| `-bench regex` | Which benchmarks to run (`.` = all) |
|
|
124
|
+
| `-benchmem` | Report `B/op` + `allocs/op` — always on |
|
|
125
|
+
| `-count N` | Run each benchmark N times (≥ 6 for stats) |
|
|
126
|
+
| `-benchtime Xs` or `-benchtime Nx` | Target duration OR fixed iterations |
|
|
127
|
+
| `-cpu 1,2,4` | Set `GOMAXPROCS` for each run — finds contention |
|
|
128
|
+
| `-cpuprofile cpu.prof` | Capture CPU profile |
|
|
129
|
+
| `-memprofile mem.prof` | Capture heap profile |
|
|
130
|
+
| `-trace trace.out` | Execution tracer output |
|
|
131
|
+
| `-run ^$` | Skip tests (tests run by default alongside benchmarks) |
|
|
132
|
+
|
|
133
|
+
## benchstat Workflow
|
|
134
|
+
|
|
135
|
+
Install: `go install golang.org/x/perf/cmd/benchstat@latest`.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Baseline — your current branch
|
|
139
|
+
go test -run ^$ -bench=BenchmarkProcess -benchmem -count=10 > old.txt
|
|
140
|
+
|
|
141
|
+
# Apply your change, then:
|
|
142
|
+
go test -run ^$ -bench=BenchmarkProcess -benchmem -count=10 > new.txt
|
|
143
|
+
|
|
144
|
+
benchstat old.txt new.txt
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Capture the two passes serially on the same quiet machine.** Run the `old.txt` pass, then the `new.txt` pass — never concurrently, and never on different hardware. Concurrent captures reintroduce the CPU/cache/scheduler contention that benchstat's `p`-values exist to remove, silently corrupting the comparison. This is orthogonal to the `-cpu` sweep, which varies `GOMAXPROCS` *within* a single pass.
|
|
148
|
+
|
|
149
|
+
Output:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
│ old.txt │ new.txt │
|
|
153
|
+
│ sec/op │ sec/op vs base │
|
|
154
|
+
Process-8 12.30m ± 2% 8.70m ± 1% -29.27% (p=0.002 n=10)
|
|
155
|
+
|
|
156
|
+
│ old.txt │ new.txt │
|
|
157
|
+
│ B/op │ B/op vs base │
|
|
158
|
+
Process-8 4.80Ki ± 0% 1.20Ki ± 0% -75.00% (p=0.002 n=10)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Read:
|
|
162
|
+
|
|
163
|
+
- `± N%` — coefficient of variation; under 5% is stable.
|
|
164
|
+
- `vs base` — relative delta; negative is faster / less memory.
|
|
165
|
+
- `p=0.002` — Mann-Whitney U test p-value; `< 0.05` means statistically significant.
|
|
166
|
+
- `n=10` — sample size used (might be trimmed if outliers were rejected).
|
|
167
|
+
|
|
168
|
+
If `p > 0.05`, benchstat prints `~` — the delta is indistinguishable from noise. Don't claim wins on insignificant results.
|
|
169
|
+
|
|
170
|
+
## Profiling Alongside Benchmarks
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
go test -run ^$ -bench=BenchmarkProcess -cpuprofile cpu.prof -memprofile mem.prof -count=1
|
|
174
|
+
|
|
175
|
+
go tool pprof cpu.prof
|
|
176
|
+
# (pprof) top 20
|
|
177
|
+
# (pprof) list ProcessOrder
|
|
178
|
+
# (pprof) web # flame graph in browser
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
For long-running benchmarks, also capture a trace:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
go test -bench=BenchmarkProcess -trace trace.out -benchtime=5s
|
|
185
|
+
go tool trace trace.out
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The trace UI shows goroutine scheduling, GC pauses, and syscall blocking — invaluable for diagnosing variance.
|
|
189
|
+
|
|
190
|
+
## The Dead Code Elimination Trap
|
|
191
|
+
|
|
192
|
+
Benchmarks that don't use their result get eliminated by the compiler. Classic failure mode: you measure 0.3 ns/op for a function that obviously can't be that fast.
|
|
193
|
+
|
|
194
|
+
```go
|
|
195
|
+
// WRONG — result unused, loop may be elided
|
|
196
|
+
func BenchmarkHash(b *testing.B) {
|
|
197
|
+
for i := 0; i < b.N; i++ {
|
|
198
|
+
hash("hello")
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// RIGHT — assign to a package-level sink
|
|
203
|
+
var sink uint64
|
|
204
|
+
|
|
205
|
+
func BenchmarkHash(b *testing.B) {
|
|
206
|
+
var s uint64
|
|
207
|
+
for i := 0; i < b.N; i++ {
|
|
208
|
+
s = hash("hello")
|
|
209
|
+
}
|
|
210
|
+
sink = s // prevent elimination
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Alternatives: pass the result to `b.Log` conditionally (`if sink == 0 { b.Log(sink) }`), or call `runtime.KeepAlive(result)`.
|
|
215
|
+
|
|
216
|
+
Symptom: nanoseconds per op that don't match the operation cost. `go test -gcflags="-m"` will show inlining / dead-store eliminations.
|
|
217
|
+
|
|
218
|
+
## Reading Benchmark Output
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
BenchmarkFoo-8 1000000 1234 ns/op 456 B/op 7 allocs/op
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| Column | Meaning |
|
|
225
|
+
|--------|---------|
|
|
226
|
+
| `BenchmarkFoo-8` | Name + `GOMAXPROCS` |
|
|
227
|
+
| `1000000` | Iterations the framework chose (`b.N`) |
|
|
228
|
+
| `1234 ns/op` | Wall-clock nanoseconds per iteration |
|
|
229
|
+
| `456 B/op` | Bytes allocated per iteration (from `-benchmem`) |
|
|
230
|
+
| `7 allocs/op` | Number of heap allocations per iteration |
|
|
231
|
+
|
|
232
|
+
Derived:
|
|
233
|
+
|
|
234
|
+
- Ops/sec = `1e9 / ns/op` — handy for throughput claims
|
|
235
|
+
- Allocation pressure = `allocs/op` × call rate — often the real bottleneck on a GC'd language
|
|
236
|
+
- Target "0 allocs/op" for hot paths — it's achievable; profile to find the leak
|
|
237
|
+
|
|
238
|
+
## Common Pitfalls
|
|
239
|
+
|
|
240
|
+
| Pitfall | Symptom | Fix |
|
|
241
|
+
|---------|---------|-----|
|
|
242
|
+
| Running with `-count=1` | Can't distinguish signal from noise | `-count=6` minimum |
|
|
243
|
+
| No `benchstat` / just eyeball percentages | False wins on noise | Use `benchstat`; respect `p > 0.05` |
|
|
244
|
+
| Setup inside the loop body | Measures builder+work, not work | Pull setup out; use `b.ResetTimer` |
|
|
245
|
+
| Laptop on battery / thermal throttle | CPU downclocks mid-run | Plug in; close browser; disable Turbo Boost for stability if benchmarking often |
|
|
246
|
+
| `-benchtime=100x` in CI | Each op might take milliseconds; 100 samples is nothing | `-benchtime=2s` or more |
|
|
247
|
+
| Comparing across Go versions or hardware | Apples to oranges | Benchmark on the same machine, same `go version` |
|
|
248
|
+
| Capturing variants concurrently | benchstat still shows a delta, but it is contention, not code | Run the two passes serially |
|
|
249
|
+
| Ignoring `allocs/op` delta | CPU looks equal, but allocations doubled | Always read all three columns |
|
|
250
|
+
| `b.ReportAllocs` missing | `B/op` / `allocs/op` missing from output | Add `b.ReportAllocs()` at the top |
|
|
251
|
+
|
|
252
|
+
## Methodology Checklist
|
|
253
|
+
|
|
254
|
+
Before claiming a perf improvement:
|
|
255
|
+
|
|
256
|
+
- [ ] Ran with `-count=6` or higher
|
|
257
|
+
- [ ] Ran on a quiet machine (closed other heavy apps)
|
|
258
|
+
- [ ] Compared with `benchstat`, saw `p < 0.05`
|
|
259
|
+
- [ ] Inspected `B/op` and `allocs/op` — not just `ns/op`
|
|
260
|
+
- [ ] Added a sink or `runtime.KeepAlive` if the result isn't consumed
|
|
261
|
+
- [ ] Reproduced on a second machine / CI runner
|
|
262
|
+
|
|
263
|
+
## Review Checklist
|
|
264
|
+
|
|
265
|
+
When reviewing benchmark code, flag:
|
|
266
|
+
|
|
267
|
+
- **CRITICAL** — Benchmark result never consumed (`sink` missing) → measuring nothing
|
|
268
|
+
- **WARNING** — `-count=1` assumed in CI scripts or PR templates → no statistical rigor
|
|
269
|
+
- **WARNING** — Setup inside the loop body, no `b.ResetTimer`
|
|
270
|
+
- **WARNING** — Benchmark without `-benchmem` / no `b.ReportAllocs()` → missing allocation story
|
|
271
|
+
- **SUGGESTION** — No sub-benchmarks (`b.Run`) for a parameter sweep that would surface nonlinear cost
|
|
272
|
+
- **SUGGESTION** — Missing `b.SetBytes` in an I/O / parsing benchmark (throughput easier to reason about)
|
|
273
|
+
|
|
274
|
+
## Further Reading
|
|
275
|
+
|
|
276
|
+
- `testing` package: https://pkg.go.dev/testing#hdr-Benchmarks
|
|
277
|
+
- `benchstat`: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
|
|
278
|
+
- Profiling workflow: `packages/stack-agents/go/references/performance-profiling.md`
|
|
279
|
+
- Dave Cheney's "High Performance Go Workshop": https://dave.cheney.net/high-performance-go-workshop/gophercon-2019.html
|
|
280
|
+
- Escape analysis flag: `go build -gcflags="-m"`
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Channel Patterns
|
|
2
|
+
|
|
3
|
+
> Reference document for go-concurrency-reviewer. Loaded on demand via Read tool.
|
|
4
|
+
|
|
5
|
+
## Fan-Out / Fan-In
|
|
6
|
+
|
|
7
|
+
Distribute work across multiple goroutines (fan-out), collect results into one channel (fan-in).
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// Fan-out: spawn N workers reading from one input channel
|
|
11
|
+
func fanOut(ctx context.Context, input <-chan Job, workers int) []<-chan Result {
|
|
12
|
+
outputs := make([]<-chan Result, workers)
|
|
13
|
+
for i := range workers {
|
|
14
|
+
out := make(chan Result)
|
|
15
|
+
outputs[i] = out
|
|
16
|
+
go func() {
|
|
17
|
+
defer close(out)
|
|
18
|
+
for job := range input {
|
|
19
|
+
select {
|
|
20
|
+
case <-ctx.Done():
|
|
21
|
+
return
|
|
22
|
+
case out <- process(job):
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}()
|
|
26
|
+
}
|
|
27
|
+
return outputs
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fan-in: merge multiple channels into one
|
|
31
|
+
func fanIn(ctx context.Context, channels ...<-chan Result) <-chan Result {
|
|
32
|
+
merged := make(chan Result)
|
|
33
|
+
var wg sync.WaitGroup
|
|
34
|
+
wg.Add(len(channels))
|
|
35
|
+
|
|
36
|
+
for _, ch := range channels {
|
|
37
|
+
go func() {
|
|
38
|
+
defer wg.Done()
|
|
39
|
+
for result := range ch {
|
|
40
|
+
select {
|
|
41
|
+
case <-ctx.Done():
|
|
42
|
+
return
|
|
43
|
+
case merged <- result:
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
go func() {
|
|
50
|
+
wg.Wait()
|
|
51
|
+
close(merged)
|
|
52
|
+
}()
|
|
53
|
+
|
|
54
|
+
return merged
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Usage
|
|
58
|
+
input := make(chan Job)
|
|
59
|
+
outputs := fanOut(ctx, input, 4)
|
|
60
|
+
results := fanIn(ctx, outputs...)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Pipeline
|
|
64
|
+
|
|
65
|
+
Chain of stages where each stage reads from input, processes, writes to output.
|
|
66
|
+
|
|
67
|
+
```go
|
|
68
|
+
// Stage 1: generate
|
|
69
|
+
func generate(ctx context.Context, nums ...int) <-chan int {
|
|
70
|
+
out := make(chan int)
|
|
71
|
+
go func() {
|
|
72
|
+
defer close(out)
|
|
73
|
+
for _, n := range nums {
|
|
74
|
+
select {
|
|
75
|
+
case <-ctx.Done():
|
|
76
|
+
return
|
|
77
|
+
case out <- n:
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}()
|
|
81
|
+
return out
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Stage 2: square
|
|
85
|
+
func square(ctx context.Context, in <-chan int) <-chan int {
|
|
86
|
+
out := make(chan int)
|
|
87
|
+
go func() {
|
|
88
|
+
defer close(out)
|
|
89
|
+
for n := range in {
|
|
90
|
+
select {
|
|
91
|
+
case <-ctx.Done():
|
|
92
|
+
return
|
|
93
|
+
case out <- n * n:
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}()
|
|
97
|
+
return out
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Stage 3: filter
|
|
101
|
+
func filter(ctx context.Context, in <-chan int, predicate func(int) bool) <-chan int {
|
|
102
|
+
out := make(chan int)
|
|
103
|
+
go func() {
|
|
104
|
+
defer close(out)
|
|
105
|
+
for n := range in {
|
|
106
|
+
if predicate(n) {
|
|
107
|
+
select {
|
|
108
|
+
case <-ctx.Done():
|
|
109
|
+
return
|
|
110
|
+
case out <- n:
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}()
|
|
115
|
+
return out
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Compose pipeline
|
|
119
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
120
|
+
defer cancel()
|
|
121
|
+
|
|
122
|
+
nums := generate(ctx, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
123
|
+
squared := square(ctx, nums)
|
|
124
|
+
even := filter(ctx, squared, func(n int) bool { return n%2 == 0 })
|
|
125
|
+
|
|
126
|
+
for result := range even {
|
|
127
|
+
fmt.Println(result) // 4, 16, 36, 64, 100
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Signaling Patterns
|
|
132
|
+
|
|
133
|
+
### Done Channel
|
|
134
|
+
|
|
135
|
+
```go
|
|
136
|
+
// Signal completion without data
|
|
137
|
+
done := make(chan struct{})
|
|
138
|
+
|
|
139
|
+
go func() {
|
|
140
|
+
defer close(done) // signal by closing
|
|
141
|
+
doWork()
|
|
142
|
+
}()
|
|
143
|
+
|
|
144
|
+
<-done // wait for completion
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Context Done
|
|
148
|
+
|
|
149
|
+
```go
|
|
150
|
+
go func(ctx context.Context) {
|
|
151
|
+
for {
|
|
152
|
+
select {
|
|
153
|
+
case <-ctx.Done():
|
|
154
|
+
fmt.Println("cancelled:", ctx.Err())
|
|
155
|
+
return
|
|
156
|
+
case data := <-dataCh:
|
|
157
|
+
process(data)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}(ctx)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Channel Ownership Rules
|
|
164
|
+
|
|
165
|
+
**Only the sender closes a channel. Never the receiver.**
|
|
166
|
+
|
|
167
|
+
```go
|
|
168
|
+
// CORRECT — producer owns and closes
|
|
169
|
+
func produce(ctx context.Context) <-chan Item {
|
|
170
|
+
ch := make(chan Item)
|
|
171
|
+
go func() {
|
|
172
|
+
defer close(ch) // producer closes
|
|
173
|
+
for item := range items {
|
|
174
|
+
select {
|
|
175
|
+
case <-ctx.Done():
|
|
176
|
+
return
|
|
177
|
+
case ch <- item:
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}()
|
|
181
|
+
return ch // return read-only channel
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Consumer just ranges — never closes
|
|
185
|
+
for item := range produce(ctx) {
|
|
186
|
+
process(item)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Directed Channel Types
|
|
191
|
+
|
|
192
|
+
Use directed types in function signatures to enforce ownership.
|
|
193
|
+
|
|
194
|
+
```go
|
|
195
|
+
// Send-only: can write, cannot read
|
|
196
|
+
func producer(out chan<- int) {
|
|
197
|
+
out <- 42
|
|
198
|
+
close(out)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Receive-only: can read, cannot write or close
|
|
202
|
+
func consumer(in <-chan int) {
|
|
203
|
+
for v := range in {
|
|
204
|
+
fmt.Println(v)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Buffered vs Unbuffered
|
|
210
|
+
|
|
211
|
+
| Type | Behavior | Use When |
|
|
212
|
+
|------|----------|----------|
|
|
213
|
+
| Unbuffered `make(chan T)` | Send blocks until receiver ready | Synchronization between goroutines |
|
|
214
|
+
| Buffered `make(chan T, n)` | Send blocks only when buffer full | Throughput, decoupling speed differences |
|
|
215
|
+
|
|
216
|
+
```go
|
|
217
|
+
// Unbuffered — synchronization point
|
|
218
|
+
// Both goroutines must be ready simultaneously
|
|
219
|
+
sync := make(chan struct{})
|
|
220
|
+
go func() {
|
|
221
|
+
prepareData()
|
|
222
|
+
sync <- struct{}{} // blocks until main reads
|
|
223
|
+
}()
|
|
224
|
+
<-sync // blocks until goroutine sends
|
|
225
|
+
useData()
|
|
226
|
+
|
|
227
|
+
// Buffered — throughput
|
|
228
|
+
// Producer can run ahead of consumer
|
|
229
|
+
jobs := make(chan Job, 100)
|
|
230
|
+
go func() {
|
|
231
|
+
for _, j := range allJobs {
|
|
232
|
+
jobs <- j // won't block until buffer full
|
|
233
|
+
}
|
|
234
|
+
close(jobs)
|
|
235
|
+
}()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Buffer Size Guidelines
|
|
239
|
+
|
|
240
|
+
| Size | Use Case |
|
|
241
|
+
|------|----------|
|
|
242
|
+
| 0 (unbuffered) | Synchronization, handoff |
|
|
243
|
+
| 1 | Signal that can be "pending" (e.g., notify channel) |
|
|
244
|
+
| N (known batch) | Pre-buffering known workload |
|
|
245
|
+
| N (worker count) | One slot per worker |
|
|
246
|
+
|
|
247
|
+
```go
|
|
248
|
+
// Size 1 — "at most one pending notification"
|
|
249
|
+
notify := make(chan struct{}, 1)
|
|
250
|
+
|
|
251
|
+
// Non-blocking send: notify if not already pending
|
|
252
|
+
select {
|
|
253
|
+
case notify <- struct{}{}:
|
|
254
|
+
default: // already pending, skip
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Select with Default (Non-Blocking)
|
|
259
|
+
|
|
260
|
+
```go
|
|
261
|
+
// Non-blocking receive
|
|
262
|
+
select {
|
|
263
|
+
case msg := <-ch:
|
|
264
|
+
process(msg)
|
|
265
|
+
default:
|
|
266
|
+
// ch not ready, do something else
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Non-blocking send
|
|
270
|
+
select {
|
|
271
|
+
case ch <- msg:
|
|
272
|
+
// sent
|
|
273
|
+
default:
|
|
274
|
+
// channel full or not ready, drop or queue
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Try-lock pattern
|
|
278
|
+
select {
|
|
279
|
+
case sem <- struct{}{}: // acquire
|
|
280
|
+
defer func() { <-sem }() // release
|
|
281
|
+
doCriticalWork()
|
|
282
|
+
default:
|
|
283
|
+
// someone else holds it
|
|
284
|
+
return ErrBusy
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Nil Channel Tricks
|
|
289
|
+
|
|
290
|
+
A nil channel blocks forever on both send and receive. This is useful in select to dynamically disable cases.
|
|
291
|
+
|
|
292
|
+
```go
|
|
293
|
+
func merge(ctx context.Context, a, b <-chan int) <-chan int {
|
|
294
|
+
out := make(chan int)
|
|
295
|
+
go func() {
|
|
296
|
+
defer close(out)
|
|
297
|
+
for a != nil || b != nil {
|
|
298
|
+
select {
|
|
299
|
+
case <-ctx.Done():
|
|
300
|
+
return
|
|
301
|
+
case v, ok := <-a:
|
|
302
|
+
if !ok {
|
|
303
|
+
a = nil // disable this case
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
out <- v
|
|
307
|
+
case v, ok := <-b:
|
|
308
|
+
if !ok {
|
|
309
|
+
b = nil // disable this case
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
312
|
+
out <- v
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}()
|
|
316
|
+
return out
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Dynamic Channel Selection
|
|
321
|
+
|
|
322
|
+
```go
|
|
323
|
+
func processWithTimeout(ch <-chan Data, timeout time.Duration) (Data, error) {
|
|
324
|
+
var timer <-chan time.Time // nil — won't fire
|
|
325
|
+
if timeout > 0 {
|
|
326
|
+
t := time.NewTimer(timeout)
|
|
327
|
+
defer t.Stop()
|
|
328
|
+
timer = t.C // now it will fire
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
select {
|
|
332
|
+
case data := <-ch:
|
|
333
|
+
return data, nil
|
|
334
|
+
case <-timer: // skipped entirely if timer is nil
|
|
335
|
+
return Data{}, ErrTimeout
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Common Mistakes
|
|
341
|
+
|
|
342
|
+
### Forgetting to Close
|
|
343
|
+
|
|
344
|
+
```go
|
|
345
|
+
// BUG — consumer ranges forever
|
|
346
|
+
func produce() <-chan int {
|
|
347
|
+
ch := make(chan int)
|
|
348
|
+
go func() {
|
|
349
|
+
for i := range 10 {
|
|
350
|
+
ch <- i
|
|
351
|
+
}
|
|
352
|
+
// forgot close(ch) — consumer blocks forever after 10 items
|
|
353
|
+
}()
|
|
354
|
+
return ch
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Closing from Receiver Side
|
|
359
|
+
|
|
360
|
+
```go
|
|
361
|
+
// BUG — panic if sender writes after close
|
|
362
|
+
go func() {
|
|
363
|
+
for data := range input {
|
|
364
|
+
process(data)
|
|
365
|
+
}
|
|
366
|
+
close(input) // WRONG: receiver should not close
|
|
367
|
+
}()
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Range Over Non-Closed Channel
|
|
371
|
+
|
|
372
|
+
```go
|
|
373
|
+
// BUG — deadlock if channel is never closed
|
|
374
|
+
for v := range ch { // blocks forever after last value
|
|
375
|
+
process(v)
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## When to Use
|
|
380
|
+
|
|
381
|
+
Apply when reviewing channel usage and concurrent communication patterns. Flag violations as:
|
|
382
|
+
- **CRITICAL**: Receiver closing channel, unbuffered channel in goroutine without exit path, range over channel that's never closed
|
|
383
|
+
- **WARNING**: Missing context/done in select, oversized buffer without justification, send on closed channel risk
|
|
384
|
+
- **SUGGESTION**: Could use directed channel types, nil channel trick for dynamic disable, pipeline pattern for data processing
|