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,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Inject `tokens: N` metadata into YAML frontmatter of all agents, skills, rules.
|
|
3
|
+
// Transparency only — not a budget. Authors should update when editing.
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { measureTokens } from './measure-tokens.js';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
const DRY_RUN = process.argv.includes('--dry-run');
|
|
14
|
+
|
|
15
|
+
// Collect target files
|
|
16
|
+
function walkDir(dir, pattern = /\.md$/) {
|
|
17
|
+
const results = [];
|
|
18
|
+
if (!fs.existsSync(dir)) return results;
|
|
19
|
+
const stack = [dir];
|
|
20
|
+
while (stack.length) {
|
|
21
|
+
const current = stack.pop();
|
|
22
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const full = path.join(current, entry.name);
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
stack.push(full);
|
|
27
|
+
} else if (entry.isFile() && pattern.test(entry.name)) {
|
|
28
|
+
results.push(full);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function collectTargets() {
|
|
36
|
+
const targets = new Set();
|
|
37
|
+
|
|
38
|
+
// Agents
|
|
39
|
+
for (const p of [
|
|
40
|
+
'packages/core/agents',
|
|
41
|
+
'packages/stack-agents',
|
|
42
|
+
'packages/frontend-3d/agents',
|
|
43
|
+
'packages/frontend-ui/agents',
|
|
44
|
+
]) {
|
|
45
|
+
walkDir(path.join(ROOT, p)).forEach(f => targets.add(f));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Extras flat files (only .md at top level of packages/extras)
|
|
49
|
+
const extrasDir = path.join(ROOT, 'packages/extras');
|
|
50
|
+
if (fs.existsSync(extrasDir)) {
|
|
51
|
+
for (const entry of fs.readdirSync(extrasDir, { withFileTypes: true })) {
|
|
52
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
53
|
+
targets.add(path.join(extrasDir, entry.name));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Skills — only SKILL.md files
|
|
59
|
+
for (const p of [
|
|
60
|
+
'packages/core/skills',
|
|
61
|
+
'packages/frontend-3d/skills',
|
|
62
|
+
'packages/frontend-ui/skills',
|
|
63
|
+
]) {
|
|
64
|
+
walkDir(path.join(ROOT, p), /^SKILL\.md$/).forEach(f => targets.add(f));
|
|
65
|
+
}
|
|
66
|
+
const skillsmp = path.join(ROOT, 'packages/extras/skillsmp-search/SKILL.md');
|
|
67
|
+
if (fs.existsSync(skillsmp)) targets.add(skillsmp);
|
|
68
|
+
|
|
69
|
+
// Rules
|
|
70
|
+
for (const p of [
|
|
71
|
+
'packages/core/rules',
|
|
72
|
+
'packages/stack-rules',
|
|
73
|
+
'packages/frontend-3d/rules',
|
|
74
|
+
'packages/frontend-ui/rules',
|
|
75
|
+
]) {
|
|
76
|
+
walkDir(path.join(ROOT, p)).forEach(f => targets.add(f));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return Array.from(targets).sort();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Parse/modify frontmatter. Returns { status, oldTokens, newTokens, content }.
|
|
83
|
+
// status: 'updated' | 'inserted' | 'kept' | 'skipped-no-frontmatter' | 'skipped-malformed'
|
|
84
|
+
function processFile(filePath) {
|
|
85
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
86
|
+
|
|
87
|
+
// Detect frontmatter: must start with "---\n" and have a closing "---\n" later
|
|
88
|
+
if (!raw.startsWith('---\n') && !raw.startsWith('---\r\n')) {
|
|
89
|
+
return { status: 'skipped-no-frontmatter' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Find end of frontmatter
|
|
93
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
94
|
+
if (!match) {
|
|
95
|
+
return { status: 'skipped-malformed' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const frontmatterBlock = match[0];
|
|
99
|
+
const frontmatterBody = match[1];
|
|
100
|
+
const body = raw.slice(frontmatterBlock.length);
|
|
101
|
+
const newTokens = Math.round(body.length / 4);
|
|
102
|
+
|
|
103
|
+
// Look for existing tokens field
|
|
104
|
+
const lines = frontmatterBody.split(/\r?\n/);
|
|
105
|
+
const tokensIdx = lines.findIndex(l => /^tokens:\s*\d+\s*$/.test(l));
|
|
106
|
+
|
|
107
|
+
if (tokensIdx !== -1) {
|
|
108
|
+
const existing = parseInt(lines[tokensIdx].match(/^tokens:\s*(\d+)/)[1], 10);
|
|
109
|
+
// Within ±20%? Leave alone.
|
|
110
|
+
const tolerance = existing * 0.2;
|
|
111
|
+
if (Math.abs(existing - newTokens) <= tolerance) {
|
|
112
|
+
return { status: 'kept', oldTokens: existing, newTokens };
|
|
113
|
+
}
|
|
114
|
+
// Update in place
|
|
115
|
+
lines[tokensIdx] = `tokens: ${newTokens}`;
|
|
116
|
+
const newFrontmatter = `---\n${lines.join('\n')}\n---\n`;
|
|
117
|
+
return {
|
|
118
|
+
status: 'updated',
|
|
119
|
+
oldTokens: existing,
|
|
120
|
+
newTokens,
|
|
121
|
+
content: newFrontmatter + body,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Insert. After `description:` line if exists, else before closing ---
|
|
126
|
+
const descIdx = lines.findIndex(l => /^description:/.test(l));
|
|
127
|
+
if (descIdx !== -1) {
|
|
128
|
+
// Handle possible multi-line description (YAML block) — find next top-level key
|
|
129
|
+
let insertAt = descIdx + 1;
|
|
130
|
+
// If the description continues onto next lines (indented), skip those.
|
|
131
|
+
// Blank lines inside a YAML block scalar are continuations too — skip a
|
|
132
|
+
// blank line when a later indented line follows (fixes tokens: being
|
|
133
|
+
// inserted mid-description in multi-paragraph block descriptions).
|
|
134
|
+
while (insertAt < lines.length) {
|
|
135
|
+
if (/^\s+/.test(lines[insertAt])) {
|
|
136
|
+
insertAt++;
|
|
137
|
+
} else if (
|
|
138
|
+
lines[insertAt].trim() === '' &&
|
|
139
|
+
insertAt + 1 < lines.length &&
|
|
140
|
+
/^\s+/.test(lines[insertAt + 1])
|
|
141
|
+
) {
|
|
142
|
+
insertAt++;
|
|
143
|
+
} else {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
lines.splice(insertAt, 0, `tokens: ${newTokens}`);
|
|
148
|
+
} else {
|
|
149
|
+
lines.push(`tokens: ${newTokens}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const newFrontmatter = `---\n${lines.join('\n')}\n---\n`;
|
|
153
|
+
return {
|
|
154
|
+
status: 'inserted',
|
|
155
|
+
newTokens,
|
|
156
|
+
content: newFrontmatter + body,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Main
|
|
161
|
+
const targets = collectTargets();
|
|
162
|
+
const counts = { updated: 0, inserted: 0, kept: 0, skippedNoFm: 0, skippedMalformed: 0 };
|
|
163
|
+
const tokenValues = [];
|
|
164
|
+
const skipped = [];
|
|
165
|
+
const changes = [];
|
|
166
|
+
|
|
167
|
+
for (const file of targets) {
|
|
168
|
+
const result = processFile(file);
|
|
169
|
+
const rel = path.relative(ROOT, file);
|
|
170
|
+
switch (result.status) {
|
|
171
|
+
case 'updated':
|
|
172
|
+
counts.updated++;
|
|
173
|
+
tokenValues.push({ file: rel, tokens: result.newTokens });
|
|
174
|
+
changes.push(`UPDATE ${rel}: ${result.oldTokens} → ${result.newTokens}`);
|
|
175
|
+
if (!DRY_RUN) fs.writeFileSync(file, result.content);
|
|
176
|
+
break;
|
|
177
|
+
case 'inserted':
|
|
178
|
+
counts.inserted++;
|
|
179
|
+
tokenValues.push({ file: rel, tokens: result.newTokens });
|
|
180
|
+
changes.push(`INSERT ${rel}: ${result.newTokens}`);
|
|
181
|
+
if (!DRY_RUN) fs.writeFileSync(file, result.content);
|
|
182
|
+
break;
|
|
183
|
+
case 'kept':
|
|
184
|
+
counts.kept++;
|
|
185
|
+
tokenValues.push({ file: rel, tokens: result.oldTokens });
|
|
186
|
+
break;
|
|
187
|
+
case 'skipped-no-frontmatter':
|
|
188
|
+
counts.skippedNoFm++;
|
|
189
|
+
skipped.push(`${rel} — no frontmatter`);
|
|
190
|
+
break;
|
|
191
|
+
case 'skipped-malformed':
|
|
192
|
+
counts.skippedMalformed++;
|
|
193
|
+
skipped.push(`${rel} — malformed frontmatter`);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`\n=== inject-tokens ${DRY_RUN ? '(DRY RUN)' : ''} ===`);
|
|
199
|
+
console.log(`Targets scanned: ${targets.length}`);
|
|
200
|
+
console.log(` Inserted: ${counts.inserted}`);
|
|
201
|
+
console.log(` Updated: ${counts.updated}`);
|
|
202
|
+
console.log(` Kept: ${counts.kept}`);
|
|
203
|
+
console.log(` Skipped (no frontmatter): ${counts.skippedNoFm}`);
|
|
204
|
+
console.log(` Skipped (malformed): ${counts.skippedMalformed}`);
|
|
205
|
+
|
|
206
|
+
if (changes.length && (DRY_RUN || process.argv.includes('--verbose'))) {
|
|
207
|
+
console.log('\n--- Changes ---');
|
|
208
|
+
for (const c of changes) console.log(c);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (skipped.length) {
|
|
212
|
+
console.log('\n--- Skipped ---');
|
|
213
|
+
for (const s of skipped) console.log(s);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Stats on token distribution
|
|
217
|
+
if (tokenValues.length) {
|
|
218
|
+
const nums = tokenValues.map(t => t.tokens).sort((a, b) => a - b);
|
|
219
|
+
const min = nums[0];
|
|
220
|
+
const max = nums[nums.length - 1];
|
|
221
|
+
const median = nums[Math.floor(nums.length / 2)];
|
|
222
|
+
console.log(`\n--- Token distribution (all ${tokenValues.length} files) ---`);
|
|
223
|
+
console.log(`min=${min} median=${median} max=${max}`);
|
|
224
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Approximate token count using ~4 chars/token heuristic.
|
|
3
|
+
// Measures body only, frontmatter excluded.
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export function measureTokens(filePath) {
|
|
7
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
8
|
+
const body = content.replace(/^---[\s\S]*?---\n/, '');
|
|
9
|
+
return Math.round(body.length / 4);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// CLI mode
|
|
13
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
14
|
+
const file = process.argv[2];
|
|
15
|
+
if (!file) { console.error('usage: measure-tokens.js <file>'); process.exit(1); }
|
|
16
|
+
console.log(measureTokens(file));
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/superkit-counts-verify.sh — convenience wrapper
|
|
3
|
+
#
|
|
4
|
+
# Forwards to the canonical hook at packages/core/hooks/superkit-counts-verify.sh.
|
|
5
|
+
# The canonical path is wired into .claude/settings.json as a PreToolUse hook;
|
|
6
|
+
# this wrapper exists so contributors can run the same check manually from
|
|
7
|
+
# the repo root via `bash bin/superkit-counts-verify.sh` without remembering
|
|
8
|
+
# the full hook path.
|
|
9
|
+
#
|
|
10
|
+
# CLI args (e.g. --check-remote, which verifies the GitHub About description
|
|
11
|
+
# over the network) are forwarded to the canonical hook.
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
15
|
+
HOOK="$REPO_ROOT/packages/core/hooks/superkit-counts-verify.sh"
|
|
16
|
+
|
|
17
|
+
if [ ! -f "$HOOK" ]; then
|
|
18
|
+
printf 'Error: canonical hook missing at %s\n' "$HOOK" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# This convenience wrapper is for MANUAL runs from the repo root. It always
|
|
23
|
+
# synthesises a git-commit payload so the canonical hook runs its count/version
|
|
24
|
+
# checks (instead of early-exiting "not a commit"). It deliberately does NOT
|
|
25
|
+
# read stdin: a previous `[ -t 0 ]` check fell through to reading stdin in
|
|
26
|
+
# non-interactive contexts (CI, detached/backgrounded shells) where stdin has
|
|
27
|
+
# no TTY and no EOF, hanging forever — and silently no-op'd (early-exit) when
|
|
28
|
+
# stdin EOF'd empty. To feed a real PreToolUse payload, invoke the canonical
|
|
29
|
+
# hook ($HOOK) directly; it reads stdin as normal.
|
|
30
|
+
printf '{"tool_input":{"command":"git commit -m verify"}}' | exec bash "$HOOK" "$@"
|
package/lib/codex.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { lstatSync, readlinkSync, linkSync, symlinkSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, writeFileSync, renameSync, unlinkSync, openSync, closeSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { info, warn, commandExists } from './utils.js';
|
|
5
|
+
|
|
6
|
+
const MANIFEST = '.codex/.superkit-manifest.json';
|
|
7
|
+
const BACKUPS = '.codex/.superkit-backups';
|
|
8
|
+
const LOCK = '.codex/.superkit-install.lock';
|
|
9
|
+
const HOOK_ASSETS = ['hooks/superkit.mjs', 'superkit-hooks.json'];
|
|
10
|
+
const skillName = /^[a-z0-9][a-z0-9-]*$/;
|
|
11
|
+
const digest = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
|
|
13
|
+
function stat(path) {
|
|
14
|
+
try { return lstatSync(path); } catch (err) {
|
|
15
|
+
if (err.code === 'ENOENT') return null;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Do not follow even dangling links, nor write through a linked ancestor.
|
|
21
|
+
// The caller owns the project directory; concurrent external edits are unsupported.
|
|
22
|
+
function checkPath(root, relative, directory = false) {
|
|
23
|
+
const parts = relative.split('/');
|
|
24
|
+
for (let i = 0; i < parts.length; i++) {
|
|
25
|
+
const path = join(root, ...parts.slice(0, i + 1));
|
|
26
|
+
const entry = stat(path);
|
|
27
|
+
if (!entry) continue;
|
|
28
|
+
if (entry.isSymbolicLink()) throw new Error(`Refusing symbolic link: ${path}`);
|
|
29
|
+
const needDirectory = i < parts.length - 1 || directory;
|
|
30
|
+
if (needDirectory ? !entry.isDirectory() : !entry.isFile()) {
|
|
31
|
+
throw new Error(`Expected ${needDirectory ? 'directory' : 'regular file'}: ${path}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readHashes(path, manifest) {
|
|
37
|
+
let record;
|
|
38
|
+
try {
|
|
39
|
+
record = JSON.parse(readFileSync(path, 'utf8'));
|
|
40
|
+
if (!record || (manifest && record.schemaVersion !== 1) ||
|
|
41
|
+
(!manifest && (record.version !== '1.5.2' || !/^[a-f0-9]{40}$/.test(record.sourceCommit))) ||
|
|
42
|
+
!record.skills || typeof record.skills !== 'object' || Array.isArray(record.skills) ||
|
|
43
|
+
Object.entries(record.skills).some(([name, hash]) => !skillName.test(name) || typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash)) ||
|
|
44
|
+
(manifest && record.assets !== undefined && (!record.assets || typeof record.assets !== 'object' || Array.isArray(record.assets) ||
|
|
45
|
+
Object.entries(record.assets).some(([name, hash]) => !HOOK_ASSETS.includes(name) || typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash))))) {
|
|
46
|
+
throw new Error('unsupported schema or invalid skill hashes');
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(`Invalid Codex ${manifest ? 'manifest' : 'baseline'} at ${path}: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
return record;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loadPackage(packagesDir) {
|
|
55
|
+
const root = join(packagesDir, 'codex');
|
|
56
|
+
const templates = [
|
|
57
|
+
['AGENTS.md', 'AGENTS.md', 'Created AGENTS.md template'],
|
|
58
|
+
['config.toml', '.codex/config.toml', 'Copied shipped Codex config template → .codex/config.toml'],
|
|
59
|
+
['rules/default.rules', '.codex/rules/default.rules', 'Created .codex/rules/default.rules (approval policy DSL)']
|
|
60
|
+
].map(([source, destination, message]) => {
|
|
61
|
+
checkPath(root, source);
|
|
62
|
+
return { destination, message, bytes: readFileSync(join(root, source)) };
|
|
63
|
+
});
|
|
64
|
+
checkPath(root, 'skills', true);
|
|
65
|
+
const skills = [];
|
|
66
|
+
for (const name of readdirSync(join(root, 'skills')).sort()) {
|
|
67
|
+
if (!skillName.test(name)) throw new Error(`Invalid packaged Codex skill name: ${name}`);
|
|
68
|
+
const source = `skills/${name}/SKILL.md`;
|
|
69
|
+
checkPath(root, source);
|
|
70
|
+
const bytes = readFileSync(join(root, source));
|
|
71
|
+
skills.push({ name, destination: `.codex/${source}`, bytes, hash: digest(bytes) });
|
|
72
|
+
}
|
|
73
|
+
if (!skills.length) throw new Error('Codex package contains no skills');
|
|
74
|
+
checkPath(root, 'legacy-baseline.json');
|
|
75
|
+
const baseline = readHashes(join(root, 'legacy-baseline.json'), false).skills;
|
|
76
|
+
const assets = HOOK_ASSETS.map(name => {
|
|
77
|
+
const source = name === 'superkit-hooks.json' ? 'hooks.json' : name;
|
|
78
|
+
checkPath(root, source);
|
|
79
|
+
const bytes = readFileSync(join(root, source));
|
|
80
|
+
if (source === 'hooks.json') {
|
|
81
|
+
const definition = JSON.parse(bytes.toString('utf8'));
|
|
82
|
+
if (!definition?.hooks || typeof definition.hooks !== 'object' || Array.isArray(definition.hooks) ||
|
|
83
|
+
Object.values(definition.hooks).some(groups => !Array.isArray(groups) || groups.some(group =>
|
|
84
|
+
!group || (group.matcher !== undefined && typeof group.matcher !== 'string') ||
|
|
85
|
+
!Array.isArray(group.hooks) || !group.hooks.length || group.hooks.some(handler =>
|
|
86
|
+
!handler || handler.type !== 'command' || typeof handler.command !== 'string' || !handler.command.trim() ||
|
|
87
|
+
(handler.timeout !== undefined && (!Number.isFinite(handler.timeout) || handler.timeout <= 0)))))) {
|
|
88
|
+
throw new Error('Invalid packaged hooks.json: expected command hook groups');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { name, destination: `.codex/${name}`, bytes, hash: digest(bytes) };
|
|
92
|
+
});
|
|
93
|
+
return { skills, templates, baseline, assets };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function writeAtomic(root, relative, bytes) {
|
|
97
|
+
checkPath(root, relative);
|
|
98
|
+
const path = join(root, relative);
|
|
99
|
+
const previous = stat(path);
|
|
100
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
101
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
102
|
+
try {
|
|
103
|
+
writeFileSync(temporary, bytes, { flag: 'wx', mode: previous ? previous.mode & 0o777 : 0o644 });
|
|
104
|
+
if (previous) {
|
|
105
|
+
renameSync(temporary, path);
|
|
106
|
+
} else {
|
|
107
|
+
// Atomic publication without replacing a destination that appeared meanwhile.
|
|
108
|
+
linkSync(temporary, path);
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
if (stat(temporary)) unlinkSync(temporary);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Intentionally independent of Claude's fresh/merge/overwrite selection.
|
|
116
|
+
// Extra legacy arguments are ignored, preserving the former call signature.
|
|
117
|
+
export function installCodex(projectDir, packagesDir) {
|
|
118
|
+
const { skills, templates, baseline, assets } = loadPackage(packagesDir);
|
|
119
|
+
const hadCodex = Boolean(stat(join(projectDir, '.codex')));
|
|
120
|
+
for (const relative of [MANIFEST, LOCK, '.codex/hooks.json', ...skills.map(s => s.destination), ...assets.map(a => a.destination), ...templates.map(t => t.destination)]) {
|
|
121
|
+
checkPath(projectDir, relative);
|
|
122
|
+
}
|
|
123
|
+
checkPath(projectDir, BACKUPS, true);
|
|
124
|
+
checkPath(projectDir, '.agents/skills', true);
|
|
125
|
+
const manifestFile = join(projectDir, MANIFEST);
|
|
126
|
+
const hadManifest = Boolean(stat(manifestFile));
|
|
127
|
+
// Validate before creating even the lock or target directory.
|
|
128
|
+
const previousState = hadManifest ? readHashes(manifestFile, true) : { skills: baseline };
|
|
129
|
+
const previous = previousState.skills;
|
|
130
|
+
const configPath = join(projectDir, '.codex/config.toml');
|
|
131
|
+
// Unknown configuration may already contain inline hooks. Never silently add
|
|
132
|
+
// a second active source beside it; supply a separate merge template instead.
|
|
133
|
+
const canCreateHookConfig = !stat(configPath) || readFileSync(configPath).equals(templates.find(t => t.destination === '.codex/config.toml').bytes);
|
|
134
|
+
if (stat(join(projectDir, LOCK))) throw new Error(`Codex install lock exists: ${join(projectDir, LOCK)}. Check for another installer before removing a stale lock.`);
|
|
135
|
+
mkdirSync(join(projectDir, '.codex'), { recursive: true });
|
|
136
|
+
const lock = openSync(join(projectDir, LOCK), 'wx');
|
|
137
|
+
closeSync(lock);
|
|
138
|
+
try {
|
|
139
|
+
if (!commandExists('codex')) {
|
|
140
|
+
warn('Codex CLI not found. Install: npm install -g @openai/codex');
|
|
141
|
+
warn('Continuing with file setup anyway...');
|
|
142
|
+
}
|
|
143
|
+
const managed = { ...(hadManifest ? previous : {}) };
|
|
144
|
+
const additions = [];
|
|
145
|
+
const updates = [];
|
|
146
|
+
let unchanged = 0;
|
|
147
|
+
let conflicts = 0;
|
|
148
|
+
let recognizedLegacy = false;
|
|
149
|
+
for (const skill of skills) {
|
|
150
|
+
const destination = join(projectDir, skill.destination);
|
|
151
|
+
if (!stat(destination)) {
|
|
152
|
+
additions.push(skill);
|
|
153
|
+
managed[skill.name] = skill.hash;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const oldBytes = readFileSync(destination);
|
|
157
|
+
const oldHash = digest(oldBytes);
|
|
158
|
+
if (!hadManifest && oldHash === previous[skill.name]) recognizedLegacy = true;
|
|
159
|
+
if (oldHash === skill.hash) {
|
|
160
|
+
unchanged++;
|
|
161
|
+
managed[skill.name] = skill.hash;
|
|
162
|
+
} else if (oldHash === previous[skill.name]) {
|
|
163
|
+
updates.push({ ...skill, oldBytes });
|
|
164
|
+
managed[skill.name] = skill.hash;
|
|
165
|
+
} else {
|
|
166
|
+
conflicts++;
|
|
167
|
+
warn(`CONFLICT preserved ${skill.destination} — custom or unrecognized content; compare with packages/codex/skills/${skill.name}/SKILL.md`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const managedAssets = { ...(previousState.assets || {}) };
|
|
171
|
+
const assetAdditions = [];
|
|
172
|
+
const assetUpdates = [];
|
|
173
|
+
let assetConflicts = 0;
|
|
174
|
+
for (const asset of assets) {
|
|
175
|
+
const destination = join(projectDir, asset.destination);
|
|
176
|
+
if (!stat(destination)) {
|
|
177
|
+
assetAdditions.push(asset);
|
|
178
|
+
managedAssets[asset.name] = asset.hash;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const oldBytes = readFileSync(destination);
|
|
182
|
+
const oldHash = digest(oldBytes);
|
|
183
|
+
if (oldHash === asset.hash) {
|
|
184
|
+
managedAssets[asset.name] = asset.hash;
|
|
185
|
+
} else if (oldHash === previousState.assets?.[asset.name]) {
|
|
186
|
+
assetUpdates.push({ ...asset, oldBytes });
|
|
187
|
+
managedAssets[asset.name] = asset.hash;
|
|
188
|
+
} else {
|
|
189
|
+
assetConflicts++;
|
|
190
|
+
warn(`CONFLICT preserved ${asset.destination} — review custom hook assets before trusting or merging hooks`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const replacements = [...updates, ...assetUpdates];
|
|
194
|
+
const mode = !hadCodex ? 'fresh' : hadManifest || recognizedLegacy ? 'upgrade' : 'merge';
|
|
195
|
+
info(`Codex mode: ${mode} (independent of .claude)`);
|
|
196
|
+
|
|
197
|
+
// Finish all backups before the first replacement. Never reuse/delete an old backup.
|
|
198
|
+
if (replacements.length) {
|
|
199
|
+
mkdirSync(join(projectDir, BACKUPS), { recursive: true });
|
|
200
|
+
const backup = mkdtempSync(join(projectDir, BACKUPS, `${new Date().toISOString().replace(/[:.]/g, '-')}-`));
|
|
201
|
+
for (const skill of replacements) {
|
|
202
|
+
const relative = skill.destination.slice('.codex/'.length);
|
|
203
|
+
mkdirSync(dirname(join(backup, relative)), { recursive: true });
|
|
204
|
+
writeFileSync(join(backup, relative), skill.oldBytes, { flag: 'wx' });
|
|
205
|
+
}
|
|
206
|
+
if (hadManifest) writeFileSync(join(backup, '.superkit-manifest.json'), readFileSync(manifestFile), { flag: 'wx' });
|
|
207
|
+
info(`Codex backup: ${backup}`);
|
|
208
|
+
}
|
|
209
|
+
for (const skill of [...additions, ...assetAdditions, ...replacements]) writeAtomic(projectDir, skill.destination, skill.bytes);
|
|
210
|
+
for (const template of templates) {
|
|
211
|
+
if (stat(join(projectDir, template.destination))) {
|
|
212
|
+
warn(`${template.destination} already exists — skipped to preserve consumer configuration/instructions`);
|
|
213
|
+
} else {
|
|
214
|
+
writeAtomic(projectDir, template.destination, template.bytes);
|
|
215
|
+
info(template.message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const hookConfig = '.codex/hooks.json';
|
|
219
|
+
if (!stat(join(projectDir, hookConfig)) && canCreateHookConfig && !assetConflicts) {
|
|
220
|
+
writeAtomic(projectDir, hookConfig, assets.find(a => a.name === 'superkit-hooks.json').bytes);
|
|
221
|
+
info('Created .codex/hooks.json — hooks are configured, not trusted');
|
|
222
|
+
} else {
|
|
223
|
+
warn('Hook definitions preserved/not auto-merged; compare .codex/superkit-hooks.json with your active hooks.json or inline [hooks] configuration');
|
|
224
|
+
}
|
|
225
|
+
info('Review installed hook scripts and use /hooks to review/trust definitions in a trusted project. Installer does not grant hook trust.');
|
|
226
|
+
mkdirSync(join(projectDir, '.agents/skills'), { recursive: true });
|
|
227
|
+
for (const skill of skills) {
|
|
228
|
+
const link = join(projectDir, '.agents/skills', skill.name);
|
|
229
|
+
const target = `../../.codex/skills/${skill.name}`;
|
|
230
|
+
const existing = stat(link);
|
|
231
|
+
if (!existing) {
|
|
232
|
+
symlinkSync(target, link, 'dir');
|
|
233
|
+
} else if (!existing.isSymbolicLink() || readlinkSync(link) !== target) {
|
|
234
|
+
warn(`DISCOVERY CONFLICT preserved .agents/skills/${skill.name} — installed Superkit skill is at ${skill.destination}; choose your active skill manually`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const manifestBytes = JSON.stringify({ schemaVersion: 1, skills: managed, assets: managedAssets }, null, 2) + '\n';
|
|
238
|
+
if (!hadManifest || readFileSync(manifestFile, 'utf8') !== manifestBytes) writeAtomic(projectDir, MANIFEST, manifestBytes);
|
|
239
|
+
info(`Codex skills: ${additions.length} added, ${updates.length} updated, ${unchanged} unchanged, ${conflicts} conflicts preserved`);
|
|
240
|
+
info(`Codex hook assets: ${assetAdditions.length} added, ${assetUpdates.length} updated, ${assetConflicts} conflicts preserved`);
|
|
241
|
+
if (conflicts) warn('Review preserved conflicts manually; new upstream skill content was not applied to those files.');
|
|
242
|
+
return additions.length + updates.length;
|
|
243
|
+
} finally {
|
|
244
|
+
unlinkSync(join(projectDir, LOCK));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, statSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { info, warn, copyFile, copyDir, commandExists } from './utils.js';
|
|
5
|
+
|
|
6
|
+
function findInProject(projectDir, name, maxDepth = 2) {
|
|
7
|
+
function search(dir, depth) {
|
|
8
|
+
if (depth > maxDepth) return false;
|
|
9
|
+
try {
|
|
10
|
+
for (const entry of readdirSync(dir)) {
|
|
11
|
+
if (entry === 'node_modules' || entry === '.git' || entry === 'vendor') continue;
|
|
12
|
+
const full = join(dir, entry);
|
|
13
|
+
if (entry === name) return true;
|
|
14
|
+
try {
|
|
15
|
+
if (statSync(full).isDirectory() && depth < maxDepth) {
|
|
16
|
+
if (search(full, depth + 1)) return true;
|
|
17
|
+
}
|
|
18
|
+
} catch { /* skip */ }
|
|
19
|
+
}
|
|
20
|
+
} catch { /* skip */ }
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return search(projectDir, 0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function scaffoldDocs(projectDir, packagesDir, mode) {
|
|
27
|
+
mkdirSync(join(projectDir, 'docs', 'architecture'), { recursive: true });
|
|
28
|
+
mkdirSync(join(projectDir, 'docs', 'trees'), { recursive: true });
|
|
29
|
+
|
|
30
|
+
const tmplDir = join(packagesDir, 'core', 'docs-templates', 'architecture');
|
|
31
|
+
let tmplCount = 0;
|
|
32
|
+
|
|
33
|
+
if (existsSync(tmplDir)) {
|
|
34
|
+
tmplCount = copyDir(tmplDir, join(projectDir, 'docs', 'architecture'), mode, '.md');
|
|
35
|
+
info(`Copied ${tmplCount} architecture doc templates → docs/architecture/`);
|
|
36
|
+
|
|
37
|
+
let removed = 0;
|
|
38
|
+
|
|
39
|
+
if (!existsSync(join(projectDir, 'go.mod')) && !findInProject(projectDir, 'go.mod')) {
|
|
40
|
+
const p = join(projectDir, 'docs', 'architecture', 'backend-layers.md');
|
|
41
|
+
if (existsSync(p)) { rmSync(p); removed++; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!findInProject(projectDir, 'package.json')) {
|
|
45
|
+
const p = join(projectDir, 'docs', 'architecture', 'frontend-state.md');
|
|
46
|
+
if (existsSync(p)) { rmSync(p); removed++; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!findInProject(projectDir, 'migrations', 3) && !findInProject(projectDir, 'schema.prisma')) {
|
|
50
|
+
const p = join(projectDir, 'docs', 'architecture', 'database-schema.md');
|
|
51
|
+
if (existsSync(p)) { rmSync(p); removed++; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!findInProject(projectDir, 'Dockerfile') && !findInProject(projectDir, 'docker-compose.yml')) {
|
|
55
|
+
const p = join(projectDir, 'docs', 'architecture', 'deployment.md');
|
|
56
|
+
if (existsSync(p)) { rmSync(p); removed++; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (removed > 0) {
|
|
60
|
+
info(`Removed ${removed} irrelevant templates (auto-detected)`);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
info('Created docs/architecture/ and docs/trees/ directories');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
generateTree(projectDir);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function generateTree(projectDir) {
|
|
70
|
+
const treePath = join(projectDir, 'docs', 'trees', 'tree-project.md');
|
|
71
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
72
|
+
let treeContent;
|
|
73
|
+
|
|
74
|
+
if (commandExists('tree')) {
|
|
75
|
+
try {
|
|
76
|
+
treeContent = execSync(
|
|
77
|
+
`tree "${projectDir}" -I 'node_modules|.git|__pycache__|vendor|dist|build|.next|.cache|*.pyc|.DS_Store' --dirsfirst -L 3`,
|
|
78
|
+
{ stdio: 'pipe', maxBuffer: 1024 * 1024 }
|
|
79
|
+
).toString();
|
|
80
|
+
} catch {
|
|
81
|
+
treeContent = generateFallbackTree(projectDir);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
treeContent = generateFallbackTree(projectDir);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const content = [
|
|
88
|
+
'# Project Tree',
|
|
89
|
+
`> Auto-generated on ${date}. Regenerate with tree-generator agent.`,
|
|
90
|
+
'',
|
|
91
|
+
'```',
|
|
92
|
+
treeContent.trimEnd(),
|
|
93
|
+
'```',
|
|
94
|
+
''
|
|
95
|
+
].join('\n');
|
|
96
|
+
|
|
97
|
+
writeFileSync(treePath, content);
|
|
98
|
+
info('Generated project tree → docs/trees/tree-project.md');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function generateFallbackTree(rootDir, maxDepth = 3) {
|
|
102
|
+
const IGNORE = new Set([
|
|
103
|
+
'node_modules', '.git', '__pycache__', 'vendor', 'dist',
|
|
104
|
+
'build', '.next', '.cache', '.DS_Store'
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
const lines = [];
|
|
108
|
+
let count = 0;
|
|
109
|
+
const MAX_ENTRIES = 100;
|
|
110
|
+
|
|
111
|
+
function walk(dir, prefix, depth) {
|
|
112
|
+
if (depth > maxDepth || count >= MAX_ENTRIES) return;
|
|
113
|
+
let entries;
|
|
114
|
+
try {
|
|
115
|
+
entries = readdirSync(dir).filter(e => !IGNORE.has(e) && !e.endsWith('.pyc')).sort();
|
|
116
|
+
} catch { return; }
|
|
117
|
+
|
|
118
|
+
const dirs = entries.filter(e => {
|
|
119
|
+
try { return statSync(join(dir, e)).isDirectory(); } catch { return false; }
|
|
120
|
+
});
|
|
121
|
+
const files = entries.filter(e => {
|
|
122
|
+
try { return statSync(join(dir, e)).isFile(); } catch { return false; }
|
|
123
|
+
});
|
|
124
|
+
const sorted = [...dirs, ...files];
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < sorted.length && count < MAX_ENTRIES; i++) {
|
|
127
|
+
const entry = sorted[i];
|
|
128
|
+
const isLast = i === sorted.length - 1;
|
|
129
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
130
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
131
|
+
|
|
132
|
+
lines.push(`${prefix}${connector}${entry}`);
|
|
133
|
+
count++;
|
|
134
|
+
|
|
135
|
+
const full = join(dir, entry);
|
|
136
|
+
try {
|
|
137
|
+
if (statSync(full).isDirectory()) {
|
|
138
|
+
walk(full, prefix + childPrefix, depth + 1);
|
|
139
|
+
}
|
|
140
|
+
} catch { /* skip */ }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
lines.push('.');
|
|
145
|
+
walk(rootDir, '', 0);
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|