oh-my-customcodex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +327 -0
- package/dist/cli/index.js +32054 -0
- package/dist/index.js +5469 -0
- package/package.json +89 -0
- package/templates/.claude/agents/arch-documenter.md +38 -0
- package/templates/.claude/agents/arch-speckit-agent.md +66 -0
- package/templates/.claude/agents/be-django-expert.md +47 -0
- package/templates/.claude/agents/be-express-expert.md +31 -0
- package/templates/.claude/agents/be-fastapi-expert.md +45 -0
- package/templates/.claude/agents/be-go-backend-expert.md +45 -0
- package/templates/.claude/agents/be-nestjs-expert.md +29 -0
- package/templates/.claude/agents/be-springboot-expert.md +42 -0
- package/templates/.claude/agents/db-alembic-expert.md +73 -0
- package/templates/.claude/agents/db-postgres-expert.md +38 -0
- package/templates/.claude/agents/db-redis-expert.md +38 -0
- package/templates/.claude/agents/db-supabase-expert.md +37 -0
- package/templates/.claude/agents/de-airflow-expert.md +48 -0
- package/templates/.claude/agents/de-dbt-expert.md +36 -0
- package/templates/.claude/agents/de-kafka-expert.md +83 -0
- package/templates/.claude/agents/de-pipeline-expert.md +34 -0
- package/templates/.claude/agents/de-snowflake-expert.md +38 -0
- package/templates/.claude/agents/de-spark-expert.md +38 -0
- package/templates/.claude/agents/fe-design-expert.md +120 -0
- package/templates/.claude/agents/fe-flutter-agent.md +48 -0
- package/templates/.claude/agents/fe-svelte-agent.md +33 -0
- package/templates/.claude/agents/fe-vercel-agent.md +40 -0
- package/templates/.claude/agents/fe-vuejs-agent.md +34 -0
- package/templates/.claude/agents/infra-aws-expert.md +49 -0
- package/templates/.claude/agents/infra-docker-expert.md +49 -0
- package/templates/.claude/agents/lang-golang-expert.md +46 -0
- package/templates/.claude/agents/lang-java21-expert.md +42 -0
- package/templates/.claude/agents/lang-kotlin-expert.md +45 -0
- package/templates/.claude/agents/lang-python-expert.md +45 -0
- package/templates/.claude/agents/lang-rust-expert.md +45 -0
- package/templates/.claude/agents/lang-typescript-expert.md +45 -0
- package/templates/.claude/agents/mgr-claude-code-bible.md +62 -0
- package/templates/.claude/agents/mgr-creator.md +57 -0
- package/templates/.claude/agents/mgr-gitnerd.md +50 -0
- package/templates/.claude/agents/mgr-sauron.md +163 -0
- package/templates/.claude/agents/mgr-supplier.md +39 -0
- package/templates/.claude/agents/mgr-updater.md +40 -0
- package/templates/.claude/agents/qa-engineer.md +36 -0
- package/templates/.claude/agents/qa-planner.md +78 -0
- package/templates/.claude/agents/qa-writer.md +32 -0
- package/templates/.claude/agents/sec-codeql-expert.md +56 -0
- package/templates/.claude/agents/slack-cli-expert.md +98 -0
- package/templates/.claude/agents/souls/lang-golang-expert.soul.md +21 -0
- package/templates/.claude/agents/sys-memory-keeper.md +123 -0
- package/templates/.claude/agents/sys-naggy.md +76 -0
- package/templates/.claude/agents/tool-bun-expert.md +26 -0
- package/templates/.claude/agents/tool-npm-expert.md +31 -0
- package/templates/.claude/agents/tool-optimizer.md +37 -0
- package/templates/.claude/agents/wiki-curator.md +72 -0
- package/templates/.claude/config/required-plugins.json +30 -0
- package/templates/.claude/contexts/dev.md +20 -0
- package/templates/.claude/contexts/ecomode.md +110 -0
- package/templates/.claude/contexts/index.yaml +41 -0
- package/templates/.claude/contexts/research.md +28 -0
- package/templates/.claude/contexts/review.md +23 -0
- package/templates/.claude/hooks/hooks.json +533 -0
- package/templates/.claude/hooks/scripts/adaptive-harness-scan.sh +45 -0
- package/templates/.claude/hooks/scripts/agent-start-recorder.sh +40 -0
- package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +76 -0
- package/templates/.claude/hooks/scripts/audit-log.sh +64 -0
- package/templates/.claude/hooks/scripts/auto-continue-guard.sh +33 -0
- package/templates/.claude/hooks/scripts/content-hash-validator.sh +75 -0
- package/templates/.claude/hooks/scripts/context-budget-advisor.sh +107 -0
- package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +71 -0
- package/templates/.claude/hooks/scripts/cwd-change-detector.sh +36 -0
- package/templates/.claude/hooks/scripts/eval-core-batch-save.sh +46 -0
- package/templates/.claude/hooks/scripts/feedback-collector.sh +92 -0
- package/templates/.claude/hooks/scripts/file-change-validator.sh +26 -0
- package/templates/.claude/hooks/scripts/git-delegation-guard.sh +57 -0
- package/templates/.claude/hooks/scripts/model-escalation-advisor.sh +106 -0
- package/templates/.claude/hooks/scripts/omcodex-auto-update.sh +4 -0
- package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +177 -0
- package/templates/.claude/hooks/scripts/rtk-intercept.sh +77 -0
- package/templates/.claude/hooks/scripts/rule-deletion-guard.sh +60 -0
- package/templates/.claude/hooks/scripts/schema-validator.sh +106 -0
- package/templates/.claude/hooks/scripts/secret-filter.sh +100 -0
- package/templates/.claude/hooks/scripts/session-autofix-prompt.sh +34 -0
- package/templates/.claude/hooks/scripts/session-autofix.sh +146 -0
- package/templates/.claude/hooks/scripts/session-env-check.sh +254 -0
- package/templates/.claude/hooks/scripts/skill-extractor-analyzer.sh +49 -0
- package/templates/.claude/hooks/scripts/stage-blocker.sh +16 -0
- package/templates/.claude/hooks/scripts/stale-todo-scanner.sh +91 -0
- package/templates/.claude/hooks/scripts/stall-detection-advisor.sh +112 -0
- package/templates/.claude/hooks/scripts/stop-console-audit.sh +46 -0
- package/templates/.claude/hooks/scripts/stuck-detector.sh +199 -0
- package/templates/.claude/hooks/scripts/task-outcome-recorder.sh +119 -0
- package/templates/.claude/hooks/scripts/task-state-precompact.sh +58 -0
- package/templates/.claude/hooks/scripts/user-prompt-preprocessor.sh +32 -0
- package/templates/.claude/hooks/skill-count-reminder.sh +34 -0
- package/templates/.claude/install-hooks.sh +100 -0
- package/templates/.claude/ontology/agents.yaml +546 -0
- package/templates/.claude/ontology/graphs/agent-skill.json +102 -0
- package/templates/.claude/ontology/graphs/full-graph.json +629 -0
- package/templates/.claude/ontology/graphs/routing.json +112 -0
- package/templates/.claude/ontology/graphs/skill-rule.json +78 -0
- package/templates/.claude/ontology/rules.yaml +251 -0
- package/templates/.claude/ontology/schema.yaml +144 -0
- package/templates/.claude/ontology/skills.yaml +575 -0
- package/templates/.claude/rules/MAY-optimization.md +42 -0
- package/templates/.claude/rules/MUST-agent-design.md +340 -0
- package/templates/.claude/rules/MUST-agent-identification.md +77 -0
- package/templates/.claude/rules/MUST-agent-teams.md +293 -0
- package/templates/.claude/rules/MUST-completion-verification.md +112 -0
- package/templates/.claude/rules/MUST-continuous-improvement.md +69 -0
- package/templates/.claude/rules/MUST-enforcement-policy.md +50 -0
- package/templates/.claude/rules/MUST-intent-transparency.md +74 -0
- package/templates/.claude/rules/MUST-language-policy.md +28 -0
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +399 -0
- package/templates/.claude/rules/MUST-parallel-execution.md +184 -0
- package/templates/.claude/rules/MUST-permissions.md +32 -0
- package/templates/.claude/rules/MUST-safety.md +23 -0
- package/templates/.claude/rules/MUST-sync-verification.md +145 -0
- package/templates/.claude/rules/MUST-tool-identification.md +97 -0
- package/templates/.claude/rules/SHOULD-ecomode.md +123 -0
- package/templates/.claude/rules/SHOULD-error-handling.md +33 -0
- package/templates/.claude/rules/SHOULD-hud-statusline.md +51 -0
- package/templates/.claude/rules/SHOULD-interaction.md +77 -0
- package/templates/.claude/rules/SHOULD-memory-integration.md +371 -0
- package/templates/.claude/rules/SHOULD-ontology-rag-routing.md +49 -0
- package/templates/.claude/rules/SHOULD-wiki-sync.md +73 -0
- package/templates/.claude/rules/index.yaml +141 -0
- package/templates/.claude/schemas/tool-inputs.json +62 -0
- package/templates/.claude/skills/action-validator/SKILL.md +89 -0
- package/templates/.claude/skills/adaptive-harness/SKILL.md +335 -0
- package/templates/.claude/skills/adversarial-review/SKILL.md +80 -0
- package/templates/.claude/skills/agora/SKILL.md +194 -0
- package/templates/.claude/skills/airflow-best-practices/SKILL.md +94 -0
- package/templates/.claude/skills/alembic-best-practices/SKILL.md +295 -0
- package/templates/.claude/skills/ambiguity-gate/SKILL.md +94 -0
- package/templates/.claude/skills/analysis/SKILL.md +223 -0
- package/templates/.claude/skills/audit-agents/SKILL.md +118 -0
- package/templates/.claude/skills/aws-best-practices/SKILL.md +281 -0
- package/templates/.claude/skills/claude-code-bible/SKILL.md +93 -0
- package/templates/.claude/skills/claude-code-bible/scripts/fetch-docs.js +244 -0
- package/templates/.claude/skills/claude-native/SKILL.md +215 -0
- package/templates/.claude/skills/codex-exec/SKILL.md +206 -0
- package/templates/.claude/skills/codex-exec/scripts/codex-wrapper.cjs +430 -0
- package/templates/.claude/skills/create-agent/SKILL.md +94 -0
- package/templates/.claude/skills/cve-triage/SKILL.md +91 -0
- package/templates/.claude/skills/dag-orchestration/SKILL.md +201 -0
- package/templates/.claude/skills/dbt-best-practices/SKILL.md +55 -0
- package/templates/.claude/skills/de-lead-routing/SKILL.md +230 -0
- package/templates/.claude/skills/deep-plan/SKILL.md +344 -0
- package/templates/.claude/skills/deep-verify/SKILL.md +111 -0
- package/templates/.claude/skills/dev-lead-routing/SKILL.md +161 -0
- package/templates/.claude/skills/dev-refactor/SKILL.md +234 -0
- package/templates/.claude/skills/dev-review/SKILL.md +172 -0
- package/templates/.claude/skills/django-best-practices/SKILL.md +334 -0
- package/templates/.claude/skills/docker-best-practices/SKILL.md +276 -0
- package/templates/.claude/skills/evaluator-optimizer/SKILL.md +421 -0
- package/templates/.claude/skills/fastapi-best-practices/SKILL.md +271 -0
- package/templates/.claude/skills/fix-refs/SKILL.md +109 -0
- package/templates/.claude/skills/flutter-best-practices/SKILL.md +325 -0
- package/templates/.claude/skills/gemini-exec/SKILL.md +215 -0
- package/templates/.claude/skills/gemini-exec/scripts/gemini-wrapper.cjs +485 -0
- package/templates/.claude/skills/go-backend-best-practices/SKILL.md +135 -0
- package/templates/.claude/skills/go-best-practices/SKILL.md +204 -0
- package/templates/.claude/skills/hada-scout/SKILL.md +92 -0
- package/templates/.claude/skills/harness-eval/SKILL.md +95 -0
- package/templates/.claude/skills/harness-synthesizer/SKILL.md +145 -0
- package/templates/.claude/skills/help/SKILL.md +127 -0
- package/templates/.claude/skills/idea/SKILL.md +88 -0
- package/templates/.claude/skills/impeccable-design/SKILL.md +173 -0
- package/templates/.claude/skills/intent-detection/SKILL.md +293 -0
- package/templates/.claude/skills/intent-detection/patterns/agent-triggers.yaml +438 -0
- package/templates/.claude/skills/java21-best-practices/SKILL.md +190 -0
- package/templates/.claude/skills/jinja2-prompts/SKILL.md +86 -0
- package/templates/.claude/skills/kafka-best-practices/SKILL.md +53 -0
- package/templates/.claude/skills/kotlin-best-practices/SKILL.md +257 -0
- package/templates/.claude/skills/lists/SKILL.md +80 -0
- package/templates/.claude/skills/memory-management/SKILL.md +196 -0
- package/templates/.claude/skills/memory-recall/SKILL.md +172 -0
- package/templates/.claude/skills/memory-save/SKILL.md +128 -0
- package/templates/.claude/skills/model-escalation/SKILL.md +62 -0
- package/templates/.claude/skills/monitoring-setup/SKILL.md +215 -0
- package/templates/.claude/skills/multi-model-verification/SKILL.md +130 -0
- package/templates/.claude/skills/npm-audit/SKILL.md +74 -0
- package/templates/.claude/skills/npm-publish/SKILL.md +65 -0
- package/templates/.claude/skills/npm-version/SKILL.md +104 -0
- package/templates/.claude/skills/omcodex-auto-improve/SKILL.md +136 -0
- package/templates/.claude/skills/omcodex-feedback/SKILL.md +205 -0
- package/templates/.claude/skills/omcodex-improve-report/SKILL.md +65 -0
- package/templates/.claude/skills/omcodex-loop/SKILL.md +45 -0
- package/templates/.claude/skills/omcodex-release-notes/SKILL.md +117 -0
- package/templates/.claude/skills/omcodex-takeover/SKILL.md +115 -0
- package/templates/.claude/skills/omcodex-web/SKILL.md +95 -0
- package/templates/.claude/skills/optimize-analyze/SKILL.md +57 -0
- package/templates/.claude/skills/optimize-bundle/SKILL.md +69 -0
- package/templates/.claude/skills/optimize-report/SKILL.md +76 -0
- package/templates/.claude/skills/peer-messaging/SKILL.md +59 -0
- package/templates/.claude/skills/pipeline/SKILL.md +103 -0
- package/templates/.claude/skills/pipeline-architecture-patterns/SKILL.md +84 -0
- package/templates/.claude/skills/pipeline-guards/SKILL.md +173 -0
- package/templates/.claude/skills/post-release-followup/SKILL.md +134 -0
- package/templates/.claude/skills/postgres-best-practices/SKILL.md +67 -0
- package/templates/.claude/skills/pr-auto-improve/SKILL.md +129 -0
- package/templates/.claude/skills/professor-triage/SKILL.md +321 -0
- package/templates/.claude/skills/python-best-practices/SKILL.md +223 -0
- package/templates/.claude/skills/qa-lead-routing/SKILL.md +104 -0
- package/templates/.claude/skills/react-best-practices/SKILL.md +102 -0
- package/templates/.claude/skills/reasoning-sandwich/SKILL.md +64 -0
- package/templates/.claude/skills/redis-best-practices/SKILL.md +84 -0
- package/templates/.claude/skills/release-plan/SKILL.md +207 -0
- package/templates/.claude/skills/research/SKILL.md +493 -0
- package/templates/.claude/skills/result-aggregation/SKILL.md +165 -0
- package/templates/.claude/skills/rtk-exec/SKILL.md +199 -0
- package/templates/.claude/skills/rtk-exec/scripts/rtk-wrapper.cjs +377 -0
- package/templates/.claude/skills/rust-best-practices/SKILL.md +268 -0
- package/templates/.claude/skills/sauron-watch/SKILL.md +239 -0
- package/templates/.claude/skills/scout/SKILL.md +250 -0
- package/templates/.claude/skills/sdd/SKILL.md +24 -0
- package/templates/.claude/skills/sdd-dev/SKILL.md +257 -0
- package/templates/.claude/skills/sdd-development/SKILL.md +24 -0
- package/templates/.claude/skills/secretary-routing/SKILL.md +132 -0
- package/templates/.claude/skills/skill-extractor/SKILL.md +155 -0
- package/templates/.claude/skills/skills-sh-search/SKILL.md +210 -0
- package/templates/.claude/skills/snowflake-best-practices/SKILL.md +66 -0
- package/templates/.claude/skills/spark-best-practices/SKILL.md +53 -0
- package/templates/.claude/skills/springboot-best-practices/SKILL.md +74 -0
- package/templates/.claude/skills/springboot-best-practices/examples/config-properties-example.java +22 -0
- package/templates/.claude/skills/springboot-best-practices/examples/controller-example.java +28 -0
- package/templates/.claude/skills/springboot-best-practices/examples/controller-test-example.java +33 -0
- package/templates/.claude/skills/springboot-best-practices/examples/entity-example.java +22 -0
- package/templates/.claude/skills/springboot-best-practices/examples/exception-handler-example.java +30 -0
- package/templates/.claude/skills/springboot-best-practices/examples/repository-example.java +17 -0
- package/templates/.claude/skills/springboot-best-practices/examples/repository-test-example.java +23 -0
- package/templates/.claude/skills/springboot-best-practices/examples/security-config-example.java +27 -0
- package/templates/.claude/skills/springboot-best-practices/examples/service-example.java +33 -0
- package/templates/.claude/skills/status/SKILL.md +155 -0
- package/templates/.claude/skills/structured-dev-cycle/SKILL.md +200 -0
- package/templates/.claude/skills/stuck-recovery/SKILL.md +80 -0
- package/templates/.claude/skills/supabase-postgres-best-practices/SKILL.md +100 -0
- package/templates/.claude/skills/systematic-debugging/SKILL.md +288 -0
- package/templates/.claude/skills/systematic-debugging/condition-based-waiting-example.ts +278 -0
- package/templates/.claude/skills/systematic-debugging/condition-based-waiting.md +240 -0
- package/templates/.claude/skills/systematic-debugging/defense-in-depth.md +252 -0
- package/templates/.claude/skills/systematic-debugging/find-polluter.sh +147 -0
- package/templates/.claude/skills/systematic-debugging/root-cause-tracing.md +87 -0
- package/templates/.claude/skills/task-decomposition/SKILL.md +197 -0
- package/templates/.claude/skills/typescript-best-practices/SKILL.md +322 -0
- package/templates/.claude/skills/update-docs/SKILL.md +142 -0
- package/templates/.claude/skills/update-external/SKILL.md +169 -0
- package/templates/.claude/skills/vercel-deploy/SKILL.md +75 -0
- package/templates/.claude/skills/web-design-guidelines/SKILL.md +119 -0
- package/templates/.claude/skills/wiki/SKILL.md +426 -0
- package/templates/.claude/skills/wiki-rag/SKILL.md +154 -0
- package/templates/.claude/skills/worker-reviewer-pipeline/SKILL.md +165 -0
- package/templates/.claude/skills/writing-clearly-and-concisely/SKILL.md +66 -0
- package/templates/.claude/statusline.sh +380 -0
- package/templates/.claude/uninstall-hooks.sh +52 -0
- package/templates/.github/workflows/wiki-sync.yml +132 -0
- package/templates/AGENTS.md.en +255 -0
- package/templates/AGENTS.md.ko +255 -0
- package/templates/CLAUDE.md +263 -0
- package/templates/CLAUDE.md.en +256 -0
- package/templates/CLAUDE.md.ko +256 -0
- package/templates/deprecated-files.json +10 -0
- package/templates/guides/agents-md-quality/README.md +110 -0
- package/templates/guides/airflow/README.md +47 -0
- package/templates/guides/alembic/README.md +438 -0
- package/templates/guides/aws/common-patterns.md +169 -0
- package/templates/guides/aws/index.yaml +26 -0
- package/templates/guides/aws/well-architected.md +143 -0
- package/templates/guides/cc-token-saver/README.md +97 -0
- package/templates/guides/claude-code/01-overview.md +42 -0
- package/templates/guides/claude-code/03-tools.md +107 -0
- package/templates/guides/claude-code/04-agent-skills.md +90 -0
- package/templates/guides/claude-code/05-agent-sdk.md +129 -0
- package/templates/guides/claude-code/06-mcp.md +165 -0
- package/templates/guides/claude-code/07-prompt-engineering.md +100 -0
- package/templates/guides/claude-code/08-testing.md +56 -0
- package/templates/guides/claude-code/09-guardrails.md +78 -0
- package/templates/guides/claude-code/10-monitoring.md +87 -0
- package/templates/guides/claude-code/11-sub-agents.md +159 -0
- package/templates/guides/claude-code/12-workflow-patterns.md +182 -0
- package/templates/guides/claude-code/13-cli-flags.md +151 -0
- package/templates/guides/claude-code/index.yaml +61 -0
- package/templates/guides/dbt/README.md +32 -0
- package/templates/guides/django-best-practices/README.md +476 -0
- package/templates/guides/docker/compose-best-practices.md +284 -0
- package/templates/guides/docker/dockerfile-best-practices.md +262 -0
- package/templates/guides/docker/index.yaml +26 -0
- package/templates/guides/drizzle-orm/README.md +69 -0
- package/templates/guides/elements-of-style/elements-of-style.html +2609 -0
- package/templates/guides/fastapi/best-practices.md +232 -0
- package/templates/guides/fastapi/index.yaml +21 -0
- package/templates/guides/flutter/architecture.md +141 -0
- package/templates/guides/flutter/fundamentals.md +119 -0
- package/templates/guides/flutter/index.yaml +44 -0
- package/templates/guides/flutter/performance.md +119 -0
- package/templates/guides/flutter/security.md +120 -0
- package/templates/guides/flutter/state-management.md +144 -0
- package/templates/guides/flutter/testing.md +155 -0
- package/templates/guides/git-worktree-workflow/README.md +138 -0
- package/templates/guides/go-backend/index.yaml +26 -0
- package/templates/guides/go-backend/project-layout.md +243 -0
- package/templates/guides/go-backend/uber-style.md +212 -0
- package/templates/guides/golang/concurrency.md +282 -0
- package/templates/guides/golang/effective-go.md +309 -0
- package/templates/guides/golang/error-handling.md +250 -0
- package/templates/guides/golang/index.yaml +27 -0
- package/templates/guides/hook-data-flow/README.md +135 -0
- package/templates/guides/iceberg/README.md +49 -0
- package/templates/guides/impeccable-design/color-and-contrast.md +278 -0
- package/templates/guides/impeccable-design/index.yaml +12 -0
- package/templates/guides/impeccable-design/motion-design.md +390 -0
- package/templates/guides/impeccable-design/typography.md +386 -0
- package/templates/guides/impeccable-design/ux-writing.md +400 -0
- package/templates/guides/index.yaml +265 -0
- package/templates/guides/java21/index.yaml +29 -0
- package/templates/guides/java21/java-style-guide.md +248 -0
- package/templates/guides/java21/modern-java21.md +303 -0
- package/templates/guides/kafka/README.md +32 -0
- package/templates/guides/kotlin/coding-conventions.md +247 -0
- package/templates/guides/kotlin/idioms.md +234 -0
- package/templates/guides/kotlin/index.yaml +26 -0
- package/templates/guides/multi-model-routing/README.md +101 -0
- package/templates/guides/multi-provider-exec/README.md +83 -0
- package/templates/guides/postgres/README.md +58 -0
- package/templates/guides/python/index.yaml +26 -0
- package/templates/guides/python/pep8-style-guide.md +202 -0
- package/templates/guides/python/zen-of-python.md +79 -0
- package/templates/guides/redis/README.md +50 -0
- package/templates/guides/rust/error-handling.md +262 -0
- package/templates/guides/rust/index.yaml +26 -0
- package/templates/guides/rust/ownership.md +180 -0
- package/templates/guides/skill-bundle-design/README.md +106 -0
- package/templates/guides/slack-cli/README.md +145 -0
- package/templates/guides/snowflake/README.md +32 -0
- package/templates/guides/spark/README.md +32 -0
- package/templates/guides/springboot/best-practices.md +361 -0
- package/templates/guides/springboot/index.yaml +22 -0
- package/templates/guides/supabase-postgres/README.md +32 -0
- package/templates/guides/supabase-postgres/index.yaml +19 -0
- package/templates/guides/typescript/advanced-types.md +225 -0
- package/templates/guides/typescript/index.yaml +26 -0
- package/templates/guides/typescript/type-system.md +219 -0
- package/templates/guides/web-design/accessibility.md +66 -0
- package/templates/guides/web-design/index.yaml +20 -0
- package/templates/guides/web-design/performance.md +102 -0
- package/templates/guides/web-scraping/README.md +926 -0
- package/templates/guides/web-scraping/index.yaml +19 -0
- package/templates/guides/worktree-lifecycle/README.md +104 -0
- package/templates/index.yaml +18 -0
- package/templates/manifest.json +49 -0
- package/templates/workflows/auto-dev.yaml +46 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Condition-Based Waiting
|
|
2
|
+
|
|
3
|
+
<!-- Source: https://github.com/tmdgusya/engineering-disciplines (MIT License) -->
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Arbitrary delays (`sleep`, `setTimeout`, `await new Promise(r => setTimeout(r, 1000))`) are a debugging anti-pattern. They hide timing issues, make tests slow, and still fail intermittently.
|
|
8
|
+
|
|
9
|
+
**Core principle:** Replace arbitrary delays with condition-based polling that waits for the actual state you need.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
**Use when:**
|
|
14
|
+
- Tests use `await sleep(1000)` or similar arbitrary waits
|
|
15
|
+
- Code has `setTimeout` for "giving time" to async operations
|
|
16
|
+
- Tests are flaky because timing depends on system speed
|
|
17
|
+
- You need to wait for: file to exist, process to start, server to be ready, database record to appear
|
|
18
|
+
|
|
19
|
+
## The Problem with Arbitrary Delays
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ❌ Arbitrary delay - fragile and slow
|
|
23
|
+
await fs.writeFile(path, content);
|
|
24
|
+
await sleep(500); // "Give it time to write"
|
|
25
|
+
const result = await fs.readFile(path);
|
|
26
|
+
|
|
27
|
+
// Problems:
|
|
28
|
+
// 1. 500ms may not be enough on slow systems
|
|
29
|
+
// 2. 500ms is always wasted on fast systems
|
|
30
|
+
// 3. The actual condition (file readable) is never verified
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## The Solution: Condition-Based Waiting
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// ✓ Condition-based - fast and reliable
|
|
37
|
+
await fs.writeFile(path, content);
|
|
38
|
+
await waitUntil(() => fs.access(path).then(() => true).catch(() => false));
|
|
39
|
+
const result = await fs.readFile(path);
|
|
40
|
+
|
|
41
|
+
// Benefits:
|
|
42
|
+
// 1. Proceeds as soon as condition is met (fast on fast systems)
|
|
43
|
+
// 2. Has a timeout for safety (catches real failures)
|
|
44
|
+
// 3. The actual condition is explicit and verified
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Core Implementation
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
interface WaitOptions {
|
|
51
|
+
timeout?: number; // Max wait time in ms (default: 5000)
|
|
52
|
+
interval?: number; // Poll interval in ms (default: 100)
|
|
53
|
+
message?: string; // Error message on timeout
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function waitUntil(
|
|
57
|
+
condition: () => boolean | Promise<boolean>,
|
|
58
|
+
options: WaitOptions = {}
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const { timeout = 5000, interval = 100, message = 'Condition not met' } = options;
|
|
61
|
+
const deadline = Date.now() + timeout;
|
|
62
|
+
|
|
63
|
+
while (Date.now() < deadline) {
|
|
64
|
+
const result = await condition();
|
|
65
|
+
if (result) return;
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(`waitUntil timeout after ${timeout}ms: ${message}`);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See `condition-based-waiting-example.ts` for the complete implementation with all utilities.
|
|
74
|
+
|
|
75
|
+
## Common Patterns
|
|
76
|
+
|
|
77
|
+
### Wait for File to Exist
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// ❌ Arbitrary delay
|
|
81
|
+
await triggerFileCreation();
|
|
82
|
+
await sleep(1000);
|
|
83
|
+
const content = await fs.readFile(outputPath, 'utf-8');
|
|
84
|
+
|
|
85
|
+
// ✓ Condition-based
|
|
86
|
+
await triggerFileCreation();
|
|
87
|
+
await waitUntil(
|
|
88
|
+
() => fs.access(outputPath).then(() => true).catch(() => false),
|
|
89
|
+
{ timeout: 5000, message: `File not created: ${outputPath}` }
|
|
90
|
+
);
|
|
91
|
+
const content = await fs.readFile(outputPath, 'utf-8');
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Wait for Process to Start
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// ❌ Arbitrary delay
|
|
98
|
+
startServer();
|
|
99
|
+
await sleep(2000); // "Give server time to start"
|
|
100
|
+
const response = await fetch('http://localhost:3000/health');
|
|
101
|
+
|
|
102
|
+
// ✓ Condition-based
|
|
103
|
+
startServer();
|
|
104
|
+
await waitUntil(
|
|
105
|
+
async () => {
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch('http://localhost:3000/health');
|
|
108
|
+
return res.ok;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{ timeout: 10000, message: 'Server did not start within 10s' }
|
|
114
|
+
);
|
|
115
|
+
const response = await fetch('http://localhost:3000/health');
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Wait for Database Record
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// ❌ Arbitrary delay
|
|
122
|
+
await triggerAsyncOperation();
|
|
123
|
+
await sleep(500);
|
|
124
|
+
const record = await db.find({ id: expectedId });
|
|
125
|
+
|
|
126
|
+
// ✓ Condition-based
|
|
127
|
+
await triggerAsyncOperation();
|
|
128
|
+
await waitUntil(
|
|
129
|
+
async () => {
|
|
130
|
+
const record = await db.find({ id: expectedId });
|
|
131
|
+
return record !== null;
|
|
132
|
+
},
|
|
133
|
+
{ timeout: 3000, message: `Record ${expectedId} not created` }
|
|
134
|
+
);
|
|
135
|
+
const record = await db.find({ id: expectedId });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Wait for Log Output
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// ❌ Arbitrary delay
|
|
142
|
+
process.spawn('my-command');
|
|
143
|
+
await sleep(1000);
|
|
144
|
+
expect(logOutput).toContain('Server started');
|
|
145
|
+
|
|
146
|
+
// ✓ Condition-based
|
|
147
|
+
const logOutput: string[] = [];
|
|
148
|
+
const proc = process.spawn('my-command');
|
|
149
|
+
proc.stdout.on('data', (chunk) => logOutput.push(chunk.toString()));
|
|
150
|
+
|
|
151
|
+
await waitUntil(
|
|
152
|
+
() => logOutput.some(line => line.includes('Server started')),
|
|
153
|
+
{ timeout: 10000, message: 'Server start message not seen in logs' }
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Wait for Count to Reach Expected Value
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// ❌ Arbitrary delay
|
|
161
|
+
await triggerBatchOperation();
|
|
162
|
+
await sleep(2000);
|
|
163
|
+
const items = await db.findAll();
|
|
164
|
+
expect(items.length).toBe(10);
|
|
165
|
+
|
|
166
|
+
// ✓ Condition-based
|
|
167
|
+
await triggerBatchOperation();
|
|
168
|
+
await waitUntil(
|
|
169
|
+
async () => {
|
|
170
|
+
const items = await db.findAll();
|
|
171
|
+
return items.length >= 10;
|
|
172
|
+
},
|
|
173
|
+
{ timeout: 5000, message: 'Batch did not complete: expected 10 items' }
|
|
174
|
+
);
|
|
175
|
+
const items = await db.findAll();
|
|
176
|
+
expect(items.length).toBe(10);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Choosing Timeout and Interval Values
|
|
180
|
+
|
|
181
|
+
| Scenario | Timeout | Interval | Rationale |
|
|
182
|
+
|----------|---------|----------|-----------|
|
|
183
|
+
| File write | 2s | 50ms | Fast local I/O |
|
|
184
|
+
| Process start | 10s | 200ms | Process startup varies |
|
|
185
|
+
| HTTP server ready | 15s | 300ms | Server may need to compile |
|
|
186
|
+
| Database record | 3s | 100ms | DB writes are fast |
|
|
187
|
+
| CI environment | 2-3x local | same | CI is often slower |
|
|
188
|
+
|
|
189
|
+
**Rule of thumb:**
|
|
190
|
+
- Interval: 10-20% of expected wait time, minimum 50ms
|
|
191
|
+
- Timeout: 3-5x the typical wait time
|
|
192
|
+
- Add a buffer for CI: multiply timeout by 2-3x
|
|
193
|
+
|
|
194
|
+
## Testing the Waiters Themselves
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// Test that waitUntil resolves when condition becomes true
|
|
198
|
+
it('resolves when condition becomes true', async () => {
|
|
199
|
+
let ready = false;
|
|
200
|
+
setTimeout(() => { ready = true; }, 100);
|
|
201
|
+
|
|
202
|
+
await expect(
|
|
203
|
+
waitUntil(() => ready, { timeout: 1000 })
|
|
204
|
+
).resolves.toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Test that waitUntil rejects on timeout
|
|
208
|
+
it('rejects with timeout error when condition never met', async () => {
|
|
209
|
+
await expect(
|
|
210
|
+
waitUntil(() => false, { timeout: 100, message: 'test condition' })
|
|
211
|
+
).rejects.toThrow('waitUntil timeout after 100ms: test condition');
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Migration Guide
|
|
216
|
+
|
|
217
|
+
When refactoring existing `sleep` calls:
|
|
218
|
+
|
|
219
|
+
1. **Identify what the sleep is "waiting for"** — read surrounding code
|
|
220
|
+
2. **Find the observable state change** — what becomes true when the operation completes?
|
|
221
|
+
3. **Replace with `waitUntil(condition)`** — poll for that state
|
|
222
|
+
4. **Set appropriate timeout** — 3-5x the typical wait time
|
|
223
|
+
5. **Add descriptive message** — what should have happened?
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// Before
|
|
227
|
+
await startProcess();
|
|
228
|
+
await sleep(2000);
|
|
229
|
+
checkResult();
|
|
230
|
+
|
|
231
|
+
// After - step 1: what is sleep waiting for? Process to be ready.
|
|
232
|
+
// After - step 2: observable state? Process responds to health check.
|
|
233
|
+
// After - step 3-5:
|
|
234
|
+
await startProcess();
|
|
235
|
+
await waitUntil(
|
|
236
|
+
() => isProcessReady(),
|
|
237
|
+
{ timeout: 10000, message: 'Process did not become ready' }
|
|
238
|
+
);
|
|
239
|
+
checkResult();
|
|
240
|
+
```
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Defense-in-Depth Validation
|
|
2
|
+
|
|
3
|
+
<!-- Source: https://github.com/tmdgusya/engineering-disciplines (MIT License) -->
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Instead of fixing bugs reactively, add validation at every layer so bugs become structurally impossible. Each layer catches what the previous layer missed.
|
|
8
|
+
|
|
9
|
+
**Core principle:** Don't just fix the bug. Add a guard that makes the bug impossible at every layer that should have caught it.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
**Use when:**
|
|
14
|
+
- The same class of bug keeps appearing in different places
|
|
15
|
+
- A bug got through multiple layers that should have caught it
|
|
16
|
+
- You want to prevent regression of a hard-to-reproduce bug
|
|
17
|
+
- Building new features where invalid state could cause harm
|
|
18
|
+
|
|
19
|
+
## The Four Layers
|
|
20
|
+
|
|
21
|
+
### Layer 1: Input Validation (Entry Points)
|
|
22
|
+
|
|
23
|
+
Validate at the boundary where data enters your system.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// ❌ No validation - bug can enter the system
|
|
27
|
+
async function createSession(projectDir: string): Promise<Session> {
|
|
28
|
+
return new Session(projectDir);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ✓ Validate at entry - bug is caught immediately
|
|
32
|
+
async function createSession(projectDir: string): Promise<Session> {
|
|
33
|
+
if (!projectDir || projectDir.trim() === '') {
|
|
34
|
+
throw new Error(`createSession: projectDir must be a non-empty string, got: ${JSON.stringify(projectDir)}`);
|
|
35
|
+
}
|
|
36
|
+
return new Session(projectDir);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**What to validate at entry points:**
|
|
41
|
+
- Non-empty strings for required path/name fields
|
|
42
|
+
- Valid ranges for numeric values
|
|
43
|
+
- Required fields exist in objects
|
|
44
|
+
- File/directory exists when required
|
|
45
|
+
|
|
46
|
+
### Layer 2: Constructor Guards (Object Creation)
|
|
47
|
+
|
|
48
|
+
Validate in constructors so objects can never be created in invalid state.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ❌ Object can be created with invalid state
|
|
52
|
+
class Session {
|
|
53
|
+
constructor(private projectDir: string) {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ✓ Constructor prevents invalid state
|
|
57
|
+
class Session {
|
|
58
|
+
constructor(private projectDir: string) {
|
|
59
|
+
if (!projectDir || projectDir.trim() === '') {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Session: projectDir must be non-empty, got: ${JSON.stringify(projectDir)}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Constructor guard pattern:**
|
|
69
|
+
```typescript
|
|
70
|
+
class WorktreeManager {
|
|
71
|
+
constructor(private baseDir: string, private sessionId: string) {
|
|
72
|
+
if (!baseDir) throw new Error('WorktreeManager: baseDir is required');
|
|
73
|
+
if (!sessionId) throw new Error('WorktreeManager: sessionId is required');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Layer 3: Method Preconditions (Critical Operations)
|
|
79
|
+
|
|
80
|
+
Add guards before operations that could cause harm if called with bad state.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ❌ No precondition - git init could run in wrong directory
|
|
84
|
+
async function initializeWorktree(): Promise<void> {
|
|
85
|
+
await execFileAsync('git', ['init'], { cwd: this.projectDir });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ✓ Precondition - verify state is valid before dangerous operation
|
|
89
|
+
async function initializeWorktree(): Promise<void> {
|
|
90
|
+
if (!this.projectDir) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`initializeWorktree: projectDir is not set. ` +
|
|
93
|
+
`This likely means the object was created with an empty path.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
await execFileAsync('git', ['init'], { cwd: this.projectDir });
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**When to add method preconditions:**
|
|
101
|
+
- Before file system operations (read, write, delete)
|
|
102
|
+
- Before network calls with user-provided data
|
|
103
|
+
- Before database writes
|
|
104
|
+
- Before spawning processes with user-provided arguments
|
|
105
|
+
|
|
106
|
+
### Layer 4: Test Assertions (Regression Prevention)
|
|
107
|
+
|
|
108
|
+
Add assertions in tests that would catch the bug early and clearly.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// ❌ Test doesn't verify the path is correct
|
|
112
|
+
it('should initialize session', async () => {
|
|
113
|
+
const session = await createSession(tempDir);
|
|
114
|
+
expect(session).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ✓ Test verifies the critical invariant
|
|
118
|
+
it('should initialize session with correct project directory', async () => {
|
|
119
|
+
const session = await createSession(tempDir);
|
|
120
|
+
expect(session).toBeDefined();
|
|
121
|
+
// Assert the invariant that was violated
|
|
122
|
+
expect(session.projectDir).toBe(tempDir);
|
|
123
|
+
expect(session.projectDir).not.toBe('');
|
|
124
|
+
expect(session.projectDir).not.toBe(process.cwd());
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Test assertion patterns:**
|
|
129
|
+
```typescript
|
|
130
|
+
// Assert paths are absolute and within expected location
|
|
131
|
+
expect(path.isAbsolute(result.dir)).toBe(true);
|
|
132
|
+
expect(result.dir.startsWith(tempDir)).toBe(true);
|
|
133
|
+
|
|
134
|
+
// Assert values match what was provided (not defaults or fallbacks)
|
|
135
|
+
expect(result.name).toBe(providedName);
|
|
136
|
+
expect(result.id).not.toBe('');
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Applying Defense-in-Depth
|
|
140
|
+
|
|
141
|
+
When you find a bug, don't just fix it. Add guards at ALL four layers:
|
|
142
|
+
|
|
143
|
+
### Step 1: Fix the immediate bug
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Fix the actual bug first
|
|
147
|
+
const context = setupCoreTest();
|
|
148
|
+
await context.initialize(); // Add the missing initialization
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Step 2: Add input validation
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
export function createProject(name: string, dir: string): Project {
|
|
155
|
+
if (!dir || dir.trim() === '') {
|
|
156
|
+
throw new Error(`createProject: dir must be non-empty`);
|
|
157
|
+
}
|
|
158
|
+
return new Project(name, dir);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Step 3: Add constructor guard
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
class Project {
|
|
166
|
+
constructor(name: string, dir: string) {
|
|
167
|
+
if (!dir) throw new Error('Project: dir is required');
|
|
168
|
+
this.dir = dir;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Step 4: Add method precondition
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
async clone(): Promise<Project> {
|
|
177
|
+
if (!this.dir) {
|
|
178
|
+
throw new Error('Project.clone: dir must be set before cloning');
|
|
179
|
+
}
|
|
180
|
+
// ... clone logic
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Step 5: Add regression test
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
it('should not use empty string as project dir', async () => {
|
|
188
|
+
const project = await createProject('test', tempDir);
|
|
189
|
+
expect(project.dir).toBe(tempDir);
|
|
190
|
+
expect(project.dir).not.toBe('');
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Error Message Quality
|
|
195
|
+
|
|
196
|
+
Good error messages reduce debugging time from hours to minutes.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// ❌ Unhelpful error
|
|
200
|
+
throw new Error('Invalid directory');
|
|
201
|
+
|
|
202
|
+
// ✓ Helpful error - includes what, where, why, and the actual value
|
|
203
|
+
throw new Error(
|
|
204
|
+
`WorktreeManager.createWorktree: projectDir must be an absolute path to an existing directory.\n` +
|
|
205
|
+
`Got: ${JSON.stringify(projectDir)}\n` +
|
|
206
|
+
`Hint: Check that the project was initialized before calling this method.`
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Error message template:**
|
|
211
|
+
```
|
|
212
|
+
{ClassName}.{methodName}: {what went wrong}.
|
|
213
|
+
Got: {actual value}
|
|
214
|
+
Expected: {what was expected}
|
|
215
|
+
Hint: {likely cause or fix}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Common Validation Patterns
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// Non-empty string
|
|
222
|
+
if (!value || typeof value !== 'string' || value.trim() === '') {
|
|
223
|
+
throw new Error(`${context}: ${fieldName} must be a non-empty string, got: ${JSON.stringify(value)}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Positive number
|
|
227
|
+
if (typeof value !== 'number' || value <= 0 || !isFinite(value)) {
|
|
228
|
+
throw new Error(`${context}: ${fieldName} must be a positive number, got: ${value}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Valid enum value
|
|
232
|
+
const validValues = ['draft', 'active', 'archived'] as const;
|
|
233
|
+
if (!validValues.includes(value)) {
|
|
234
|
+
throw new Error(`${context}: ${fieldName} must be one of [${validValues.join(', ')}], got: ${JSON.stringify(value)}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Required object field
|
|
238
|
+
if (!obj || typeof obj !== 'object') {
|
|
239
|
+
throw new Error(`${context}: ${fieldName} must be an object, got: ${typeof obj}`);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Result
|
|
244
|
+
|
|
245
|
+
After applying defense-in-depth to the session initialization bug:
|
|
246
|
+
|
|
247
|
+
- Layer 1 caught: Invalid input at `createSession()` entry point
|
|
248
|
+
- Layer 2 caught: Empty string in `Session` constructor
|
|
249
|
+
- Layer 3 caught: Unset `projectDir` before `git init`
|
|
250
|
+
- Layer 4 caught: Test assertion verified correct path was used
|
|
251
|
+
|
|
252
|
+
The bug class is now structurally impossible — it would be caught at the earliest possible point with a clear error message pointing to the root cause.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# find-polluter.sh — Binary search for test pollution source
|
|
3
|
+
# Source: https://github.com/tmdgusya/engineering-disciplines (MIT License)
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./find-polluter.sh <test-runner-cmd> <failing-test-pattern>
|
|
6
|
+
# Example: ./find-polluter.sh "bun test" "should not have side effects"
|
|
7
|
+
#
|
|
8
|
+
# How it works:
|
|
9
|
+
# 1. Runs all tests to confirm the failure exists
|
|
10
|
+
# 2. Binary searches through the test list to find which test causes pollution
|
|
11
|
+
# 3. Reports the polluter and its position in the test order
|
|
12
|
+
#
|
|
13
|
+
# Requirements: Test runner must support running specific test files
|
|
14
|
+
# Works with: jest, vitest, bun test, mocha
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# Configuration
|
|
19
|
+
TEST_CMD="${1:-bun test}"
|
|
20
|
+
FAILING_TEST="${2:-}"
|
|
21
|
+
TIMEOUT="${3:-60}" # seconds per test run
|
|
22
|
+
|
|
23
|
+
if [[ -z "$FAILING_TEST" ]]; then
|
|
24
|
+
echo "Usage: $0 <test-runner-cmd> <failing-test-pattern> [timeout-seconds]"
|
|
25
|
+
echo "Example: $0 'bun test' 'should not have side effects' 60"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Colors for output
|
|
30
|
+
RED='\033[0;31m'
|
|
31
|
+
GREEN='\033[0;32m'
|
|
32
|
+
YELLOW='\033[1;33m'
|
|
33
|
+
NC='\033[0m' # No Color
|
|
34
|
+
|
|
35
|
+
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
|
36
|
+
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
37
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
38
|
+
|
|
39
|
+
# Step 1: Collect all test files
|
|
40
|
+
log_info "Collecting test files..."
|
|
41
|
+
mapfile -t ALL_TEST_FILES < <(find . -name "*.test.ts" -o -name "*.test.js" -o -name "*.spec.ts" -o -name "*.spec.js" | grep -v node_modules | sort)
|
|
42
|
+
|
|
43
|
+
if [[ ${#ALL_TEST_FILES[@]} -eq 0 ]]; then
|
|
44
|
+
log_error "No test files found"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
log_info "Found ${#ALL_TEST_FILES[@]} test files"
|
|
49
|
+
|
|
50
|
+
# Step 2: Confirm the failure exists when running all tests
|
|
51
|
+
log_info "Confirming failure exists with full test suite..."
|
|
52
|
+
if timeout "$TIMEOUT" bash -c "$TEST_CMD 2>&1" | grep -q "$FAILING_TEST"; then
|
|
53
|
+
if timeout "$TIMEOUT" bash -c "$TEST_CMD 2>&1" | grep -q "FAIL\|✗\|× "; then
|
|
54
|
+
log_info "Failure confirmed in full suite"
|
|
55
|
+
else
|
|
56
|
+
log_warn "Test pattern found but no failures detected - check pattern matches failing test"
|
|
57
|
+
fi
|
|
58
|
+
else
|
|
59
|
+
log_error "Failing test pattern '$FAILING_TEST' not found in test output"
|
|
60
|
+
log_error "Check that the test exists and the pattern is correct"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Step 3: Confirm the test passes in isolation
|
|
65
|
+
FAILING_TEST_FILE=""
|
|
66
|
+
for f in "${ALL_TEST_FILES[@]}"; do
|
|
67
|
+
if grep -l "$FAILING_TEST" "$f" &>/dev/null; then
|
|
68
|
+
FAILING_TEST_FILE="$f"
|
|
69
|
+
break
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
if [[ -z "$FAILING_TEST_FILE" ]]; then
|
|
74
|
+
log_error "Could not find file containing test: $FAILING_TEST"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
log_info "Failing test is in: $FAILING_TEST_FILE"
|
|
79
|
+
log_info "Testing in isolation..."
|
|
80
|
+
|
|
81
|
+
if timeout "$TIMEOUT" bash -c "$TEST_CMD $FAILING_TEST_FILE 2>&1" | grep -q "FAIL\|✗\|× "; then
|
|
82
|
+
log_warn "Test fails even in isolation - this is not a pollution issue"
|
|
83
|
+
log_warn "The test itself has a bug, not pollution from another test"
|
|
84
|
+
exit 0
|
|
85
|
+
else
|
|
86
|
+
log_info "Test passes in isolation - pollution confirmed"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Step 4: Binary search for the polluter
|
|
90
|
+
# Remove the failing test file from the list of candidates
|
|
91
|
+
CANDIDATE_FILES=()
|
|
92
|
+
for f in "${ALL_TEST_FILES[@]}"; do
|
|
93
|
+
if [[ "$f" != "$FAILING_TEST_FILE" ]]; then
|
|
94
|
+
CANDIDATE_FILES+=("$f")
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
log_info "Binary searching through ${#CANDIDATE_FILES[@]} candidate files..."
|
|
99
|
+
|
|
100
|
+
low=0
|
|
101
|
+
high=$((${#CANDIDATE_FILES[@]} - 1))
|
|
102
|
+
found_polluter=""
|
|
103
|
+
|
|
104
|
+
while [[ $low -le $high ]]; do
|
|
105
|
+
mid=$(( (low + high) / 2 ))
|
|
106
|
+
|
|
107
|
+
# Test with files from 0 to mid, plus the failing test
|
|
108
|
+
SUBSET=("${CANDIDATE_FILES[@]:0:$((mid + 1))}" "$FAILING_TEST_FILE")
|
|
109
|
+
|
|
110
|
+
log_info "Testing subset of $((mid + 1)) files (indices 0-$mid) + failing test..."
|
|
111
|
+
|
|
112
|
+
# Build file list for the test command
|
|
113
|
+
FILE_LIST="${SUBSET[*]}"
|
|
114
|
+
|
|
115
|
+
if timeout "$TIMEOUT" bash -c "$TEST_CMD $FILE_LIST 2>&1" | grep -q "FAIL\|✗\|× "; then
|
|
116
|
+
log_info "Failure reproduced with subset 0-$mid"
|
|
117
|
+
high=$((mid - 1))
|
|
118
|
+
found_polluter="${CANDIDATE_FILES[$mid]}"
|
|
119
|
+
else
|
|
120
|
+
log_info "No failure with subset 0-$mid, polluter is in higher range"
|
|
121
|
+
low=$((mid + 1))
|
|
122
|
+
fi
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
# Step 5: Report results
|
|
126
|
+
echo ""
|
|
127
|
+
echo "=========================================="
|
|
128
|
+
if [[ -n "$found_polluter" ]]; then
|
|
129
|
+
log_info "POLLUTER FOUND: $found_polluter"
|
|
130
|
+
echo ""
|
|
131
|
+
echo "This test file causes '$FAILING_TEST' to fail when run before it."
|
|
132
|
+
echo ""
|
|
133
|
+
echo "To verify:"
|
|
134
|
+
echo " $TEST_CMD $found_polluter $FAILING_TEST_FILE"
|
|
135
|
+
echo ""
|
|
136
|
+
echo "Next steps:"
|
|
137
|
+
echo " 1. Open $found_polluter"
|
|
138
|
+
echo " 2. Look for: global state mutations, missing afterEach/afterAll cleanup"
|
|
139
|
+
echo " 3. Check for: database records not deleted, files not cleaned up"
|
|
140
|
+
echo " 4. Check for: environment variables set but not restored"
|
|
141
|
+
echo " 5. Add proper cleanup in afterEach/afterAll"
|
|
142
|
+
else
|
|
143
|
+
log_warn "Polluter not found via binary search"
|
|
144
|
+
log_warn "This may happen if multiple tests together cause the pollution"
|
|
145
|
+
log_warn "Try running subsets manually to narrow down"
|
|
146
|
+
fi
|
|
147
|
+
echo "=========================================="
|