@tyroneross/build-loop 0.30.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/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: attribution-standard
|
|
3
|
+
description: "Apply the canonical four-layer Apache-2.0 attribution model (NOTICE, per-file SPDX, REUSE.toml, canary markers) to a repo. Triggers on 'stamp attribution', 'add NOTICE', 'license headers', 'attribution layers', a newly public repo, or a repo with .git but no NOTICE / no REUSE.toml / no CONTRIBUTING.md. Build-loop Phase 1 Assess can advise running this when shipped source files lack SPDX headers."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
8
|
+
|
|
9
|
+
# Attribution Standard
|
|
10
|
+
|
|
11
|
+
Four overlapping mechanisms make stripping attribution either illegal, mechanically tedious, or detectable. Each survives a different removal pattern. The research backing this skill is at `~/dev/research/apache-2.0-attribution-watermarking-2026-05-22.md`.
|
|
12
|
+
|
|
13
|
+
## The four layers
|
|
14
|
+
|
|
15
|
+
| Layer | Survives | Legally binding? | Effort |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| 1. NOTICE file | Wholesale repo lift | **Yes** (Apache 2.0 §4(d)) | one-off |
|
|
18
|
+
| 2. Per-file SPDX headers (`SPDX-FileCopyrightText` + `SPDX-License-Identifier`) | Per-file copy-paste | **Yes** (Apache 2.0 §4(c)) | scripted |
|
|
19
|
+
| 3. REUSE.toml | Files that cannot carry a comment (JSON, binaries) | Reinforces #2 | scripted |
|
|
20
|
+
| 4. Canary markers | Naive copy-paste; detectable via GitHub code search | No | low |
|
|
21
|
+
|
|
22
|
+
## Canonical strings
|
|
23
|
+
|
|
24
|
+
Apply identically wherever they appear. Memorise these — do not paraphrase.
|
|
25
|
+
|
|
26
|
+
| Field | Value |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Copyright holder | `Tyrone Ross, Jr` |
|
|
29
|
+
| SPDX email tail | `<46267523+tyroneross@users.noreply.github.com>` |
|
|
30
|
+
| Year range | `2025-2026` |
|
|
31
|
+
| Full SPDX header value | `2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>` |
|
|
32
|
+
| NOTICE AI mention | "Portions of this software were developed with the assistance of Anthropic's Claude (via Claude Code) and OpenAI's Codex (via Codex CLI); AI-pair-programming contributions are attributed via Co-Authored-By trailers in the git history." |
|
|
33
|
+
| Claude co-author trailer | `Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>` (substitute actual model + tier) |
|
|
34
|
+
| Codex co-author trailer | `Co-Authored-By: OpenAI Codex <noreply@openai.com>` |
|
|
35
|
+
|
|
36
|
+
## Language → comment-style table
|
|
37
|
+
|
|
38
|
+
The `attribution_stamp.py` script applies the right comment syntax automatically based on extension.
|
|
39
|
+
|
|
40
|
+
| Extension | Style | Header form |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `.py`, `.sh`, `.bash`, `.zsh`, `.rb`, `.toml`, `.yml`, `.yaml` | `hash` | `# SPDX-FileCopyrightText: ...` (after shebang if present) |
|
|
43
|
+
| `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.css`, `.scss`, `.go`, `.rs`, `.swift`, `.java`, `.kt` | `slash` | `// SPDX-FileCopyrightText: ...` |
|
|
44
|
+
| `.md`, `.mdx`, `.html` | `html_comment` | `<!-- SPDX-FileCopyrightText: ... \| SPDX-License-Identifier: Apache-2.0 -->` (after YAML frontmatter if present) |
|
|
45
|
+
| `.json` and binary assets | n/a | covered by `REUSE.toml` |
|
|
46
|
+
|
|
47
|
+
## Target paths
|
|
48
|
+
|
|
49
|
+
Default shipped-source roots: `src scripts hooks skills agents commands references`. Override via `--paths`.
|
|
50
|
+
|
|
51
|
+
Default excluded path components: `node_modules dist build .git .venv venv archive tests/fixtures docs/test-fixtures __pycache__ .pytest_cache .mypy_cache .ruff_cache`. Extend via `--excludes`.
|
|
52
|
+
|
|
53
|
+
For Python-heavy repos with no `src/` directory (like agent-rally-point), pass the actual package path: `--paths agent_rally_point` or whatever directory holds the .py files.
|
|
54
|
+
|
|
55
|
+
## How to apply (the script)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python scripts/attribution_stamp.py \
|
|
59
|
+
--repo <path-to-repo> \
|
|
60
|
+
--name "Tyrone Ross, Jr" \
|
|
61
|
+
--email "46267523+tyroneross@users.noreply.github.com" \
|
|
62
|
+
--years 2025-2026 \
|
|
63
|
+
--canary-files <path1> <path2> \
|
|
64
|
+
[--paths <override-paths...>] \
|
|
65
|
+
[--restamp]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The script is **idempotent**:
|
|
69
|
+
|
|
70
|
+
- Re-running with the same args is a no-op when canonical strings already match.
|
|
71
|
+
- `--restamp` REPLACES existing SPDX header lines so a string change (e.g. adding `, Jr` or an email tail) can be rolled across the tree.
|
|
72
|
+
- NOTICE is always rewritten from the canonical template (the canonical strings are the source of truth, not what's on disk).
|
|
73
|
+
- LICENSE appendix and README "License & Attribution" section are added when missing; CONTRIBUTING.md is written unless already canonical.
|
|
74
|
+
|
|
75
|
+
## Canary files — pick two
|
|
76
|
+
|
|
77
|
+
Pick two central, stable files — one near the package entry point and one in the user-facing documentation. The canary marker is invisible to users but indexable by GitHub code search. Stable choices:
|
|
78
|
+
|
|
79
|
+
- A package's `__init__.py` or main module
|
|
80
|
+
- The orchestrator/coordinator skill or main markdown file
|
|
81
|
+
- A central agent definition
|
|
82
|
+
|
|
83
|
+
Avoid: test files, generated code, vendored libraries, files that frequently change shape.
|
|
84
|
+
|
|
85
|
+
## Build-loop Phase 1 advisory wiring
|
|
86
|
+
|
|
87
|
+
Build-loop's Phase 1 Assess fires an advisory (routes to the run report; **never** asks the user mid-run, per `feedback_advisory_checks_are_automated`) when a public repo (has a GitHub origin) is missing any of:
|
|
88
|
+
|
|
89
|
+
- `NOTICE`
|
|
90
|
+
- `REUSE.toml`
|
|
91
|
+
- `CONTRIBUTING.md`
|
|
92
|
+
- SPDX headers on at least 80% of shipped source files
|
|
93
|
+
|
|
94
|
+
The advisory line is exactly: `Repo is missing standard attribution layers — run \`python scripts/attribution_stamp.py --repo <path>\``. When the build scope is ≥ S and the advisory fires, Phase 2 Plan queues an automatic chunk to run the stamper. Hard-blocking is out of scope — this is advisory, not a gate.
|
|
95
|
+
|
|
96
|
+
## Verification
|
|
97
|
+
|
|
98
|
+
After stamping, verify:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Canonical name present in shipped source
|
|
102
|
+
grep -rln 'Tyrone Ross, Jr' src scripts skills agents commands references | wc -l
|
|
103
|
+
|
|
104
|
+
# No bare 'Tyrone Ross' (without ', Jr') in stamped files
|
|
105
|
+
grep -rln 'SPDX-FileCopyrightText:.*Tyrone Ross$' src scripts skills agents commands references | wc -l # expect 0
|
|
106
|
+
|
|
107
|
+
# No SPDX line without the email tail
|
|
108
|
+
grep -rL 'noreply.github.com' \
|
|
109
|
+
$(grep -rl 'SPDX-FileCopyrightText: 2025-2026 Tyrone Ross' src scripts skills agents commands references) \
|
|
110
|
+
| wc -l # expect 0
|
|
111
|
+
|
|
112
|
+
# REUSE compliance
|
|
113
|
+
uvx reuse lint
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
NOTICE must mention both Claude (via Claude Code) and OpenAI Codex (via Codex CLI).
|
|
117
|
+
|
|
118
|
+
## When NOT to use this skill
|
|
119
|
+
|
|
120
|
+
- Repos that aren't Apache 2.0 (the SPDX line hardcodes it; for other SPDX IDs, modify the script or invoke per-language manually).
|
|
121
|
+
- Repos that don't ship source (pure design assets, datasets, etc.).
|
|
122
|
+
- Forks of someone else's project — Apache 2.0 §4(c) requires you to PRESERVE the upstream copyright, not replace it. The script's `--restamp` flag is dangerous here.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: authentication
|
|
3
|
+
description: Use when wiring auth to a new project, debugging login errors (redirect_uri_mismatch, invalid_grant, session callback, refresh_token), or adding social/magic link flows. Covers Better Auth (Drizzle/Neon), Supabase Auth (SSR), Google OAuth, and Resend transactional email.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
8
|
+
|
|
9
|
+
# Authentication
|
|
10
|
+
|
|
11
|
+
Reference library for wiring external authentication and authorization into a web or mobile app. Each service has its own reference file — load the one you need, don't pre-load all.
|
|
12
|
+
|
|
13
|
+
**Stack default for new Next.js work**: Neon + Better Auth + Drizzle. Supabase is covered for legacy projects and as an alternate. Magic-link delivery and transactional email default to Resend.
|
|
14
|
+
|
|
15
|
+
## When to use this skill
|
|
16
|
+
|
|
17
|
+
- Setting up third-party sign-in (Google, Apple, magic-link, etc.) on a Better Auth or Supabase backend
|
|
18
|
+
- Wiring API keys / service accounts / OAuth callbacks
|
|
19
|
+
- Debugging `redirect_uri_mismatch`, `invalid_grant`, token refresh loops, session callback failures
|
|
20
|
+
- Sending magic-link / OTP emails and handling delivery webhooks
|
|
21
|
+
- SSR cookie handling and the Supabase `getAll`/`setAll` ≥0.3 breaking change
|
|
22
|
+
- Migrating away from legacy auth SDKs (e.g., `gapi.auth2`, `@supabase/ssr` <0.3)
|
|
23
|
+
|
|
24
|
+
## Routing table
|
|
25
|
+
|
|
26
|
+
| If the user is asking about... | Load | Context7 lib id |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Better Auth setup, Drizzle adapter, social providers, refresh_token | `references/better-auth-setup.md` | `/better-auth/better-auth` |
|
|
29
|
+
| Magic links / OTP via Better Auth | `references/better-auth-magic-link.md` | `/better-auth/better-auth` |
|
|
30
|
+
| Example Web App Better Auth real-build lessons | `references/lessons-example-web-app-better-auth.md` | — |
|
|
31
|
+
| Supabase Auth (SSR cookies, RLS, env validation, when-to-use vs Better Auth) | `references/supabase-auth.md` | `/supabase/supabase` |
|
|
32
|
+
| Resend transactional + Better Auth `sendVerificationEmail` integration + webhook signatures | `references/resend-email.md` | `/websites/resend` |
|
|
33
|
+
| Resend OTP / magic-link delivery + retries + bounce handling | `references/resend-otp-magic-link.md` | `/websites/resend`, `/resend/resend-skills` |
|
|
34
|
+
| Google OAuth sign-in | `references/google-oauth-setup.md` + `references/google-cloud-console.md` | — |
|
|
35
|
+
| Google Maps in a UI | `references/google-maps.md` | — |
|
|
36
|
+
| Google Places API (New) | `references/google-places.md` | — |
|
|
37
|
+
| Google Geocoding / Directions | `references/google-geocoding-directions.md` | — |
|
|
38
|
+
| Google Calendar sync | `references/google-calendar-sync.md` | — |
|
|
39
|
+
| Broad Google Cloud Console walkthrough (4-layer mental model: identity, APIs, credentials, quotas) | `references/google-cloud-console.md` | — |
|
|
40
|
+
| Real Google build incident lessons (Example Web App, Next.js + Supabase) | `references/google-lessons-example-web-app.md` | — |
|
|
41
|
+
|
|
42
|
+
Services not yet documented (planned as additional references): GitHub OAuth, generic OAuth 2.0 callbacks, service-account key handling, API key rotation. Add them as `references/<service>-<topic>.md` following the same pattern.
|
|
43
|
+
|
|
44
|
+
## Universal auth footguns (applies to any service)
|
|
45
|
+
|
|
46
|
+
1. **Redirect URIs are exact-match.** `https://app.com/cb` ≠ `https://app.com/cb/` ≠ `http://app.com/cb`. Register every variant you use in dev and prod.
|
|
47
|
+
2. **Don't store tokens in localStorage for SPAs that redirect.** Use httpOnly cookies or the platform's session store.
|
|
48
|
+
3. **Token refresh fails silently when the refresh token is expired/rotated.** Log refresh failures explicitly; don't swallow them.
|
|
49
|
+
4. **Scopes are sticky across authorizations.** If you add a scope later, existing users need to re-consent — the original grant doesn't auto-upgrade.
|
|
50
|
+
5. **"Invalid grant" usually means one of: clock skew, expired refresh token, revoked authorization, or JWT signature mismatch.** Check system clock first.
|
|
51
|
+
6. **Service accounts ≠ user accounts.** Service accounts act on their own identity with their own quota. User-delegated access (domain-wide delegation) is a separate flow with separate risks.
|
|
52
|
+
7. **`NEXT_PUBLIC_APP_URL` / `BETTER_AUTH_URL` mismatch on Vercel CNAMEs.** Preview/production URLs diverge from the registered redirect; bind detection to the actual `VERCEL_URL` fallback and verify at boot. Example Web App shipped a production-only auth failure from this exact mismatch.
|
|
53
|
+
8. **Refresh-token guarantee on Google.** Better Auth's Google provider must explicitly request `accessType: 'offline'` AND `prompt: 'select_account consent'` — otherwise `refresh_token` is missing on second consent. Verify the `account.refresh_token` and `account.expires_at` columns populate before shipping. (See `references/better-auth-setup.md`.)
|
|
54
|
+
9. **IDOR via shared DB connection.** Per-request `dbForUser(userId)` wrappers (or RLS) — never trust client-supplied user IDs in queries. Example Web App's `lib/db/index.ts` is the canonical pattern.
|
|
55
|
+
10. **Magic-link expiry & idempotency.** Default 10 min; single-use; rate-limit by email; log delivery failures (Resend bounces ≠ user error). example-app's `magicLink({ expiresIn: 60 * 10 })` is the reference config.
|
|
56
|
+
11. **Cookie config is non-negotiable.** `httpOnly: true`, `secure: true` (prod), `sameSite: 'lax'` for OAuth redirects. `sameSite: 'strict'` breaks Google callback. Better Auth defaults are correct; override with care.
|
|
57
|
+
|
|
58
|
+
## Pattern: adding a new auth reference
|
|
59
|
+
|
|
60
|
+
When expanding this skill:
|
|
61
|
+
1. Add `references/<service>-<topic>.md` with a clear `## When to use` section at top.
|
|
62
|
+
2. Add a row to the routing table above.
|
|
63
|
+
3. If the service has a shared footgun class (e.g., OAuth redirect URIs), cross-link to the universal footguns section instead of repeating.
|
|
64
|
+
4. Do **not** promote a single-service reference to a top-level skill unless it grows past ~500 lines *and* has multiple distinct sub-topics. Single services belong as references.
|
|
65
|
+
|
|
66
|
+
## Looking up current API surface
|
|
67
|
+
|
|
68
|
+
Auth SDKs change frequently — Better Auth in particular adds plugins and provider options on a fast cadence. Before writing config or debugging a session callback, fetch live docs via the Context7 MCP (mirrors the pattern in `building-with-deepagents/SKILL.md:33`):
|
|
69
|
+
|
|
70
|
+
1. **Resolve** the library id once per session:
|
|
71
|
+
`mcp__plugin_context7_context7__resolve-library-id("better-auth")` → `/better-auth/better-auth`
|
|
72
|
+
2. **Query** with the resolved id and a focused topic:
|
|
73
|
+
`mcp__plugin_context7_context7__query-docs` with `library: "/better-auth/better-auth"` and `topic: "magic link plugin config"` (or `"social provider refresh_token"`, `"SSR getAll setAll"`, `"webhook signature verification"`).
|
|
74
|
+
|
|
75
|
+
Known Context7 ids (verified 2026-05-02): `/better-auth/better-auth`, `/supabase/supabase`, `/websites/resend`, `/resend/resend-skills`. If a query returns thin results, retry with the alternate (`/resend/resend-skills` is better tuned for agent-flavored questions; `/websites/resend` covers the broader SDK surface).
|
|
76
|
+
|
|
77
|
+
## History
|
|
78
|
+
|
|
79
|
+
Originally imported from a standalone `google-cloud-console` skill (2025, Example Web App build). Converted to a general `authentication` parent skill on 2026-04-21 so GitHub OAuth, generic OAuth 2.0, and API-key patterns can share the routing layer and universal footgun section without each becoming its own island.
|
|
80
|
+
|
|
81
|
+
Extended on 2026-05-02 to multi-provider coverage: added Better Auth (Drizzle/Neon, magic-link, Google + Apple social), Supabase Auth (legacy + alternate), and Resend (transactional, magic-link/OTP, webhook verification). Lessons harvested from Example Web App, example-app, Example App, and example-app. Adopted the Context7 lookup pattern so SKILL.md stays light while doc depth lives in references and the live Context7 index.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
2
|
+
|
|
3
|
+
# Better Auth — Magic Link / OTP
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Email-only auth (no password, no social) — example-app and Example App both use this
|
|
8
|
+
- Adding magic-link as an alternative sign-in method on top of social providers
|
|
9
|
+
- OTP / verification-code flows (uses the same plugin with a different delivery template)
|
|
10
|
+
|
|
11
|
+
For the broader Better Auth setup (database, providers, routes), start at `better-auth-setup.md`.
|
|
12
|
+
For Resend wiring on the email delivery side, see `resend-email.md` (transactional) or `resend-otp-magic-link.md` (delivery + retry patterns).
|
|
13
|
+
|
|
14
|
+
## Server config (example-app pattern)
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// lib/auth.ts
|
|
18
|
+
import { betterAuth } from 'better-auth'
|
|
19
|
+
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
20
|
+
import { magicLink } from 'better-auth/plugins'
|
|
21
|
+
import { prisma } from '@/lib/prisma'
|
|
22
|
+
import { sendMagicLinkEmail } from '@/lib/email/send-magic-link'
|
|
23
|
+
|
|
24
|
+
export const auth = betterAuth({
|
|
25
|
+
database: prismaAdapter(prisma, { provider: 'postgresql' }),
|
|
26
|
+
baseURL: process.env.BETTER_AUTH_URL || 'http://localhost:3150',
|
|
27
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
28
|
+
emailAndPassword: { enabled: false },
|
|
29
|
+
plugins: [
|
|
30
|
+
magicLink({
|
|
31
|
+
// 10-minute expiry. Single-use is the plugin default — do not weaken.
|
|
32
|
+
expiresIn: 60 * 10,
|
|
33
|
+
sendMagicLink: async ({ email, url }) => {
|
|
34
|
+
await sendMagicLinkEmail({ email, url })
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For Drizzle, swap `prismaAdapter(prisma, { provider: 'postgresql' })` with `drizzleAdapter(db, { provider: 'pg' })` (Example Web App / Example App use Drizzle).
|
|
42
|
+
|
|
43
|
+
## Client (with magic-link plugin)
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// lib/auth-client.ts
|
|
47
|
+
'use client'
|
|
48
|
+
import { createAuthClient } from 'better-auth/react'
|
|
49
|
+
import { magicLinkClient } from 'better-auth/client/plugins'
|
|
50
|
+
|
|
51
|
+
export const authClient = createAuthClient({
|
|
52
|
+
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || 'http://localhost:3150',
|
|
53
|
+
plugins: [magicLinkClient()],
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
export const { signIn, signOut, useSession } = authClient
|
|
57
|
+
|
|
58
|
+
// Usage:
|
|
59
|
+
// await authClient.signIn.magicLink({ email, callbackURL: '/dashboard' })
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Email delivery handoff (dev-friendly, prod-strict)
|
|
63
|
+
|
|
64
|
+
example-app's `lib/email/send-magic-link.ts` shows a useful pattern: log to console in dev, throw in prod, with the actual Resend block ready to uncomment.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
interface SendMagicLinkParams { email: string; url: string }
|
|
68
|
+
|
|
69
|
+
export async function sendMagicLinkEmail({ email, url }: SendMagicLinkParams): Promise<void> {
|
|
70
|
+
const hasEmailProvider = Boolean(process.env.RESEND_API_KEY)
|
|
71
|
+
|
|
72
|
+
if (!hasEmailProvider) {
|
|
73
|
+
if (process.env.NODE_ENV === 'production') {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'[auth] sendMagicLinkEmail: no email provider configured. ' +
|
|
76
|
+
'Set RESEND_API_KEY or wire up a different provider.'
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
// Dev mode: print the link to the terminal so you can click it directly.
|
|
80
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
81
|
+
console.log(' 📧 MAGIC LINK (dev mode — email not sent)')
|
|
82
|
+
console.log(` To: ${email}`)
|
|
83
|
+
console.log(` Link: ${url}`)
|
|
84
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const { Resend } = await import('resend')
|
|
89
|
+
const resend = new Resend(process.env.RESEND_API_KEY)
|
|
90
|
+
|
|
91
|
+
const { error } = await resend.emails.send({
|
|
92
|
+
from: process.env.AUTH_EMAIL_FROM || 'YourApp <noreply@example.com>',
|
|
93
|
+
to: email,
|
|
94
|
+
subject: 'Sign in to YourApp',
|
|
95
|
+
html: `
|
|
96
|
+
<div style="font-family: -apple-system, system-ui, sans-serif; max-width: 480px; margin: 0 auto;">
|
|
97
|
+
<h1 style="font-size: 20px; color: #111;">Sign in to YourApp</h1>
|
|
98
|
+
<p style="color: #555; line-height: 1.5;">Click the button below to sign in. This link expires in 10 minutes.</p>
|
|
99
|
+
<a href="${url}" style="display: inline-block; padding: 12px 20px; background: #111; color: #fff; text-decoration: none; border-radius: 6px; font-weight: 500;">Sign in</a>
|
|
100
|
+
<p style="color: #888; font-size: 12px; margin-top: 24px;">If you didn't request this, you can safely ignore this email.</p>
|
|
101
|
+
</div>
|
|
102
|
+
`,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (error) throw new Error(`[auth] Resend send failed: ${error.message}`)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Key behaviors:
|
|
110
|
+
- **Dev never blocks on missing API key** — link goes to terminal
|
|
111
|
+
- **Prod hard-fails** when key is absent (no silent skip)
|
|
112
|
+
- **Dynamic import** of `resend` keeps it out of the cold-start bundle if unused
|
|
113
|
+
|
|
114
|
+
## Footguns specific to magic-link
|
|
115
|
+
|
|
116
|
+
1. **Don't lengthen `expiresIn`.** 10 minutes is the right default. Longer windows widen the attack surface for email-account compromise.
|
|
117
|
+
2. **Rate-limit by email.** A loop submitting `/sign-in/magic-link` for `victim@x.com` is an email-flood vector. Use a per-email rate limiter (Upstash, Vercel KV, or a simple in-memory LRU for low traffic).
|
|
118
|
+
3. **Log delivery failures, not delivery successes.** Bouncebacks from Resend (`email.bounced`) are NOT the user's fault — log them and surface to ops, not to the user.
|
|
119
|
+
4. **Single-use is built-in.** Don't extend `magicLink({ ... })` to allow re-use; that defeats the security model.
|
|
120
|
+
5. **Idempotent click handlers.** If a user double-clicks the magic-link, the second hit should land them in their session, not 401. Better Auth's default does this; verify if you customize.
|
|
121
|
+
|
|
122
|
+
## Verification
|
|
123
|
+
|
|
124
|
+
1. Trigger a magic-link send. Confirm the magicLink record in `verification` (Better Auth) / equivalent table.
|
|
125
|
+
2. Click the link. Confirm session created, link record marked consumed.
|
|
126
|
+
3. Click the same link again — should fail with `INVALID_TOKEN` or redirect to a "link already used" state.
|
|
127
|
+
4. Wait 11 minutes, retry — should fail with `EXPIRED_TOKEN`.
|
|
128
|
+
5. In prod with `RESEND_API_KEY` unset, sign-in should hard-fail with the error message above (not silently succeed).
|
|
129
|
+
|
|
130
|
+
## Cross-references
|
|
131
|
+
|
|
132
|
+
- Universal footgun #10 (magic-link expiry & idempotency) in `../SKILL.md`
|
|
133
|
+
- `resend-email.md` for transactional + webhook integration
|
|
134
|
+
- `resend-otp-magic-link.md` for delivery patterns
|
|
135
|
+
- For OTP (numeric code) instead of clickable link, query Context7: `library: "/better-auth/better-auth"`, `topic: "OTP plugin"` — same plugin, different delivery template.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
2
|
+
|
|
3
|
+
# Better Auth — Setup
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- New Next.js project on Neon + Drizzle (the documented stack default)
|
|
8
|
+
- Adding social providers (Google, Apple) to an existing Better Auth instance
|
|
9
|
+
- Wiring an iOS/mobile client via the Bearer plugin
|
|
10
|
+
- Verifying that Google sign-in produces a usable `refresh_token`
|
|
11
|
+
- Migrating away from `@supabase/ssr` (Example Web App did this — see `lessons-example-web-app-better-auth.md`)
|
|
12
|
+
|
|
13
|
+
For magic-link / OTP setup, see `better-auth-magic-link.md`.
|
|
14
|
+
For runtime API doc lookups, use `mcp__plugin_context7_context7__query-docs` with library `/better-auth/better-auth`.
|
|
15
|
+
|
|
16
|
+
## Server instance (Drizzle + Neon + Google + Apple)
|
|
17
|
+
|
|
18
|
+
Pattern from `Example Web App/lib/auth.ts` (production-grade, multi-platform). Adjust provider list to your needs.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// lib/auth.ts
|
|
22
|
+
import { betterAuth } from 'better-auth'
|
|
23
|
+
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
24
|
+
import { bearer } from 'better-auth/plugins'
|
|
25
|
+
import { db } from '@/lib/db'
|
|
26
|
+
|
|
27
|
+
const TRUSTED_ORIGINS = [
|
|
28
|
+
process.env.BETTER_AUTH_URL ?? 'http://localhost:3000',
|
|
29
|
+
'http://localhost:3000',
|
|
30
|
+
// Add native URL schemes here for mobile callbacks, e.g.:
|
|
31
|
+
// 'travelplanner://auth-callback',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export const auth = betterAuth({
|
|
35
|
+
database: drizzleAdapter(db, { provider: 'pg' }),
|
|
36
|
+
secret: process.env.BETTER_AUTH_SECRET!,
|
|
37
|
+
baseURL: process.env.BETTER_AUTH_URL ?? 'http://localhost:3000',
|
|
38
|
+
trustedOrigins: TRUSTED_ORIGINS,
|
|
39
|
+
socialProviders: {
|
|
40
|
+
google: {
|
|
41
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
42
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
43
|
+
// CRITICAL: both are required to guarantee refresh_token on every sign-in.
|
|
44
|
+
// See Universal footgun #8 in SKILL.md.
|
|
45
|
+
accessType: 'offline',
|
|
46
|
+
prompt: 'select_account consent',
|
|
47
|
+
scope: [
|
|
48
|
+
'openid',
|
|
49
|
+
'email',
|
|
50
|
+
'profile',
|
|
51
|
+
// Pre-approve any scopes you'll need later so the user doesn't hit a
|
|
52
|
+
// second consent screen mid-flow:
|
|
53
|
+
// 'https://www.googleapis.com/auth/calendar.app.created',
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
plugins: [bearer()], // Bearer plugin only needed if you have a mobile/native client
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export type Session = typeof auth.$Infer.Session
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Apple provider (conditional)
|
|
64
|
+
|
|
65
|
+
For mobile apps with Apple Sign-In, register the provider only when `APPLE_CLIENT_ID` is present so unconfigured environments fall back gracefully:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const appleProvider = (() => {
|
|
69
|
+
const clientId = process.env.APPLE_CLIENT_ID
|
|
70
|
+
if (!clientId) return undefined
|
|
71
|
+
return {
|
|
72
|
+
clientId,
|
|
73
|
+
// For iOS native ID-token sign-in, clientSecret is OPTIONAL.
|
|
74
|
+
// For web OAuth code-exchange, clientSecret is REQUIRED (a JWT signed with
|
|
75
|
+
// your .p8 private key; max 6-month lifetime — pre-generate or sign at startup).
|
|
76
|
+
...(process.env.APPLE_CLIENT_SECRET && { clientSecret: process.env.APPLE_CLIENT_SECRET }),
|
|
77
|
+
...(process.env.APPLE_APP_BUNDLE_IDENTIFIER && {
|
|
78
|
+
appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER,
|
|
79
|
+
}),
|
|
80
|
+
}
|
|
81
|
+
})()
|
|
82
|
+
|
|
83
|
+
// then inside socialProviders:
|
|
84
|
+
// ...(appleProvider ? { apple: appleProvider } : {}),
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Catch-all route handler (Next.js App Router)
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// app/api/auth/[...all]/route.ts
|
|
91
|
+
import { auth } from '@/lib/auth'
|
|
92
|
+
import { toNextJsHandler } from 'better-auth/next-js'
|
|
93
|
+
|
|
94
|
+
export const { GET, POST } = toNextJsHandler(auth)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
That single file replaces every Supabase callback / NextAuth route handler.
|
|
98
|
+
|
|
99
|
+
## Client (React)
|
|
100
|
+
|
|
101
|
+
Two patterns ship in the wild. Pick by need:
|
|
102
|
+
|
|
103
|
+
**Minimal (Example Web App)** — uses `window.location.origin` by default, no `basePath` override:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// lib/auth-client.ts
|
|
107
|
+
'use client'
|
|
108
|
+
import { createAuthClient } from 'better-auth/react'
|
|
109
|
+
|
|
110
|
+
export const authClient = createAuthClient({
|
|
111
|
+
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL ?? '',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
export const { signIn, signUp, signOut, useSession } = authClient
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Explicit basePath + `credentials: include` (Example App)** — needed when the client and API are on different origins, or when you've mounted the handler under a non-default path:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// client/src/lib/auth.ts
|
|
121
|
+
import { createAuthClient } from 'better-auth/react'
|
|
122
|
+
import { magicLinkClient } from 'better-auth/client/plugins'
|
|
123
|
+
|
|
124
|
+
const authOrigin =
|
|
125
|
+
typeof window !== 'undefined'
|
|
126
|
+
? window.location.origin
|
|
127
|
+
: 'http://localhost:3000'
|
|
128
|
+
|
|
129
|
+
export const authClient = createAuthClient({
|
|
130
|
+
baseURL: authOrigin,
|
|
131
|
+
basePath: '/api/auth',
|
|
132
|
+
fetchOptions: {
|
|
133
|
+
credentials: 'include' as RequestCredentials,
|
|
134
|
+
},
|
|
135
|
+
plugins: [magicLinkClient()],
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Feature flag with anonymous fallback (example-app pattern)
|
|
140
|
+
|
|
141
|
+
When auth is being introduced incrementally and existing routes were written assuming an anonymous user, this gate keeps everything working until you flip `ENABLE_AUTH=true`:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// lib/auth.ts (continued)
|
|
145
|
+
import { headers } from 'next/headers'
|
|
146
|
+
|
|
147
|
+
export const ENABLE_AUTH = process.env.ENABLE_AUTH === 'true'
|
|
148
|
+
|
|
149
|
+
export async function getUserIdFromSession(): Promise<string> {
|
|
150
|
+
if (!ENABLE_AUTH) return 'anonymous'
|
|
151
|
+
try {
|
|
152
|
+
const session = await auth.api.getSession({ headers: await headers() })
|
|
153
|
+
return session?.user?.id ?? 'anonymous'
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.warn('[auth] getUserIdFromSession failed, falling back to anonymous:', error)
|
|
156
|
+
return 'anonymous'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Important: this is a transitional pattern. Remove it before declaring auth "shipped" — `anonymous` user IDs are not a real auth boundary.
|
|
162
|
+
|
|
163
|
+
## IDOR guard — `dbForUser(userId)`
|
|
164
|
+
|
|
165
|
+
Better Auth gives you a session, but it does NOT enforce per-row authorization. Every query must scope to the session's user id. Example Web App's `lib/db/index.ts` exports a `dbForUser(userId)` helper that returns CRUD methods pre-bound to that user:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// lib/db/index.ts (sketch)
|
|
169
|
+
export function dbForUser(userId: string) {
|
|
170
|
+
return {
|
|
171
|
+
camps: {
|
|
172
|
+
list: () =>
|
|
173
|
+
db.select().from(schema.camps).where(eq(schema.camps.userId, userId)),
|
|
174
|
+
get: (id: string) =>
|
|
175
|
+
db
|
|
176
|
+
.select()
|
|
177
|
+
.from(schema.camps)
|
|
178
|
+
.where(and(eq(schema.camps.userId, userId), eq(schema.camps.id, id)))
|
|
179
|
+
.limit(1),
|
|
180
|
+
create: (input: Omit<typeof schema.camps.$inferInsert, 'userId'>) =>
|
|
181
|
+
db.insert(schema.camps).values({ ...input, userId }).returning(),
|
|
182
|
+
// update / remove follow the same and(eq(userId), eq(id)) pattern
|
|
183
|
+
},
|
|
184
|
+
// ... one block per table
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Call sites read like `dbForUser(session.user.id).camps.list()` — there's no way to forget the user filter, and a code review can grep for `from(schema.X)` outside `dbForUser` to spot violations.
|
|
190
|
+
|
|
191
|
+
## Verification checklist (before declaring auth shipped)
|
|
192
|
+
|
|
193
|
+
1. Sign in via Google. Confirm in DB:
|
|
194
|
+
- `user.id` is set
|
|
195
|
+
- `account.refresh_token` is non-null
|
|
196
|
+
- `account.access_token` is set with a future `expires_at`
|
|
197
|
+
2. Sign out, sign back in. Confirm `refresh_token` is still set (not cleared by re-consent).
|
|
198
|
+
3. Wait past the access-token expiry. Confirm the next API call refreshes silently (Better Auth handles this if `accessType: 'offline'` is set).
|
|
199
|
+
4. Verify cookies are `httpOnly`, `secure` (prod), `sameSite: 'lax'`. (See Universal footgun #11.)
|
|
200
|
+
5. In production, confirm `BETTER_AUTH_URL` matches the actual Vercel URL — boot-time validation recommended (Universal footgun #7).
|
|
201
|
+
6. Hit a protected API route from a logged-out browser; confirm 401, not anonymous fallback (unless `ENABLE_AUTH=false` is intentional).
|
|
202
|
+
|
|
203
|
+
## Required env vars
|
|
204
|
+
|
|
205
|
+
```env
|
|
206
|
+
BETTER_AUTH_SECRET=<32-byte random string>
|
|
207
|
+
BETTER_AUTH_URL=https://your-app.com # MUST match production URL exactly
|
|
208
|
+
NEXT_PUBLIC_BETTER_AUTH_URL=https://your-app.com # client uses this
|
|
209
|
+
GOOGLE_CLIENT_ID=...
|
|
210
|
+
GOOGLE_CLIENT_SECRET=...
|
|
211
|
+
# Optional Apple:
|
|
212
|
+
APPLE_CLIENT_ID=...
|
|
213
|
+
APPLE_CLIENT_SECRET=... # web flow only; iOS native skips
|
|
214
|
+
APPLE_APP_BUNDLE_IDENTIFIER=com.example.app
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
For Drizzle schema, see Better Auth docs: `mcp__plugin_context7_context7__query-docs` with `library: "/better-auth/better-auth"` and `topic: "drizzle schema"`.
|
|
218
|
+
|
|
219
|
+
## Cross-references
|
|
220
|
+
|
|
221
|
+
- Universal footguns #7 (Vercel URL mismatch), #8 (refresh_token guarantee), #9 (IDOR guard), #11 (cookie config) in `../SKILL.md`
|
|
222
|
+
- `lessons-example-web-app-better-auth.md` — incident-style narrative of these issues hitting production
|
|
223
|
+
- `better-auth-magic-link.md` — magic-link plugin setup
|
|
224
|
+
- `resend-email.md` — wiring `sendVerificationEmail` to Resend
|