@dewtech/dare-cli 2.16.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -3
- package/dist/__tests__/confidence.test.d.ts +2 -0
- package/dist/__tests__/confidence.test.d.ts.map +1 -0
- package/dist/__tests__/confidence.test.js +73 -0
- package/dist/__tests__/confidence.test.js.map +1 -0
- package/dist/__tests__/datamodel.test.d.ts +2 -0
- package/dist/__tests__/datamodel.test.d.ts.map +1 -0
- package/dist/__tests__/datamodel.test.js +131 -0
- package/dist/__tests__/datamodel.test.js.map +1 -0
- package/dist/__tests__/dna-detector.test.d.ts +2 -0
- package/dist/__tests__/dna-detector.test.d.ts.map +1 -0
- package/dist/__tests__/dna-detector.test.js +97 -0
- package/dist/__tests__/dna-detector.test.js.map +1 -0
- package/dist/__tests__/dna-facts.test.d.ts +2 -0
- package/dist/__tests__/dna-facts.test.d.ts.map +1 -0
- package/dist/__tests__/dna-facts.test.js +44 -0
- package/dist/__tests__/dna-facts.test.js.map +1 -0
- package/dist/__tests__/graph-renderer.test.d.ts +2 -0
- package/dist/__tests__/graph-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/graph-renderer.test.js +85 -0
- package/dist/__tests__/graph-renderer.test.js.map +1 -0
- package/dist/__tests__/migration.test.d.ts +2 -0
- package/dist/__tests__/migration.test.d.ts.map +1 -0
- package/dist/__tests__/migration.test.js +77 -0
- package/dist/__tests__/migration.test.js.map +1 -0
- package/dist/__tests__/module-detector.test.d.ts +2 -0
- package/dist/__tests__/module-detector.test.d.ts.map +1 -0
- package/dist/__tests__/module-detector.test.js +83 -0
- package/dist/__tests__/module-detector.test.js.map +1 -0
- package/dist/__tests__/refine.test.d.ts +2 -0
- package/dist/__tests__/refine.test.d.ts.map +1 -0
- package/dist/__tests__/refine.test.js +186 -0
- package/dist/__tests__/refine.test.js.map +1 -0
- package/dist/__tests__/reverse-facts.test.d.ts +2 -0
- package/dist/__tests__/reverse-facts.test.d.ts.map +1 -0
- package/dist/__tests__/reverse-facts.test.js +78 -0
- package/dist/__tests__/reverse-facts.test.js.map +1 -0
- package/dist/__tests__/review.test.d.ts +2 -0
- package/dist/__tests__/review.test.d.ts.map +1 -0
- package/dist/__tests__/review.test.js +242 -0
- package/dist/__tests__/review.test.js.map +1 -0
- package/dist/__tests__/update.test.d.ts +2 -0
- package/dist/__tests__/update.test.d.ts.map +1 -0
- package/dist/__tests__/update.test.js +150 -0
- package/dist/__tests__/update.test.js.map +1 -0
- package/dist/__tests__/validate.test.js +65 -65
- package/dist/bin/dare.js +38 -3
- package/dist/bin/dare.js.map +1 -1
- package/dist/commands/blueprint.js +122 -122
- package/dist/commands/dag.d.ts.map +1 -1
- package/dist/commands/dag.js +43 -79
- package/dist/commands/dag.js.map +1 -1
- package/dist/commands/dna.d.ts +3 -0
- package/dist/commands/dna.d.ts.map +1 -0
- package/dist/commands/dna.js +69 -0
- package/dist/commands/dna.js.map +1 -0
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +76 -0
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +101 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/new.d.ts +16 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +103 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/refine.d.ts +16 -0
- package/dist/commands/refine.d.ts.map +1 -0
- package/dist/commands/refine.js +167 -0
- package/dist/commands/refine.js.map +1 -0
- package/dist/commands/reverse.d.ts +3 -0
- package/dist/commands/reverse.d.ts.map +1 -0
- package/dist/commands/reverse.js +201 -0
- package/dist/commands/reverse.js.map +1 -0
- package/dist/commands/review.d.ts +16 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +106 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/update.d.ts +13 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +149 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/welcome.d.ts +14 -0
- package/dist/commands/welcome.d.ts.map +1 -0
- package/dist/commands/welcome.js +29 -0
- package/dist/commands/welcome.js.map +1 -0
- package/dist/skills/commands/add.d.ts +23 -0
- package/dist/skills/commands/add.d.ts.map +1 -0
- package/dist/skills/commands/add.js +206 -0
- package/dist/skills/commands/add.js.map +1 -0
- package/dist/skills/commands/info.d.ts +14 -0
- package/dist/skills/commands/info.d.ts.map +1 -0
- package/dist/skills/commands/info.js +99 -0
- package/dist/skills/commands/info.js.map +1 -0
- package/dist/skills/commands/list.d.ts +19 -0
- package/dist/skills/commands/list.d.ts.map +1 -0
- package/dist/skills/commands/list.js +163 -0
- package/dist/skills/commands/list.js.map +1 -0
- package/dist/skills/commands/publish.d.ts +56 -0
- package/dist/skills/commands/publish.d.ts.map +1 -0
- package/dist/skills/commands/publish.js +272 -0
- package/dist/skills/commands/publish.js.map +1 -0
- package/dist/skills/commands/remove.d.ts +19 -0
- package/dist/skills/commands/remove.d.ts.map +1 -0
- package/dist/skills/commands/remove.js +96 -0
- package/dist/skills/commands/remove.js.map +1 -0
- package/dist/skills/commands/update.d.ts +31 -0
- package/dist/skills/commands/update.d.ts.map +1 -0
- package/dist/skills/commands/update.js +132 -0
- package/dist/skills/commands/update.js.map +1 -0
- package/dist/skills/index.d.ts +22 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +33 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/manifest.d.ts +54 -0
- package/dist/skills/manifest.d.ts.map +1 -0
- package/dist/skills/manifest.js +162 -0
- package/dist/skills/manifest.js.map +1 -0
- package/dist/skills/registry-local.d.ts +67 -0
- package/dist/skills/registry-local.d.ts.map +1 -0
- package/dist/skills/registry-local.js +130 -0
- package/dist/skills/registry-local.js.map +1 -0
- package/dist/skills/registry-mock.json +109 -0
- package/dist/skills/registry-remote.d.ts +110 -0
- package/dist/skills/registry-remote.d.ts.map +1 -0
- package/dist/skills/registry-remote.js +246 -0
- package/dist/skills/registry-remote.js.map +1 -0
- package/dist/skills/registry.d.ts +49 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +94 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/tests/manifest.spec.d.ts +8 -0
- package/dist/skills/tests/manifest.spec.d.ts.map +1 -0
- package/dist/skills/tests/manifest.spec.js +176 -0
- package/dist/skills/tests/manifest.spec.js.map +1 -0
- package/dist/skills/tests/publish.spec.d.ts +12 -0
- package/dist/skills/tests/publish.spec.d.ts.map +1 -0
- package/dist/skills/tests/publish.spec.js +276 -0
- package/dist/skills/tests/publish.spec.js.map +1 -0
- package/dist/skills/tests/registry-local.spec.d.ts +8 -0
- package/dist/skills/tests/registry-local.spec.d.ts.map +1 -0
- package/dist/skills/tests/registry-local.spec.js +231 -0
- package/dist/skills/tests/registry-local.spec.js.map +1 -0
- package/dist/skills/tests/registry.spec.d.ts +7 -0
- package/dist/skills/tests/registry.spec.d.ts.map +1 -0
- package/dist/skills/tests/registry.spec.js +58 -0
- package/dist/skills/tests/registry.spec.js.map +1 -0
- package/dist/skills/tests/remote-registry.spec.d.ts +9 -0
- package/dist/skills/tests/remote-registry.spec.d.ts.map +1 -0
- package/dist/skills/tests/remote-registry.spec.js +357 -0
- package/dist/skills/tests/remote-registry.spec.js.map +1 -0
- package/dist/skills/tests/update.spec.d.ts +9 -0
- package/dist/skills/tests/update.spec.d.ts.map +1 -0
- package/dist/skills/tests/update.spec.js +166 -0
- package/dist/skills/tests/update.spec.js.map +1 -0
- package/dist/types/Refine.types.d.ts +96 -0
- package/dist/types/Refine.types.d.ts.map +1 -0
- package/dist/types/Refine.types.js +19 -0
- package/dist/types/Refine.types.js.map +1 -0
- package/dist/types/Review.types.d.ts +86 -0
- package/dist/types/Review.types.d.ts.map +1 -0
- package/dist/types/Review.types.js +15 -0
- package/dist/types/Review.types.js.map +1 -0
- package/dist/types/UpdateManifest.types.d.ts +91 -0
- package/dist/types/UpdateManifest.types.d.ts.map +1 -0
- package/dist/types/UpdateManifest.types.js +13 -0
- package/dist/types/UpdateManifest.types.js.map +1 -0
- package/dist/utils/ReviewRunner.d.ts +42 -0
- package/dist/utils/ReviewRunner.d.ts.map +1 -0
- package/dist/utils/ReviewRunner.js +175 -0
- package/dist/utils/ReviewRunner.js.map +1 -0
- package/dist/utils/UpdateApplier.d.ts +42 -0
- package/dist/utils/UpdateApplier.d.ts.map +1 -0
- package/dist/utils/UpdateApplier.js +207 -0
- package/dist/utils/UpdateApplier.js.map +1 -0
- package/dist/utils/UpdateDetector.d.ts +56 -0
- package/dist/utils/UpdateDetector.d.ts.map +1 -0
- package/dist/utils/UpdateDetector.js +164 -0
- package/dist/utils/UpdateDetector.js.map +1 -0
- package/dist/utils/banner.d.ts +28 -0
- package/dist/utils/banner.d.ts.map +1 -0
- package/dist/utils/banner.js +77 -0
- package/dist/utils/banner.js.map +1 -0
- package/dist/utils/banner.spec.d.ts +5 -0
- package/dist/utils/banner.spec.d.ts.map +1 -0
- package/dist/utils/banner.spec.js +253 -0
- package/dist/utils/banner.spec.js.map +1 -0
- package/dist/utils/complexity-analyzer.d.ts +60 -0
- package/dist/utils/complexity-analyzer.d.ts.map +1 -0
- package/dist/utils/complexity-analyzer.js +292 -0
- package/dist/utils/complexity-analyzer.js.map +1 -0
- package/dist/utils/confidence.d.ts +41 -0
- package/dist/utils/confidence.d.ts.map +1 -0
- package/dist/utils/confidence.js +101 -0
- package/dist/utils/confidence.js.map +1 -0
- package/dist/utils/datamodel.d.ts +41 -0
- package/dist/utils/datamodel.d.ts.map +1 -0
- package/dist/utils/datamodel.js +535 -0
- package/dist/utils/datamodel.js.map +1 -0
- package/dist/utils/dna-detector.d.ts +61 -0
- package/dist/utils/dna-detector.d.ts.map +1 -0
- package/dist/utils/dna-detector.js +354 -0
- package/dist/utils/dna-detector.js.map +1 -0
- package/dist/utils/dna-facts.d.ts +13 -0
- package/dist/utils/dna-facts.d.ts.map +1 -0
- package/dist/utils/dna-facts.js +109 -0
- package/dist/utils/dna-facts.js.map +1 -0
- package/dist/utils/excalidraw-renderer.d.ts +11 -71
- package/dist/utils/excalidraw-renderer.d.ts.map +1 -1
- package/dist/utils/excalidraw-renderer.js +29 -162
- package/dist/utils/excalidraw-renderer.js.map +1 -1
- package/dist/utils/graph-renderer.d.ts +115 -0
- package/dist/utils/graph-renderer.d.ts.map +1 -0
- package/dist/utils/graph-renderer.js +216 -0
- package/dist/utils/graph-renderer.js.map +1 -0
- package/dist/utils/migration.d.ts +64 -0
- package/dist/utils/migration.d.ts.map +1 -0
- package/dist/utils/migration.js +183 -0
- package/dist/utils/migration.js.map +1 -0
- package/dist/utils/module-detector.d.ts +46 -0
- package/dist/utils/module-detector.d.ts.map +1 -0
- package/dist/utils/module-detector.js +348 -0
- package/dist/utils/module-detector.js.map +1 -0
- package/dist/utils/project-generator.d.ts.map +1 -1
- package/dist/utils/project-generator.js +273 -254
- package/dist/utils/project-generator.js.map +1 -1
- package/dist/utils/reverse-facts.d.ts +50 -0
- package/dist/utils/reverse-facts.d.ts.map +1 -0
- package/dist/utils/reverse-facts.js +291 -0
- package/dist/utils/reverse-facts.js.map +1 -0
- package/dist/utils/stack-bootstrap.js +371 -371
- package/dist/utils/static-analyzer.d.ts +29 -0
- package/dist/utils/static-analyzer.d.ts.map +1 -0
- package/dist/utils/static-analyzer.js +390 -0
- package/dist/utils/static-analyzer.js.map +1 -0
- package/dist/utils/version-compare.d.ts +27 -0
- package/dist/utils/version-compare.d.ts.map +1 -0
- package/dist/utils/version-compare.js +47 -0
- package/dist/utils/version-compare.js.map +1 -0
- package/package.json +8 -3
- package/templates/DARE-dag-example.yaml +280 -280
- package/templates/UPDATE-MANIFEST.json +48 -0
- package/templates/backend/node-nestjs/.env.example +9 -9
- package/templates/backend/node-nestjs/nest-cli.json +8 -8
- package/templates/backend/node-nestjs/package.json +50 -50
- package/templates/backend/node-nestjs/src/app.controller.ts +12 -12
- package/templates/backend/node-nestjs/src/app.module.ts +15 -15
- package/templates/backend/node-nestjs/src/app.service.ts +8 -8
- package/templates/backend/node-nestjs/src/main.ts +24 -24
- package/templates/backend/node-nestjs/tsconfig.json +21 -21
- package/templates/backend/php-laravel/.env.example +22 -22
- package/templates/backend/php-laravel/app/Http/Controllers/HealthController.php +15 -15
- package/templates/backend/php-laravel/composer.json +40 -40
- package/templates/backend/python-fastapi/.env.example +4 -4
- package/templates/backend/python-fastapi/app/api/router.py +8 -8
- package/templates/backend/python-fastapi/app/core/config.py +20 -20
- package/templates/backend/python-fastapi/main.py +35 -35
- package/templates/backend/python-fastapi/requirements.txt +13 -13
- package/templates/backend/rust-axum/.env.example +3 -3
- package/templates/backend/rust-axum/Cargo.toml +23 -23
- package/templates/backend/rust-axum/src/errors.rs +30 -30
- package/templates/backend/rust-axum/src/main.rs +32 -32
- package/templates/backend/rust-axum/src/routes.rs +6 -6
- package/templates/frontend/leptos-csr/.cargo/config.toml +2 -2
- package/templates/frontend/leptos-csr/Cargo.toml +16 -16
- package/templates/frontend/leptos-csr/Trunk.toml +10 -10
- package/templates/frontend/leptos-csr/index.html +11 -11
- package/templates/frontend/leptos-csr/src/lib.rs +20 -20
- package/templates/frontend/leptos-csr/style/main.scss +19 -19
- package/templates/frontend/leptos-fullstack/.cargo/config.toml +4 -4
- package/templates/frontend/leptos-fullstack/Cargo.toml +56 -56
- package/templates/frontend/leptos-fullstack/src/app.rs +49 -49
- package/templates/frontend/leptos-fullstack/src/lib.rs +9 -9
- package/templates/frontend/leptos-fullstack/src/main.rs +29 -29
- package/templates/frontend/leptos-fullstack/style/main.scss +19 -19
- package/templates/frontend/react/index.html +12 -12
- package/templates/frontend/react/package.json +35 -35
- package/templates/frontend/react/src/App.tsx +25 -25
- package/templates/frontend/react/src/main.tsx +9 -9
- package/templates/frontend/vue/package.json +32 -32
- package/templates/frontend/vue/src/App.vue +7 -7
- package/templates/frontend/vue/src/main.ts +10 -10
- package/templates/frontend/vue/src/router/index.ts +14 -14
- package/templates/frontend/vue/src/views/HomeView.vue +6 -6
- package/templates/hooks/pre-commit-dare-validate +24 -24
- package/templates/ide/antigravity/.agents/skills/dare-ax/SKILL.md +152 -0
- package/templates/ide/antigravity/.agents/skills/dare-blueprint/SKILL.md +180 -36
- package/templates/ide/antigravity/.agents/skills/dare-dag-build/SKILL.md +154 -0
- package/templates/ide/antigravity/.agents/skills/dare-dag-run/SKILL.md +130 -0
- package/templates/ide/antigravity/.agents/skills/dare-dag-runner/SKILL.md +203 -203
- package/templates/ide/antigravity/.agents/skills/dare-dna/SKILL.md +63 -0
- package/templates/ide/antigravity/.agents/skills/dare-docker/SKILL.md +315 -0
- package/templates/ide/antigravity/.agents/skills/dare-frontend-design/SKILL.md +192 -0
- package/templates/ide/antigravity/.agents/skills/dare-laravel-api/SKILL.md +337 -0
- package/templates/ide/antigravity/.agents/skills/dare-layered-design/SKILL.md +166 -0
- package/templates/ide/antigravity/.agents/skills/dare-llm-integration/SKILL.md +217 -0
- package/templates/ide/antigravity/.agents/skills/dare-migrate/SKILL.md +61 -0
- package/templates/ide/antigravity/.agents/skills/dare-quality-telemetry/SKILL.md +187 -0
- package/templates/ide/antigravity/.agents/skills/dare-realtime/SKILL.md +217 -0
- package/templates/ide/antigravity/.agents/skills/dare-refine/SKILL.md +114 -0
- package/templates/ide/antigravity/.agents/skills/dare-reverse/SKILL.md +108 -0
- package/templates/ide/antigravity/.agents/skills/dare-review/SKILL.md +111 -0
- package/templates/ide/antigravity/.agents/skills/dare-rust-leptos/SKILL.md +263 -0
- package/templates/ide/antigravity/.agents/skills/dare-rust-workspace/SKILL.md +275 -275
- package/templates/ide/antigravity/.agents/skills/dare-security/SKILL.md +274 -0
- package/templates/ide/antigravity/.agents/skills/dare-tasks/SKILL.md +265 -224
- package/templates/ide/antigravity/.agents/skills/dare-telemetry/SKILL.md +188 -0
- package/templates/ide/antigravity/.agents/skills/skill-fastapi-api/SKILL.md +343 -0
- package/templates/ide/antigravity/.agents/skills/skill-go-gin-api/SKILL.md +377 -0
- package/templates/ide/antigravity/.agents/skills/skill-mcp-server/SKILL.md +382 -0
- package/templates/ide/antigravity/.agents/skills/skill-nestjs-api/SKILL.md +326 -0
- package/templates/ide/antigravity/.agents/skills/skill-rails-api/SKILL.md +393 -0
- package/templates/ide/antigravity/templates/BLUEPRINT-template.md +193 -193
- package/templates/ide/antigravity/templates/DESIGN-template.md +129 -129
- package/templates/ide/antigravity/templates/TASK-SPEC-template.md +141 -100
- package/templates/ide/claude/.claude/commands/dare-ax.md +131 -0
- package/templates/ide/claude/.claude/commands/dare-blueprint.md +134 -78
- package/templates/ide/claude/.claude/commands/dare-bugfix-design.md +119 -0
- package/templates/ide/claude/.claude/commands/dare-dag-build.md +151 -110
- package/templates/ide/claude/.claude/commands/dare-dag-run.md +109 -109
- package/templates/ide/claude/.claude/commands/dare-dag-runner.md +117 -0
- package/templates/ide/claude/.claude/commands/dare-dag-viz.md +197 -197
- package/templates/ide/claude/.claude/commands/dare-design.md +69 -69
- package/templates/ide/claude/.claude/commands/dare-dna.md +75 -0
- package/templates/ide/claude/.claude/commands/dare-docker.md +207 -0
- package/templates/ide/claude/.claude/commands/dare-execute.md +152 -152
- package/templates/ide/claude/.claude/commands/dare-feature-design.md +147 -0
- package/templates/ide/claude/.claude/commands/dare-frontend-design.md +149 -0
- package/templates/ide/claude/.claude/commands/dare-laravel-api.md +211 -0
- package/templates/ide/claude/.claude/commands/dare-layered-design.md +124 -0
- package/templates/ide/claude/.claude/commands/dare-llm-integration.md +148 -0
- package/templates/ide/claude/.claude/commands/dare-migrate.md +72 -0
- package/templates/ide/claude/.claude/commands/dare-quality-telemetry.md +166 -0
- package/templates/ide/claude/.claude/commands/dare-realtime.md +159 -0
- package/templates/ide/claude/.claude/commands/dare-refine.md +145 -0
- package/templates/ide/claude/.claude/commands/dare-reverse.md +139 -0
- package/templates/ide/claude/.claude/commands/dare-review.md +113 -0
- package/templates/ide/claude/.claude/commands/dare-rust-leptos.md +269 -269
- package/templates/ide/claude/.claude/commands/dare-rust-workspace.md +209 -209
- package/templates/ide/claude/.claude/commands/dare-security.md +232 -232
- package/templates/ide/claude/.claude/commands/dare-tasks.md +70 -70
- package/templates/ide/claude/.claude/commands/dare-telemetry.md +132 -0
- package/templates/ide/claude/.claude/commands/skill-fastapi-api.md +205 -0
- package/templates/ide/claude/.claude/commands/skill-go-gin-api.md +232 -0
- package/templates/ide/claude/.claude/commands/skill-mcp-server.md +228 -0
- package/templates/ide/claude/.claude/commands/skill-nestjs-api.md +210 -0
- package/templates/ide/claude/.claude/commands/skill-rails-api.md +236 -0
- package/templates/ide/claude/.claude/settings.example.json +35 -35
- package/templates/ide/claude/CLAUDE.md +146 -146
- package/templates/ide/claude/templates/BLUEPRINT-template.md +193 -193
- package/templates/ide/claude/templates/DESIGN-template.md +129 -129
- package/templates/ide/claude/templates/TASK-SPEC-template.md +141 -100
- package/templates/ide/cursor/.cursor/commands/dag-viz.md +139 -0
- package/templates/ide/cursor/.cursor/commands/generate-blueprint.md +86 -41
- package/templates/ide/cursor/.cursor/commands/generate-design.md +35 -35
- package/templates/ide/cursor/.cursor/commands/generate-tasks.md +184 -142
- package/templates/ide/cursor/.cursor/commands/refine-task.md +107 -0
- package/templates/ide/cursor/.cursor/commands/review-task.md +91 -0
- package/templates/ide/cursor/.cursor/commands/run-dag.md +110 -110
- package/templates/ide/cursor/.cursor/rules/skill-ax.mdc +263 -0
- package/templates/ide/cursor/.cursor/rules/skill-dag-build.mdc +173 -0
- package/templates/ide/cursor/.cursor/rules/skill-dag-run.mdc +134 -0
- package/templates/ide/cursor/.cursor/rules/skill-dag-runner.mdc +221 -221
- package/templates/ide/cursor/.cursor/rules/skill-dna.mdc +63 -0
- package/templates/ide/cursor/.cursor/rules/skill-fastapi-api.mdc +352 -0
- package/templates/ide/cursor/.cursor/rules/skill-frontend-design.mdc +244 -0
- package/templates/ide/cursor/.cursor/rules/skill-go-gin-api.mdc +371 -0
- package/templates/ide/cursor/.cursor/rules/skill-layered-design.mdc +266 -0
- package/templates/ide/cursor/.cursor/rules/skill-llm-integration.mdc +295 -0
- package/templates/ide/cursor/.cursor/rules/skill-mcp-server.mdc +367 -0
- package/templates/ide/cursor/.cursor/rules/skill-migrate.mdc +58 -0
- package/templates/ide/cursor/.cursor/rules/skill-nestjs-api.mdc +346 -0
- package/templates/ide/cursor/.cursor/rules/skill-quality-telemetry.mdc +248 -0
- package/templates/ide/cursor/.cursor/rules/skill-rails-api.mdc +400 -0
- package/templates/ide/cursor/.cursor/rules/skill-realtime.mdc +262 -0
- package/templates/ide/cursor/.cursor/rules/skill-reverse.mdc +107 -0
- package/templates/ide/cursor/.cursor/rules/skill-rust-leptos.mdc +281 -0
- package/templates/ide/cursor/.cursor/rules/skill-rust-workspace.mdc +312 -312
- package/templates/ide/cursor/.cursor/rules/skill-security.mdc +245 -245
- package/templates/ide/cursor/templates/BLUEPRINT-template.md +193 -193
- package/templates/ide/cursor/templates/DESIGN-template.md +129 -129
- package/templates/ide/cursor/templates/TASK-SPEC-template.md +141 -100
- package/templates/shared/docker-compose.yml +41 -41
- package/dist/__tests__/dag-runner/adapters.test.d.ts +0 -2
- package/dist/__tests__/dag-runner/adapters.test.d.ts.map +0 -1
- package/dist/__tests__/dag-runner/adapters.test.js +0 -134
- package/dist/__tests__/dag-runner/adapters.test.js.map +0 -1
- package/dist/dag-runner/adapters/antigravity.d.ts +0 -6
- package/dist/dag-runner/adapters/antigravity.d.ts.map +0 -1
- package/dist/dag-runner/adapters/antigravity.js +0 -54
- package/dist/dag-runner/adapters/antigravity.js.map +0 -1
- package/dist/dag-runner/adapters/claude.d.ts +0 -6
- package/dist/dag-runner/adapters/claude.d.ts.map +0 -1
- package/dist/dag-runner/adapters/claude.js +0 -48
- package/dist/dag-runner/adapters/claude.js.map +0 -1
- package/dist/dag-runner/adapters/cursor.d.ts +0 -6
- package/dist/dag-runner/adapters/cursor.d.ts.map +0 -1
- package/dist/dag-runner/adapters/cursor.js +0 -58
- package/dist/dag-runner/adapters/cursor.js.map +0 -1
- package/dist/dag-runner/adapters/index.d.ts +0 -46
- package/dist/dag-runner/adapters/index.d.ts.map +0 -1
- package/dist/dag-runner/adapters/index.js +0 -55
- package/dist/dag-runner/adapters/index.js.map +0 -1
- package/dist/dag-runner/utils/timeout.d.ts +0 -27
- package/dist/dag-runner/utils/timeout.d.ts.map +0 -1
- package/dist/dag-runner/utils/timeout.js +0 -55
- package/dist/dag-runner/utils/timeout.js.map +0 -1
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Padrões DARE para APIs em Ruby on Rails 8 — API mode, ActiveRecord, Solid Queue, Solid Cable, Action Cable, strong parameters, services, serializers (Blueprinter/Alba), Devise/JWT, rack-attack, rswag/grape-swagger.
|
|
3
|
+
globs: **/*.rb,Gemfile,Gemfile.lock,config/**/*.rb,db/**/*.rb,spec/**/*.rb,app/**/*.rb
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill: Rails 8 API DARE
|
|
8
|
+
|
|
9
|
+
Você é um desenvolvedor sênior Ruby on Rails 8.x especializado em APIs. Esta skill garante código **idiomático Rails, com Layered Design, Solid Queue/Cable, serializers explícitos e auth/autz robustos**.
|
|
10
|
+
|
|
11
|
+
## Stack canônica
|
|
12
|
+
|
|
13
|
+
- **Ruby 3.3+**
|
|
14
|
+
- **Rails 8.x** modo API (`rails new --api`)
|
|
15
|
+
- **PostgreSQL 16**
|
|
16
|
+
- **Solid Queue** — substitui Sidekiq, built-in Rails 8
|
|
17
|
+
- **Solid Cable** — substitui Redis para Action Cable, built-in Rails 8
|
|
18
|
+
- **Devise + devise-jwt** ou JWT puro
|
|
19
|
+
- **Pundit** ou **CanCanCan**
|
|
20
|
+
- **Blueprinter** ou **Alba** para serializers
|
|
21
|
+
- **rack-attack** para rate limit
|
|
22
|
+
- **rswag** para OpenAPI/Swagger
|
|
23
|
+
- **RSpec + FactoryBot + Faker**
|
|
24
|
+
- **Rubocop + rubocop-rails-omakase** (estilo oficial)
|
|
25
|
+
- **bundler-audit**
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Layered Design em Rails
|
|
30
|
+
|
|
31
|
+
| Camada DARE | Pasta Rails |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Handler | `app/controllers/api/v1/` |
|
|
34
|
+
| Service | `app/services/` (ou `app/interactors/`) |
|
|
35
|
+
| Repository | `app/repositories/` (opcional em Rails) |
|
|
36
|
+
| Model | `app/models/` |
|
|
37
|
+
| Presenter | `app/blueprints/` ou `app/serializers/` |
|
|
38
|
+
|
|
39
|
+
> Em Rails 8 API, Repositories são opcionais — ActiveRecord queries dentro de Services bem encapsulados é aceito.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Controllers
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
module Api
|
|
47
|
+
module V1
|
|
48
|
+
class UsersController < ApplicationController
|
|
49
|
+
before_action :authenticate_user!
|
|
50
|
+
|
|
51
|
+
def create
|
|
52
|
+
result = RegisterUser.new(user_params).call
|
|
53
|
+
if result.success?
|
|
54
|
+
render json: UserBlueprint.render(result.user), status: :created
|
|
55
|
+
else
|
|
56
|
+
render json: { error: result.error_code }, status: :conflict
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def user_params
|
|
63
|
+
params.require(:user).permit(:email, :name, :password)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Regras:
|
|
71
|
+
- Apenas: autenticar → strong params → chamar Service → renderizar Blueprint
|
|
72
|
+
- NUNCA: query AR no controller, lógica de negócio
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Services (Service Object)
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
class RegisterUser
|
|
80
|
+
Result = Struct.new(:success?, :user, :error_code, keyword_init: true)
|
|
81
|
+
|
|
82
|
+
def initialize(params)
|
|
83
|
+
@params = params
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def call
|
|
87
|
+
return Result.new(success?: false, error_code: 'USER_EXISTS') if User.exists?(email: @params[:email])
|
|
88
|
+
user = User.create!(@params)
|
|
89
|
+
Result.new(success?: true, user: user)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Alternativa com gem `interactor`:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class RegisterUser
|
|
98
|
+
include Interactor
|
|
99
|
+
|
|
100
|
+
def call
|
|
101
|
+
context.fail!(error_code: 'USER_EXISTS') if User.exists?(email: context.params[:email])
|
|
102
|
+
context.user = User.create!(context.params)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Models
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
class User < ApplicationRecord
|
|
113
|
+
has_secure_password
|
|
114
|
+
|
|
115
|
+
validates :email, presence: true, uniqueness: true, format: URI::MailTo::EMAIL_REGEXP
|
|
116
|
+
validates :name, presence: true
|
|
117
|
+
validates :password, length: { minimum: 12 }, if: :password_required?
|
|
118
|
+
|
|
119
|
+
has_many :sessions, dependent: :destroy
|
|
120
|
+
scope :active, -> { where(deleted_at: nil) }
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`has_secure_password` usa BCrypt (default). Para Argon2, troque para gem `argon2` + métodos customizados.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Serializers (Blueprinter)
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
class UserBlueprint < Blueprinter::Base
|
|
132
|
+
identifier :id
|
|
133
|
+
fields :email, :name
|
|
134
|
+
|
|
135
|
+
field :created_at do |user|
|
|
136
|
+
user.created_at.iso8601
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
view :extended do
|
|
140
|
+
fields :phone, :address
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Uso:
|
|
145
|
+
UserBlueprint.render(user)
|
|
146
|
+
UserBlueprint.render(users, view: :extended)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Auth (devise-jwt)
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# Gemfile
|
|
155
|
+
gem 'devise'
|
|
156
|
+
gem 'devise-jwt'
|
|
157
|
+
|
|
158
|
+
# config/initializers/devise.rb
|
|
159
|
+
config.jwt do |jwt|
|
|
160
|
+
jwt.secret = Rails.application.credentials.devise_jwt_secret_key!
|
|
161
|
+
jwt.dispatch_requests = [['POST', %r{^/api/v1/login$}]]
|
|
162
|
+
jwt.revocation_requests = [['DELETE', %r{^/api/v1/logout$}]]
|
|
163
|
+
jwt.expiration_time = 15.minutes.to_i
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Refresh token com rotação em tabela própria.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Rate limit (rack-attack)
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
# config/initializers/rack_attack.rb
|
|
175
|
+
class Rack::Attack
|
|
176
|
+
throttle('login/ip', limit: 5, period: 15.minutes) do |req|
|
|
177
|
+
req.ip if req.path == '/api/v1/login' && req.post?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
throttle('login/email', limit: 5, period: 15.minutes) do |req|
|
|
181
|
+
if req.path == '/api/v1/login' && req.post?
|
|
182
|
+
req.params['email'].to_s.downcase.gsub(/\s+/, '')
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
throttle('api/ip', limit: 100, period: 1.minute) do |req|
|
|
187
|
+
req.ip if req.path.start_with?('/api/')
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Solid Queue (jobs)
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
class SendWelcomeEmailJob < ApplicationJob
|
|
198
|
+
queue_as :default
|
|
199
|
+
|
|
200
|
+
def perform(user_id)
|
|
201
|
+
user = User.find(user_id)
|
|
202
|
+
UserMailer.welcome(user).deliver_now
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# config/queue.yml — Solid Queue default em Rails 8
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Solid Cable + Action Cable
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
class NotificationsChannel < ApplicationCable::Channel
|
|
215
|
+
def subscribed
|
|
216
|
+
stream_for current_user
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Broadcast em qualquer lugar
|
|
221
|
+
NotificationsChannel.broadcast_to(user, type: 'message.sent.v1', payload: {...})
|
|
222
|
+
|
|
223
|
+
# Connection auth
|
|
224
|
+
class ApplicationCable::Connection < ActionCable::Connection::Base
|
|
225
|
+
identified_by :current_user
|
|
226
|
+
|
|
227
|
+
def connect
|
|
228
|
+
self.current_user = find_verified_user
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
233
|
+
def find_verified_user
|
|
234
|
+
if (user = decode_jwt(request.params[:token]))
|
|
235
|
+
user
|
|
236
|
+
else
|
|
237
|
+
reject_unauthorized_connection
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## OpenAPI (rswag)
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# Gemfile
|
|
249
|
+
group :development, :test do
|
|
250
|
+
gem 'rswag-specs'
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# spec/integration/users_spec.rb
|
|
254
|
+
require 'swagger_helper'
|
|
255
|
+
|
|
256
|
+
RSpec.describe 'Users API', type: :request do
|
|
257
|
+
path '/api/v1/users' do
|
|
258
|
+
post 'Creates a user' do
|
|
259
|
+
tags 'Users'
|
|
260
|
+
consumes 'application/json'
|
|
261
|
+
parameter name: :user, in: :body, schema: {
|
|
262
|
+
type: :object,
|
|
263
|
+
properties: {
|
|
264
|
+
email: { type: :string },
|
|
265
|
+
name: { type: :string },
|
|
266
|
+
password: { type: :string, minLength: 12 },
|
|
267
|
+
},
|
|
268
|
+
required: %w[email name password],
|
|
269
|
+
}
|
|
270
|
+
response '201', 'user created' do ... end
|
|
271
|
+
response '409', 'duplicate email' do ... end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# rake rswag:specs:swaggerize → swagger/v1/swagger.yaml
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Exportar para JSON e expor em `/openapi.json`.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Testes RSpec
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
# spec/services/register_user_spec.rb
|
|
287
|
+
RSpec.describe RegisterUser do
|
|
288
|
+
let(:params) { { email: 'jane@example.com', name: 'Jane', password: 'longsecret123' } }
|
|
289
|
+
|
|
290
|
+
it 'cria usuário com sucesso' do
|
|
291
|
+
result = described_class.new(params).call
|
|
292
|
+
expect(result.success?).to be true
|
|
293
|
+
expect(result.user.email).to eq 'jane@example.com'
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'falha se email já existe' do
|
|
297
|
+
User.create!(params)
|
|
298
|
+
result = described_class.new(params).call
|
|
299
|
+
expect(result.success?).to be false
|
|
300
|
+
expect(result.error_code).to eq 'USER_EXISTS'
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# spec/requests/api/v1/users_spec.rb
|
|
305
|
+
RSpec.describe 'POST /api/v1/users', type: :request do
|
|
306
|
+
let(:admin) { create(:user, :admin) }
|
|
307
|
+
|
|
308
|
+
it 'cria com sucesso' do
|
|
309
|
+
post '/api/v1/users',
|
|
310
|
+
params: { user: { email: 'jane@example.com', name: 'Jane', password: 'longsecret123' } },
|
|
311
|
+
headers: { 'Authorization' => "Bearer #{token_for(admin)}" }
|
|
312
|
+
expect(response).to have_http_status(:created)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Antipatterns
|
|
320
|
+
|
|
321
|
+
| AP | Antipattern | Correção |
|
|
322
|
+
|---|---|---|
|
|
323
|
+
| AP-01 | Query AR no controller | Service object |
|
|
324
|
+
| AP-02 | Lógica no controller | Service object |
|
|
325
|
+
| AP-03 | `render json: user` | Blueprinter/Alba |
|
|
326
|
+
| AP-04 | Fat Model (>500 linhas) | Concerns + Services |
|
|
327
|
+
| AP-05 | Skip strong params | `params.require(...).permit(...)` |
|
|
328
|
+
| AP-06 | Sem rack-attack em login | rate limit obrigatório |
|
|
329
|
+
| AP-07 | JWT secret em código | `Rails.application.credentials` |
|
|
330
|
+
| AP-08 | Logs com PII | `filter_parameters` configurado |
|
|
331
|
+
| AP-09 | N+1 sem `includes` | `bullet` gem em dev |
|
|
332
|
+
| AP-10 | Sem `--api` no rails new | use modo API |
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Segurança
|
|
337
|
+
|
|
338
|
+
- `has_secure_password` (BCrypt cost ≥ 12)
|
|
339
|
+
- `force_ssl = true` em produção
|
|
340
|
+
- `secure_headers` gem ou middleware nativo Rails 8
|
|
341
|
+
- `rack-cors` com origens específicas
|
|
342
|
+
- `filter_parameters` para `password`, `token`, `secret`, `ssn`
|
|
343
|
+
- bundler-audit no CI
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## CI
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
bundle exec rubocop
|
|
351
|
+
bundle exec rspec
|
|
352
|
+
bundle exec bundler-audit check --update
|
|
353
|
+
bundle exec rails db:schema:dump
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Aplicação por fase DARE
|
|
359
|
+
|
|
360
|
+
### Design
|
|
361
|
+
- Listar endpoints REST com tags + métodos
|
|
362
|
+
- Definir strong params por controller
|
|
363
|
+
- Auth strategy (Devise/JWT)
|
|
364
|
+
|
|
365
|
+
### Blueprint
|
|
366
|
+
- Estrutura `controllers/api/v1/`, `services/`, `blueprints/`
|
|
367
|
+
- Schema do banco (migrations)
|
|
368
|
+
- Strategy de jobs (Solid Queue) e realtime (Solid Cable)
|
|
369
|
+
|
|
370
|
+
### Tasks
|
|
371
|
+
- Por feature: Migration, Model, Service, Controller, Blueprint, RSpec
|
|
372
|
+
- Task de configuração rack-attack + filter_parameters
|
|
373
|
+
- Task de OpenAPI com rswag
|
|
374
|
+
|
|
375
|
+
### Execute
|
|
376
|
+
- Ralph Loop:
|
|
377
|
+
```bash
|
|
378
|
+
bundle exec rubocop && bundle exec rspec && bundle exec bundler-audit check
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Checklist final
|
|
384
|
+
|
|
385
|
+
- [ ] Ruby 3.3+, Rails 8.x modo API
|
|
386
|
+
- [ ] Rubocop + rails-omakase configurado
|
|
387
|
+
- [ ] Strong parameters em todos os controllers
|
|
388
|
+
- [ ] Lógica em Services (não em controllers)
|
|
389
|
+
- [ ] Blueprinter/Alba (não `render json: object`)
|
|
390
|
+
- [ ] rack-attack para login + APIs públicas
|
|
391
|
+
- [ ] `has_secure_password` (não senha plaintext)
|
|
392
|
+
- [ ] JWT secret via credentials.yml.enc
|
|
393
|
+
- [ ] `filter_parameters` configurado
|
|
394
|
+
- [ ] Solid Queue para jobs (default Rails 8)
|
|
395
|
+
- [ ] OpenAPI exportado via rswag
|
|
396
|
+
- [ ] bundler-audit no CI
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
Skill licenciada MIT — parte do DARE Method v3.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Comunicação real-time (WebSocket, SSE) DARE-compliant — eventos tipados com JSON Schema, subscriptions autorizadas, reconexão com exponential backoff, e gerenciamento de subscriptions sem ghost listeners.
|
|
3
|
+
globs: **/realtime/**,**/ws/**,**/socket*.ts,**/socket*.py,**/socket*.rs,**/cable/**,**/channels/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill: Realtime DARE
|
|
8
|
+
|
|
9
|
+
Você é um especialista em comunicação real-time (WebSocket, SSE). Esta skill garante que toda integração real-time em projeto DARE seja **tipada, autorizada, reconectável e sem ghost listeners**.
|
|
10
|
+
|
|
11
|
+
## Arquitetura recomendada
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────────────────────────────────────────┐
|
|
15
|
+
│ Event Registry │
|
|
16
|
+
│ - Tipos de evento com schema JSON │
|
|
17
|
+
│ - Versionamento (.v1, .v2…) │
|
|
18
|
+
│ - Autorização declarada por evento │
|
|
19
|
+
└─────────────────────────────────────────────────────────┘
|
|
20
|
+
↓
|
|
21
|
+
┌─────────────────────────────────────────────────────────┐
|
|
22
|
+
│ Subscription Manager │
|
|
23
|
+
│ - Map<connection_id, Set<event_type>> │
|
|
24
|
+
│ - Cleanup garantido on disconnect │
|
|
25
|
+
└─────────────────────────────────────────────────────────┘
|
|
26
|
+
↓
|
|
27
|
+
┌─────────────────────────────────────────────────────────┐
|
|
28
|
+
│ Reconnect Strategy │
|
|
29
|
+
│ - Exponential backoff (1s, 2s, 4s, 8s, max 30s) │
|
|
30
|
+
│ - Jitter para evitar thundering herd │
|
|
31
|
+
│ - Resync de estado após reconectar │
|
|
32
|
+
└─────────────────────────────────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Os 4 pilares
|
|
38
|
+
|
|
39
|
+
### 1. Schema validation de eventos
|
|
40
|
+
|
|
41
|
+
Todo evento WS/SSE tem schema JSON declarado:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// src/realtime/event_registry.ts
|
|
45
|
+
import { z } from 'zod';
|
|
46
|
+
|
|
47
|
+
export const EventRegistry = {
|
|
48
|
+
'user.created.v1': {
|
|
49
|
+
schema: z.object({
|
|
50
|
+
id: z.string().uuid(),
|
|
51
|
+
email: z.string().email(),
|
|
52
|
+
createdAt: z.string().datetime(),
|
|
53
|
+
}),
|
|
54
|
+
requires: 'admin',
|
|
55
|
+
},
|
|
56
|
+
'message.sent.v1': {
|
|
57
|
+
schema: z.object({
|
|
58
|
+
conversationId: z.string().uuid(),
|
|
59
|
+
senderId: z.string().uuid(),
|
|
60
|
+
body: z.string().max(2000),
|
|
61
|
+
sentAt: z.string().datetime(),
|
|
62
|
+
}),
|
|
63
|
+
requires: 'participant',
|
|
64
|
+
},
|
|
65
|
+
} as const;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Server valida antes de emitir, client valida antes de processar. Evento inválido = log + drop.
|
|
69
|
+
|
|
70
|
+
### 2. Registro central de tipos
|
|
71
|
+
|
|
72
|
+
Um único arquivo `event_registry.ts/py/rs`. Adicionar evento passa por PR explícito. Versionamento via sufixo `.v1`, `.v2`.
|
|
73
|
+
|
|
74
|
+
### 3. Reconexão com exponential backoff
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export class ReconnectStrategy {
|
|
78
|
+
private attempt = 0;
|
|
79
|
+
constructor(private base = 1000, private max = 30_000) {}
|
|
80
|
+
|
|
81
|
+
next(): number {
|
|
82
|
+
const delay = Math.min(this.base * 2 ** this.attempt, this.max);
|
|
83
|
+
const jitter = Math.random() * 1000;
|
|
84
|
+
this.attempt++;
|
|
85
|
+
return delay + jitter;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
reset() { this.attempt = 0; }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Após reconectar, **resync de estado** — buscar eventos perdidos via REST ou pedir snapshot.
|
|
93
|
+
|
|
94
|
+
### 4. Subscription manager (zero ghost listeners)
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
export class SubscriptionManager {
|
|
98
|
+
private subs = new Map<string, Set<string>>();
|
|
99
|
+
|
|
100
|
+
subscribe(connId: string, event: string) {
|
|
101
|
+
if (!this.subs.has(connId)) this.subs.set(connId, new Set());
|
|
102
|
+
this.subs.get(connId)!.add(event);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
unsubscribe(connId: string, event: string) {
|
|
106
|
+
this.subs.get(connId)?.delete(event);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Cleanup SEMPRE on disconnect — sem exceção
|
|
110
|
+
onDisconnect(connId: string) {
|
|
111
|
+
this.subs.delete(connId);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
metrics() {
|
|
115
|
+
return { connections: this.subs.size, totalSubs: [...this.subs.values()].reduce((s, set) => s + set.size, 0) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Métricas (verificáveis em CI/runtime)
|
|
123
|
+
|
|
124
|
+
| ID | Métrica | Como medir |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| M-01 | 100% de event types com JSON Schema | grep no registry — todo evento tem `schema:` |
|
|
127
|
+
| M-02 | 100% de subscriptions autorizadas | grep por `authorize(` antes de `subscribe(` |
|
|
128
|
+
| M-03 | 0 ghost listeners após desconexão | métrica runtime `subs.size === 0` após disconnect |
|
|
129
|
+
| M-04 | Reconnect strategy configurada | grep por `ReconnectStrategy` ou backoff manual |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Antipatterns
|
|
134
|
+
|
|
135
|
+
| AP | Antipattern | Sinal | Correção |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| AP-01 | Evento sem schema | `socket.emit('foo', anything)` | Adicionar schema no registry |
|
|
138
|
+
| AP-02 | `socket.on(...)` sem cleanup | listener vive após disconnect | Usar SubscriptionManager |
|
|
139
|
+
| AP-03 | Reconexão sem backoff | reconecta imediatamente em loop | Exponential + jitter |
|
|
140
|
+
| AP-04 | Sem autorização de subscription | usuário ouve eventos cross-tenant | `authorize(user, event)` antes de `subscribe()` |
|
|
141
|
+
| AP-05 | Broadcast sem filtro | `io.emit()` para todos | Rooms/channels por tenant |
|
|
142
|
+
| AP-06 | Estado não recuperado | UI mostra dados stale | Resync via REST ou snapshot WS |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Stack recomendada
|
|
147
|
+
|
|
148
|
+
| Stack | WebSocket | SSE |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| Node | `socket.io`, `ws` | endpoint custom + `EventSource` no client |
|
|
151
|
+
| Rails | Action Cable + Solid Cable | Rack streaming |
|
|
152
|
+
| Rust/Axum | `tokio-tungstenite`, `axum::extract::ws` | `axum::response::sse::Sse` |
|
|
153
|
+
| FastAPI | `fastapi.WebSocket` | `sse-starlette` (`EventSourceResponse`) |
|
|
154
|
+
| Go | `gorilla/websocket`, `melody` | stdlib `http.Flusher` |
|
|
155
|
+
| NestJS | `@nestjs/websockets` (Socket.io ou ws) | endpoint custom |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Exemplo completo — Socket.io (Node)
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Server
|
|
163
|
+
io.on('connection', (socket) => {
|
|
164
|
+
socket.on('subscribe', ({ eventType }) => {
|
|
165
|
+
const entry = EventRegistry[eventType];
|
|
166
|
+
if (!entry) return socket.emit('error', { code: 'UNKNOWN_EVENT' });
|
|
167
|
+
if (!authorize(socket.data.user, entry.requires)) {
|
|
168
|
+
return socket.emit('error', { code: 'FORBIDDEN' });
|
|
169
|
+
}
|
|
170
|
+
subMgr.subscribe(socket.id, eventType);
|
|
171
|
+
socket.join(`event:${eventType}`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
socket.on('disconnect', () => {
|
|
175
|
+
subMgr.onDisconnect(socket.id); // cleanup SEMPRE
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Quando emitir um evento (de qualquer lugar)
|
|
180
|
+
export function emit<E extends keyof typeof EventRegistry>(eventType: E, payload: unknown) {
|
|
181
|
+
const entry = EventRegistry[eventType];
|
|
182
|
+
const valid = entry.schema.safeParse(payload);
|
|
183
|
+
if (!valid.success) { log.error('invalid event payload', valid.error); return; }
|
|
184
|
+
io.to(`event:${eventType}`).emit(eventType, valid.data);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// Client
|
|
190
|
+
const socket = io(url);
|
|
191
|
+
const strategy = new ReconnectStrategy();
|
|
192
|
+
|
|
193
|
+
socket.on('connect', () => {
|
|
194
|
+
strategy.reset();
|
|
195
|
+
resyncState();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
socket.on('disconnect', () => {
|
|
199
|
+
setTimeout(() => socket.connect(), strategy.next());
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
socket.on('message.sent.v1', (payload) => {
|
|
203
|
+
const valid = EventRegistry['message.sent.v1'].schema.safeParse(payload);
|
|
204
|
+
if (!valid.success) return log.warn('invalid event received', valid.error);
|
|
205
|
+
handleMessage(valid.data);
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Aplicação por fase DARE
|
|
212
|
+
|
|
213
|
+
### Design
|
|
214
|
+
- Listar eventos WS/SSE necessários (formato `dominio.acao.vN`)
|
|
215
|
+
- Autorização por evento
|
|
216
|
+
- Estratégia de resync após reconect
|
|
217
|
+
|
|
218
|
+
### Blueprint
|
|
219
|
+
- Diagrama de event flow
|
|
220
|
+
- Stack escolhida (WS lib + cliente)
|
|
221
|
+
- Backoff configurado (base, max, jitter)
|
|
222
|
+
|
|
223
|
+
### Tasks
|
|
224
|
+
- Task: criar `event_registry.ts`
|
|
225
|
+
- Task: implementar `SubscriptionManager`
|
|
226
|
+
- Task: implementar `ReconnectStrategy`
|
|
227
|
+
- Task: configurar autorização por evento
|
|
228
|
+
- Task: instrumentar métrica de ghost listeners
|
|
229
|
+
|
|
230
|
+
### Execute
|
|
231
|
+
- Ralph Loop: grep falha se `socket.on` sem cleanup correspondente
|
|
232
|
+
- Métrica runtime: `subMgr.metrics()` em endpoint `/internal/realtime/stats`
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Boas práticas
|
|
237
|
+
|
|
238
|
+
1. **SSE para server→client unidirecional** — logs, métricas, dashboards
|
|
239
|
+
2. **WebSocket para bidirectional** — chat, colaboração ao vivo
|
|
240
|
+
3. **Heartbeat ping-pong** — detecta conexão zumbi (TCP OK, peer não responde)
|
|
241
|
+
4. **Backpressure** — se client lento, drop eventos antigos para evitar OOM
|
|
242
|
+
5. **Replay limitado** — guardar últimos N eventos por room (não histórico completo)
|
|
243
|
+
6. **Versionar eventos** — `.v1`, `.v2` permite migração gradual
|
|
244
|
+
7. **Logs estruturados** — emitir log por evento com tenant, user, eventType, success/fail
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Checklist final
|
|
249
|
+
|
|
250
|
+
- [ ] `event_registry.ts/py/rs` com todos os event types
|
|
251
|
+
- [ ] Cada evento tem schema JSON validado
|
|
252
|
+
- [ ] Cada evento declara `requires:` (autorização)
|
|
253
|
+
- [ ] `SubscriptionManager` com cleanup on disconnect
|
|
254
|
+
- [ ] `ReconnectStrategy` no client com exponential backoff + jitter
|
|
255
|
+
- [ ] Resync de estado pós-reconect (REST ou snapshot WS)
|
|
256
|
+
- [ ] Validação de payload no server antes de emitir
|
|
257
|
+
- [ ] Validação de payload no client antes de processar
|
|
258
|
+
- [ ] Métrica de ghost listeners em endpoint `/internal/realtime/stats`
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
Skill licenciada MIT — parte do DARE Method v3.
|