timsquad 2.1.0 → 3.4.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.ko.md +288 -0
- package/README.md +170 -763
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +170 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +229 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +235 -14
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/full.js +2 -2
- package/dist/commands/full.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +118 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/knowledge.d.ts +3 -0
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/commands/knowledge.js +316 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/log.d.ts +27 -0
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +965 -0
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/meta-index.d.ts +3 -0
- package/dist/commands/meta-index.d.ts.map +1 -0
- package/dist/commands/meta-index.js +401 -0
- package/dist/commands/meta-index.js.map +1 -0
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +239 -4
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/retro.js +154 -6
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/skills.d.ts +12 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +231 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/upgrade.d.ts +8 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +292 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/workflow.d.ts +3 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +607 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/daemon/context-writer.d.ts +16 -0
- package/dist/daemon/context-writer.d.ts.map +1 -0
- package/dist/daemon/context-writer.js +35 -0
- package/dist/daemon/context-writer.js.map +1 -0
- package/dist/daemon/entry.d.ts +7 -0
- package/dist/daemon/entry.d.ts.map +1 -0
- package/dist/daemon/entry.js +17 -0
- package/dist/daemon/entry.js.map +1 -0
- package/dist/daemon/event-queue.d.ts +52 -0
- package/dist/daemon/event-queue.d.ts.map +1 -0
- package/dist/daemon/event-queue.js +255 -0
- package/dist/daemon/event-queue.js.map +1 -0
- package/dist/daemon/file-watcher.d.ts +19 -0
- package/dist/daemon/file-watcher.d.ts.map +1 -0
- package/dist/daemon/file-watcher.js +87 -0
- package/dist/daemon/file-watcher.js.map +1 -0
- package/dist/daemon/index.d.ts +29 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +296 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/jsonl-watcher.d.ts +49 -0
- package/dist/daemon/jsonl-watcher.d.ts.map +1 -0
- package/dist/daemon/jsonl-watcher.js +258 -0
- package/dist/daemon/jsonl-watcher.js.map +1 -0
- package/dist/daemon/meta-cache.d.ts +63 -0
- package/dist/daemon/meta-cache.d.ts.map +1 -0
- package/dist/daemon/meta-cache.js +249 -0
- package/dist/daemon/meta-cache.js.map +1 -0
- package/dist/daemon/session-state.d.ts +19 -0
- package/dist/daemon/session-state.d.ts.map +1 -0
- package/dist/daemon/session-state.js +132 -0
- package/dist/daemon/session-state.js.map +1 -0
- package/dist/daemon/shutdown.d.ts +21 -0
- package/dist/daemon/shutdown.d.ts.map +1 -0
- package/dist/daemon/shutdown.js +164 -0
- package/dist/daemon/shutdown.js.map +1 -0
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-composer.d.ts +38 -0
- package/dist/lib/agent-composer.d.ts.map +1 -0
- package/dist/lib/agent-composer.js +128 -0
- package/dist/lib/agent-composer.js.map +1 -0
- package/dist/lib/agent-generator.d.ts +22 -0
- package/dist/lib/agent-generator.d.ts.map +1 -0
- package/dist/lib/agent-generator.js +150 -0
- package/dist/lib/agent-generator.js.map +1 -0
- package/dist/lib/ast-parser.d.ts +11 -0
- package/dist/lib/ast-parser.d.ts.map +1 -0
- package/dist/lib/ast-parser.js +282 -0
- package/dist/lib/ast-parser.js.map +1 -0
- package/dist/lib/compile-rules.d.ts +66 -0
- package/dist/lib/compile-rules.d.ts.map +1 -0
- package/dist/lib/compile-rules.js +114 -0
- package/dist/lib/compile-rules.js.map +1 -0
- package/dist/lib/compiler.d.ts +105 -0
- package/dist/lib/compiler.d.ts.map +1 -0
- package/dist/lib/compiler.js +368 -0
- package/dist/lib/compiler.js.map +1 -0
- package/dist/lib/config.d.ts +7 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +34 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/meta-index.d.ts +19 -0
- package/dist/lib/meta-index.d.ts.map +1 -0
- package/dist/lib/meta-index.js +573 -0
- package/dist/lib/meta-index.js.map +1 -0
- package/dist/lib/project.js +1 -1
- package/dist/lib/project.js.map +1 -1
- package/dist/lib/skill-generator.d.ts +32 -0
- package/dist/lib/skill-generator.d.ts.map +1 -0
- package/dist/lib/skill-generator.js +187 -0
- package/dist/lib/skill-generator.js.map +1 -0
- package/dist/lib/template.d.ts +16 -2
- package/dist/lib/template.d.ts.map +1 -1
- package/dist/lib/template.js +115 -20
- package/dist/lib/template.js.map +1 -1
- package/dist/lib/ui-index.d.ts +12 -0
- package/dist/lib/ui-index.d.ts.map +1 -0
- package/dist/lib/ui-index.js +239 -0
- package/dist/lib/ui-index.js.map +1 -0
- package/dist/lib/ui-parser.d.ts +12 -0
- package/dist/lib/ui-parser.d.ts.map +1 -0
- package/dist/lib/ui-parser.js +472 -0
- package/dist/lib/ui-parser.js.map +1 -0
- package/dist/lib/update-check.d.ts +6 -0
- package/dist/lib/update-check.d.ts.map +1 -0
- package/dist/lib/update-check.js +121 -0
- package/dist/lib/update-check.js.map +1 -0
- package/dist/lib/upgrade-backup.d.ts +33 -0
- package/dist/lib/upgrade-backup.d.ts.map +1 -0
- package/dist/lib/upgrade-backup.js +101 -0
- package/dist/lib/upgrade-backup.js.map +1 -0
- package/dist/lib/version.d.ts +19 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +35 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/lib/workflow-state.d.ts +48 -0
- package/dist/lib/workflow-state.d.ts.map +1 -0
- package/dist/lib/workflow-state.js +67 -0
- package/dist/lib/workflow-state.js.map +1 -0
- package/dist/types/config.d.ts +103 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +184 -9
- package/dist/types/config.js.map +1 -1
- package/dist/types/feedback.d.ts +7 -0
- package/dist/types/feedback.d.ts.map +1 -1
- package/dist/types/feedback.js +1 -1
- package/dist/types/feedback.js.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/meta-index.d.ts +146 -0
- package/dist/types/meta-index.d.ts.map +1 -0
- package/dist/types/meta-index.js +7 -0
- package/dist/types/meta-index.js.map +1 -0
- package/dist/types/project.d.ts +19 -3
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +23 -0
- package/dist/types/project.js.map +1 -1
- package/dist/types/task-log.d.ts +208 -0
- package/dist/types/task-log.d.ts.map +1 -0
- package/dist/types/task-log.js +6 -0
- package/dist/types/task-log.js.map +1 -0
- package/dist/types/ui-meta.d.ts +118 -0
- package/dist/types/ui-meta.d.ts.map +1 -0
- package/dist/types/ui-meta.js +7 -0
- package/dist/types/ui-meta.js.map +1 -0
- package/package.json +12 -4
- package/templates/base/agents/base/tsq-architect.md +68 -0
- package/templates/base/agents/base/tsq-dba.md +56 -0
- package/templates/base/agents/base/tsq-designer.md +72 -0
- package/templates/base/agents/base/tsq-developer.md +67 -0
- package/templates/base/agents/base/tsq-qa.md +55 -0
- package/templates/base/agents/base/tsq-security.md +65 -0
- package/templates/base/agents/overlays/domain/general-web/_common.md +11 -0
- package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
- package/templates/base/agents/overlays/platform/claude-code.md +12 -0
- package/templates/base/config.template.yaml +213 -0
- package/templates/base/knowledge/checklists/accessibility.md +37 -0
- package/templates/base/knowledge/checklists/architecture-review.md +28 -0
- package/templates/base/knowledge/checklists/database-standards.md +84 -0
- package/templates/base/knowledge/checklists/design-reference.md +97 -0
- package/templates/base/knowledge/checklists/security.md +50 -0
- package/templates/base/knowledge/checklists/ssot-validation.md +19 -0
- package/templates/base/knowledge/domains/_template.md +16 -0
- package/templates/base/knowledge/platforms/_template.md +16 -0
- package/templates/base/knowledge/templates/sequence-report.md +44 -0
- package/templates/base/knowledge/templates/task-result.md +105 -0
- package/templates/base/skills/_template/SKILL.md +59 -0
- package/templates/base/skills/_template/references/_template.md +35 -0
- package/templates/base/skills/_template/rules/_sections.md +34 -0
- package/templates/base/skills/_template/rules/_template.md +32 -0
- package/templates/base/skills/_template/scripts/_template.sh +31 -0
- package/templates/base/skills/architecture/SKILL.md +54 -0
- package/templates/base/skills/architecture/references/adr-template.md +50 -0
- package/templates/base/skills/architecture/references/api-design.md +64 -0
- package/templates/base/skills/backend/node/SKILL.md +81 -0
- package/templates/base/skills/backend/node/rules/async-patterns.md +81 -0
- package/templates/base/skills/backend/node/rules/deployment.md +33 -0
- package/templates/base/skills/backend/node/rules/env-config.md +41 -0
- package/templates/base/skills/backend/node/rules/error-handling.md +83 -0
- package/templates/base/skills/backend/node/rules/hono-app-setup.md +98 -0
- package/templates/base/skills/backend/node/rules/jwt-auth.md +76 -0
- package/templates/base/skills/backend/node/rules/middleware.md +56 -0
- package/templates/base/skills/backend/node/rules/testing.md +82 -0
- package/templates/base/skills/coding/SKILL.md +47 -0
- package/templates/base/skills/coding/rules/patterns.md +81 -0
- package/templates/base/skills/controller/SKILL.md +111 -0
- package/templates/base/skills/controller/references/README.md +35 -0
- package/templates/base/skills/controller/rules/README.md +18 -0
- package/templates/base/skills/database/SKILL.md +98 -0
- package/templates/base/skills/database/prisma/SKILL.md +57 -0
- package/templates/base/skills/database/prisma/rules/queries.md +133 -0
- package/templates/base/skills/database/prisma/rules/schema-design.md +80 -0
- package/templates/base/skills/frontend/nextjs/SKILL.md +59 -0
- package/templates/base/skills/frontend/nextjs/rules/app-router.md +138 -0
- package/templates/base/skills/frontend/react/SKILL.md +86 -0
- package/templates/base/skills/frontend/react/rules/_sections.md +88 -0
- package/templates/base/skills/frontend/react/rules/anti-patterns.md +67 -0
- package/templates/base/skills/frontend/react/rules/async-api-routes.md +38 -0
- package/templates/base/skills/frontend/react/rules/async-defer-await.md +80 -0
- package/templates/base/skills/frontend/react/rules/async-dependencies.md +36 -0
- package/templates/base/skills/frontend/react/rules/async-parallel.md +28 -0
- package/templates/base/skills/frontend/react/rules/async-suspense-boundaries.md +99 -0
- package/templates/base/skills/frontend/react/rules/bundle-barrel-imports.md +59 -0
- package/templates/base/skills/frontend/react/rules/bundle-defer-third-party.md +49 -0
- package/templates/base/skills/frontend/react/rules/bundle-dynamic-imports.md +35 -0
- package/templates/base/skills/frontend/react/rules/component-conventions.md +74 -0
- package/templates/base/skills/frontend/react/rules/js-combine-iterations.md +32 -0
- package/templates/base/skills/frontend/react/rules/js-early-exit.md +50 -0
- package/templates/base/skills/frontend/react/rules/js-index-maps.md +37 -0
- package/templates/base/skills/frontend/react/rules/js-set-map-lookups.md +24 -0
- package/templates/base/skills/frontend/react/rules/rendering-conditional-render.md +40 -0
- package/templates/base/skills/frontend/react/rules/rendering-content-visibility.md +38 -0
- package/templates/base/skills/frontend/react/rules/rendering-hoist-jsx.md +46 -0
- package/templates/base/skills/frontend/react/rules/rerender-defer-reads.md +39 -0
- package/templates/base/skills/frontend/react/rules/rerender-derived-state.md +29 -0
- package/templates/base/skills/frontend/react/rules/rerender-memo.md +44 -0
- package/templates/base/skills/frontend/react/rules/rerender-transitions.md +40 -0
- package/templates/base/skills/frontend/react/rules/server-after-nonblocking.md +73 -0
- package/templates/base/skills/frontend/react/rules/server-cache-react.md +26 -0
- package/templates/base/skills/frontend/react/rules/server-parallel-fetching.md +79 -0
- package/templates/base/skills/frontend/react/rules/state-location.md +55 -0
- package/templates/base/skills/methodology/bdd/SKILL.md +69 -0
- package/templates/base/skills/methodology/bdd/rules/gherkin-patterns.md +113 -0
- package/templates/base/skills/methodology/ddd/SKILL.md +74 -0
- package/templates/base/skills/methodology/ddd/rules/strategic-patterns.md +98 -0
- package/templates/base/skills/methodology/debugging/SKILL.md +60 -0
- package/templates/base/skills/methodology/debugging/references/root-cause-tracing.md +84 -0
- package/templates/base/skills/methodology/tdd/SKILL.md +66 -0
- package/templates/base/skills/methodology/tdd/rules/real-world-example.md +88 -0
- package/templates/base/skills/methodology/tdd/rules/techniques.md +185 -0
- package/templates/base/skills/mobile/dart/SKILL.md +69 -0
- package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
- package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
- package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
- package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
- package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
- package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
- package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
- package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
- package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
- package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
- package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
- package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
- package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
- package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
- package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
- package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
- package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
- package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
- package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
- package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
- package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
- package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
- package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
- package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
- package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
- package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
- package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
- package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
- package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
- package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
- package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
- package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
- package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
- package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
- package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
- package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
- package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
- package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
- package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
- package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
- package/templates/base/skills/planning/SKILL.md +58 -0
- package/templates/base/skills/planning/references/prd-guide.md +47 -0
- package/templates/base/skills/planning/references/requirements-guide.md +46 -0
- package/templates/base/skills/prompt-engineering/SKILL.md +103 -0
- package/templates/base/skills/retrospective/SKILL.md +102 -0
- package/templates/base/skills/security/SKILL.md +55 -0
- package/templates/base/skills/security/rules/owasp-examples.md +119 -0
- package/templates/base/skills/security/scripts/check-secrets.sh +55 -0
- package/templates/base/skills/testing/SKILL.md +63 -0
- package/templates/base/skills/testing/references/testing-patterns.md +103 -0
- package/templates/base/skills/tsq-protocol/SKILL.md +51 -0
- package/templates/base/skills/typescript/SKILL.md +67 -0
- package/templates/base/skills/typescript/rules/type-patterns.md +135 -0
- package/templates/base/skills/typescript/rules/utility-types.md +76 -0
- package/templates/base/skills/ui-design/SKILL.md +70 -0
- package/templates/{common → base}/timsquad/feedback/routing-rules.yaml +1 -1
- package/templates/{common → base}/timsquad/retrospective/metrics/metrics-schema.json +46 -1
- package/templates/platforms/claude-code/CLAUDE.md.template +89 -0
- package/templates/platforms/claude-code/rules/adr-rules.md +32 -0
- package/templates/platforms/claude-code/rules/feedback-routing.md +18 -0
- package/templates/platforms/claude-code/rules/phase-management.md +23 -0
- package/templates/platforms/claude-code/rules/reporting-format.md +26 -0
- package/templates/platforms/claude-code/rules/sequence-management.md +72 -0
- package/templates/platforms/claude-code/rules/workspace-sync.md +33 -0
- package/templates/platforms/claude-code/scripts/completion-guard.sh +57 -0
- package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
- package/templates/platforms/claude-code/settings.json +98 -0
- package/templates/project-types/api-backend/config.yaml +227 -0
- package/templates/project-types/api-backend/process/workflow.xml +214 -0
- package/templates/project-types/fintech/config.yaml +151 -0
- package/templates/project-types/fintech/process/workflow.xml +316 -0
- package/templates/project-types/infra/config.yaml +327 -0
- package/templates/project-types/infra/process/workflow.xml +296 -0
- package/templates/project-types/mobile-app/config.yaml +123 -0
- package/templates/project-types/mobile-app/process/workflow.xml +191 -0
- package/templates/project-types/platform/config.yaml +254 -0
- package/templates/project-types/platform/process/workflow.xml +254 -0
- package/templates/project-types/web-app/config.yaml +198 -0
- package/templates/project-types/web-app/process/workflow.xml +210 -0
- package/templates/project-types/web-service/config.yaml +136 -0
- package/templates/project-types/web-service/process/workflow.xml +184 -0
- package/templates/common/CLAUDE.md.template +0 -254
- package/templates/common/claude/agents/tsq-dba.md +0 -311
- package/templates/common/claude/agents/tsq-designer.md +0 -323
- package/templates/common/claude/agents/tsq-developer.md +0 -177
- package/templates/common/claude/agents/tsq-planner.md +0 -190
- package/templates/common/claude/agents/tsq-prompter.md +0 -356
- package/templates/common/claude/agents/tsq-qa.md +0 -168
- package/templates/common/claude/agents/tsq-retro.md +0 -193
- package/templates/common/claude/agents/tsq-security.md +0 -221
- package/templates/common/claude/hooks/auto-metrics.sh +0 -165
- package/templates/common/claude/hooks/auto-worklog.sh +0 -245
- package/templates/common/claude/hooks/event-logger.sh +0 -208
- package/templates/common/claude/settings.json +0 -86
- package/templates/common/claude/skills/architecture/SKILL.md +0 -123
- package/templates/common/claude/skills/backend/node/SKILL.md +0 -1015
- package/templates/common/claude/skills/coding/SKILL.md +0 -171
- package/templates/common/claude/skills/database/prisma/SKILL.md +0 -357
- package/templates/common/claude/skills/frontend/nextjs/SKILL.md +0 -279
- package/templates/common/claude/skills/frontend/react/SKILL.md +0 -1729
- package/templates/common/claude/skills/methodology/bdd/SKILL.md +0 -234
- package/templates/common/claude/skills/methodology/ddd/SKILL.md +0 -311
- package/templates/common/claude/skills/methodology/tdd/SKILL.md +0 -512
- package/templates/common/claude/skills/planning/SKILL.md +0 -90
- package/templates/common/claude/skills/security/SKILL.md +0 -234
- package/templates/common/claude/skills/testing/SKILL.md +0 -146
- package/templates/common/claude/skills/typescript/SKILL.md +0 -435
- package/templates/common/config.template.yaml +0 -132
- /package/templates/{common → base}/timsquad/architectures/clean/ARCHITECTURE.md +0 -0
- /package/templates/{common → base}/timsquad/architectures/clean/backend.xml +0 -0
- /package/templates/{common → base}/timsquad/architectures/clean/frontend.xml +0 -0
- /package/templates/{common → base}/timsquad/architectures/fsd/ARCHITECTURE.md +0 -0
- /package/templates/{common → base}/timsquad/architectures/fsd/frontend.xml +0 -0
- /package/templates/{common → base}/timsquad/architectures/hexagonal/ARCHITECTURE.md +0 -0
- /package/templates/{common → base}/timsquad/architectures/hexagonal/backend.xml +0 -0
- /package/templates/{common → base}/timsquad/constraints/competency-framework.xml +0 -0
- /package/templates/{common → base}/timsquad/constraints/ssot-schema.xml +0 -0
- /package/templates/{common → base}/timsquad/feedback/feedback-router.sh +0 -0
- /package/templates/{common → base}/timsquad/generators/data-design.xml +0 -0
- /package/templates/{common → base}/timsquad/generators/prd.xml +0 -0
- /package/templates/{common → base}/timsquad/generators/requirements.xml +0 -0
- /package/templates/{common → base}/timsquad/generators/service-spec.xml +0 -0
- /package/templates/{common → base}/timsquad/logs/_example.md +0 -0
- /package/templates/{common → base}/timsquad/logs/_template.md +0 -0
- /package/templates/{common → base}/timsquad/patterns/cqrs.xml +0 -0
- /package/templates/{common → base}/timsquad/patterns/event-sourcing.xml +0 -0
- /package/templates/{common → base}/timsquad/patterns/repository.xml +0 -0
- /package/templates/{common → base}/timsquad/process/phase-checklist.yaml +0 -0
- /package/templates/{common → base}/timsquad/process/state-machine.xml +0 -0
- /package/templates/{common → base}/timsquad/process/validation-rules.xml +0 -0
- /package/templates/{common → base}/timsquad/process/workflow-base.xml +0 -0
- /package/templates/{common → base}/timsquad/retrospective/cycle-report.template.md +0 -0
- /package/templates/{common → base}/timsquad/retrospective/patterns/failure-patterns.md +0 -0
- /package/templates/{common → base}/timsquad/retrospective/patterns/success-patterns.md +0 -0
- /package/templates/{common → base}/timsquad/retrospective/retrospective-config.xml +0 -0
- /package/templates/{common → base}/timsquad/retrospective/retrospective-state.xml +0 -0
- /package/templates/{common → base}/timsquad/ssot/adr/ADR-000-template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/adr/ADR-001-example.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/data-design.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/deployment-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/env-config.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/error-codes.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/functional-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/glossary.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/integration-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/planning.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/prd.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/requirements.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/security-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/service-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/test-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/ui-ux-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/state/workspace.xml +0 -0
package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Background Processing
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "백그라운드 미처리 → 데이터 미동기화, 사용자 재진입 시 stale 데이터"
|
|
5
|
+
tags: workmanager, background-fetch, isolate, periodic-task
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Background Processing
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (백그라운드 미처리 → 데이터 미동기화, 사용자 재진입 시 stale 데이터)**
|
|
11
|
+
|
|
12
|
+
workmanager 패키지 기반 백그라운드 태스크. 주기적 동기화, 데이터 프리페치, 오프라인 큐 처리.
|
|
13
|
+
|
|
14
|
+
### 의존성
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
dependencies:
|
|
18
|
+
workmanager: ^0.5.2
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 초기화
|
|
22
|
+
|
|
23
|
+
**Incorrect (콜백을 클래스 메서드로 등록):**
|
|
24
|
+
```dart
|
|
25
|
+
class BackgroundService {
|
|
26
|
+
// 클래스 인스턴스 메서드 → top-level 아니므로 실패
|
|
27
|
+
void callbackDispatcher() {
|
|
28
|
+
Workmanager().executeTask((task, inputData) async {
|
|
29
|
+
return true;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (top-level 함수로 콜백 등록):**
|
|
36
|
+
```dart
|
|
37
|
+
// background_tasks.dart — top-level 함수 필수
|
|
38
|
+
@pragma('vm:entry-point')
|
|
39
|
+
void callbackDispatcher() {
|
|
40
|
+
Workmanager().executeTask((taskName, inputData) async {
|
|
41
|
+
// 백그라운드 Isolate에서 실행
|
|
42
|
+
// → 앱 상태, Provider, Navigator 접근 불가
|
|
43
|
+
// → DB, SharedPreferences, HTTP 직접 사용
|
|
44
|
+
|
|
45
|
+
switch (taskName) {
|
|
46
|
+
case BackgroundTasks.syncMatches:
|
|
47
|
+
return await _syncMatches();
|
|
48
|
+
case BackgroundTasks.cleanCache:
|
|
49
|
+
return await _cleanCache();
|
|
50
|
+
case BackgroundTasks.uploadPendingData:
|
|
51
|
+
return await _uploadPendingData(inputData);
|
|
52
|
+
case Workmanager.iOSBackgroundTask:
|
|
53
|
+
// iOS 백그라운드 fetch (시스템이 자동 호출)
|
|
54
|
+
return await _syncMatches();
|
|
55
|
+
default:
|
|
56
|
+
return Future.value(true);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// 태스크 이름 상수
|
|
62
|
+
abstract class BackgroundTasks {
|
|
63
|
+
static const syncMatches = 'sync_matches';
|
|
64
|
+
static const cleanCache = 'clean_cache';
|
|
65
|
+
static const uploadPendingData = 'upload_pending_data';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// main.dart
|
|
69
|
+
Future<void> main() async {
|
|
70
|
+
WidgetsFlutterBinding.ensureInitialized();
|
|
71
|
+
|
|
72
|
+
// Workmanager 초기화
|
|
73
|
+
await Workmanager().initialize(
|
|
74
|
+
callbackDispatcher,
|
|
75
|
+
isInDebugMode: kDebugMode, // 디버그 시 알림으로 태스크 실행 확인
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
runApp(const ProviderScope(child: MyApp()));
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 주기적 태스크 등록
|
|
83
|
+
|
|
84
|
+
```dart
|
|
85
|
+
class BackgroundTaskManager {
|
|
86
|
+
/// 앱 시작 시 또는 로그인 후 등록
|
|
87
|
+
Future<void> registerPeriodicTasks() async {
|
|
88
|
+
// === 매치 데이터 동기화 (15분 간격) ===
|
|
89
|
+
await Workmanager().registerPeriodicTask(
|
|
90
|
+
'sync-matches-periodic', // uniqueName (취소용)
|
|
91
|
+
BackgroundTasks.syncMatches,
|
|
92
|
+
frequency: const Duration(minutes: 15), // 최소 15분 (OS 제어)
|
|
93
|
+
constraints: Constraints(
|
|
94
|
+
networkType: NetworkType.connected, // 네트워크 필요
|
|
95
|
+
requiresBatteryNotLow: true, // 배터리 부족 시 스킵
|
|
96
|
+
),
|
|
97
|
+
existingWorkPolicy: ExistingWorkPolicy.keep, // 기존 등록 유지
|
|
98
|
+
backoffPolicy: BackoffPolicy.exponential,
|
|
99
|
+
initialDelay: const Duration(minutes: 5),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// === 캐시 정리 (1일 간격) ===
|
|
103
|
+
await Workmanager().registerPeriodicTask(
|
|
104
|
+
'clean-cache-daily',
|
|
105
|
+
BackgroundTasks.cleanCache,
|
|
106
|
+
frequency: const Duration(hours: 24),
|
|
107
|
+
constraints: Constraints(
|
|
108
|
+
requiresCharging: true, // 충전 중일 때만
|
|
109
|
+
requiresDeviceIdle: true, // 유휴 상태일 때만 (Android)
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// 1회성 태스크 (즉시 또는 지연 실행)
|
|
115
|
+
Future<void> scheduleOneOffTask({
|
|
116
|
+
required String data,
|
|
117
|
+
}) async {
|
|
118
|
+
await Workmanager().registerOneOffTask(
|
|
119
|
+
'upload-pending-${DateTime.now().millisecondsSinceEpoch}',
|
|
120
|
+
BackgroundTasks.uploadPendingData,
|
|
121
|
+
inputData: {'data': data}, // 태스크에 전달할 데이터
|
|
122
|
+
constraints: Constraints(
|
|
123
|
+
networkType: NetworkType.connected,
|
|
124
|
+
),
|
|
125
|
+
initialDelay: const Duration(seconds: 10),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// 모든 태스크 취소 (로그아웃 시)
|
|
130
|
+
Future<void> cancelAll() async {
|
|
131
|
+
await Workmanager().cancelAll();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// 특정 태스크 취소
|
|
135
|
+
Future<void> cancelTask(String uniqueName) async {
|
|
136
|
+
await Workmanager().cancelByUniqueName(uniqueName);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 백그라운드 태스크 구현
|
|
142
|
+
|
|
143
|
+
```dart
|
|
144
|
+
/// 매치 데이터 동기화 (백그라운드 Isolate에서 실행)
|
|
145
|
+
Future<bool> _syncMatches() async {
|
|
146
|
+
try {
|
|
147
|
+
// 직접 HTTP 호출 (Dio/http 패키지)
|
|
148
|
+
// Provider/Repository 사용 불가 → 직접 구현
|
|
149
|
+
final client = http.Client();
|
|
150
|
+
final prefs = await SharedPreferences.getInstance();
|
|
151
|
+
final token = prefs.getString('auth_token');
|
|
152
|
+
|
|
153
|
+
if (token == null) return true; // 미로그인 → 스킵
|
|
154
|
+
|
|
155
|
+
final response = await client.get(
|
|
156
|
+
Uri.parse('https://api.example.com/matches/sync'),
|
|
157
|
+
headers: {'Authorization': 'Bearer $token'},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (response.statusCode == 200) {
|
|
161
|
+
final data = jsonDecode(response.body) as List;
|
|
162
|
+
// 로컬 DB에 저장 (SQLite/Hive 직접 접근)
|
|
163
|
+
final db = await openDatabase('app.db');
|
|
164
|
+
for (final match in data) {
|
|
165
|
+
await db.insert(
|
|
166
|
+
'matches',
|
|
167
|
+
match as Map<String, dynamic>,
|
|
168
|
+
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
client.close();
|
|
174
|
+
return true; // 성공
|
|
175
|
+
} catch (e) {
|
|
176
|
+
debugPrint('Background sync failed: $e');
|
|
177
|
+
return false; // 실패 → OS가 재시도 스케줄
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// 오프라인 데이터 업로드 (네트워크 복구 시)
|
|
182
|
+
Future<bool> _uploadPendingData(Map<String, dynamic>? inputData) async {
|
|
183
|
+
try {
|
|
184
|
+
final prefs = await SharedPreferences.getInstance();
|
|
185
|
+
final pendingJson = prefs.getString('pending_uploads');
|
|
186
|
+
if (pendingJson == null) return true;
|
|
187
|
+
|
|
188
|
+
final pendingList = jsonDecode(pendingJson) as List;
|
|
189
|
+
final client = http.Client();
|
|
190
|
+
final token = prefs.getString('auth_token');
|
|
191
|
+
|
|
192
|
+
for (final item in pendingList) {
|
|
193
|
+
await client.post(
|
|
194
|
+
Uri.parse('https://api.example.com/data'),
|
|
195
|
+
headers: {
|
|
196
|
+
'Authorization': 'Bearer $token',
|
|
197
|
+
'Content-Type': 'application/json',
|
|
198
|
+
},
|
|
199
|
+
body: jsonEncode(item),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 업로드 완료 → 대기열 비우기
|
|
204
|
+
await prefs.remove('pending_uploads');
|
|
205
|
+
client.close();
|
|
206
|
+
return true;
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// 캐시 정리
|
|
213
|
+
Future<bool> _cleanCache() async {
|
|
214
|
+
try {
|
|
215
|
+
final cacheDir = await getTemporaryDirectory();
|
|
216
|
+
final now = DateTime.now();
|
|
217
|
+
|
|
218
|
+
await for (final entity in cacheDir.list()) {
|
|
219
|
+
if (entity is File) {
|
|
220
|
+
final stat = await entity.stat();
|
|
221
|
+
// 7일 이상 된 캐시 파일 삭제
|
|
222
|
+
if (now.difference(stat.modified).inDays > 7) {
|
|
223
|
+
await entity.delete();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
} catch (e) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### iOS 백그라운드 설정
|
|
235
|
+
|
|
236
|
+
```dart
|
|
237
|
+
// iOS는 BGTaskScheduler 기반
|
|
238
|
+
// Info.plist에 등록 필요:
|
|
239
|
+
// <key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
240
|
+
// <array>
|
|
241
|
+
// <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
242
|
+
// </array>
|
|
243
|
+
//
|
|
244
|
+
// Runner/AppDelegate.swift에서 추가 설정 불필요 (workmanager가 자동 처리)
|
|
245
|
+
//
|
|
246
|
+
// iOS 제약사항:
|
|
247
|
+
// - 주기적 태스크 최소 간격: OS가 결정 (15분보다 길 수 있음)
|
|
248
|
+
// - 실행 시점: OS 최적화에 따라 지연 가능
|
|
249
|
+
// - 배터리/네트워크 상태에 따라 스킵 가능
|
|
250
|
+
// - Background App Refresh가 꺼져 있으면 미실행
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Isolate 직접 사용 (무거운 연산)
|
|
254
|
+
|
|
255
|
+
```dart
|
|
256
|
+
/// 이미지 처리, 대량 데이터 파싱 등 CPU 집약적 태스크
|
|
257
|
+
Future<List<MatchSummary>> processMatchDataInBackground(
|
|
258
|
+
String rawJson) async {
|
|
259
|
+
// compute() → 별도 Isolate에서 실행 (UI 스레드 블로킹 방지)
|
|
260
|
+
return await compute(_parseMatchData, rawJson);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// top-level 또는 static 함수만 가능
|
|
264
|
+
List<MatchSummary> _parseMatchData(String rawJson) {
|
|
265
|
+
final data = jsonDecode(rawJson) as List;
|
|
266
|
+
return data.map((e) => MatchSummary.fromJson(e)).toList();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// 장기 실행 Isolate (양방향 통신)
|
|
270
|
+
Future<void> startLongRunningIsolate() async {
|
|
271
|
+
final receivePort = ReceivePort();
|
|
272
|
+
await Isolate.spawn(_isolateEntryPoint, receivePort.sendPort);
|
|
273
|
+
|
|
274
|
+
receivePort.listen((message) {
|
|
275
|
+
if (message is SendPort) {
|
|
276
|
+
// 메인 → Isolate 통신용 SendPort
|
|
277
|
+
message.send({'action': 'process', 'data': '...'});
|
|
278
|
+
} else {
|
|
279
|
+
// Isolate → 메인 결과 수신
|
|
280
|
+
debugPrint('Isolate result: $message');
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
void _isolateEntryPoint(SendPort mainSendPort) {
|
|
286
|
+
final receivePort = ReceivePort();
|
|
287
|
+
mainSendPort.send(receivePort.sendPort);
|
|
288
|
+
|
|
289
|
+
receivePort.listen((message) {
|
|
290
|
+
// 메인에서 받은 데이터 처리
|
|
291
|
+
final result = _heavyComputation(message);
|
|
292
|
+
mainSendPort.send(result);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 규칙
|
|
298
|
+
|
|
299
|
+
- `callbackDispatcher` → top-level 함수, `@pragma('vm:entry-point')` 필수
|
|
300
|
+
- 백그라운드 태스크 → Provider/Navigator/BuildContext 접근 불가
|
|
301
|
+
- HTTP/DB → 직접 인스턴스 생성 (앱 상태와 독립)
|
|
302
|
+
- 주기적 태스크 최소 간격 → 15분 (OS가 실제 실행 시점 결정)
|
|
303
|
+
- `constraints` → 네트워크/배터리/충전 조건 설정 (배터리 소모 방지)
|
|
304
|
+
- `existingWorkPolicy.keep` → 중복 등록 방지 (앱 재시작 시)
|
|
305
|
+
- 태스크 반환값 → `true` (성공), `false` (실패 → OS 재시도)
|
|
306
|
+
- 로그아웃 시 → `cancelAll()` 로 모든 백그라운드 태스크 해제
|
|
307
|
+
- CPU 집약적 작업 → `compute()` 또는 `Isolate.spawn` (UI 스레드 보호)
|
|
308
|
+
- iOS → Background App Refresh 설정에 의존, 실행 보장 없음
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deep Linking from Notifications
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "탭 후 홈으로만 이동 → 전환율 30% 하락, 딥링크 → 전환율 2-3x"
|
|
5
|
+
tags: deep-link, go-router, payload, navigation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Deep Linking from Notifications
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (탭 후 홈으로만 이동 → 전환율 30% 하락, 딥링크 → 전환율 2-3x)**
|
|
11
|
+
|
|
12
|
+
알림 탭 → 특정 화면 네비게이션. FCM 페이로드 → go_router 딥링크 통합.
|
|
13
|
+
|
|
14
|
+
### 페이로드 설계
|
|
15
|
+
|
|
16
|
+
**Incorrect (구조 없는 페이로드):**
|
|
17
|
+
```dart
|
|
18
|
+
// 서버에서 보내는 데이터
|
|
19
|
+
// { "data": { "screen": "match_detail_123" } }
|
|
20
|
+
// → 파싱 로직이 복잡해지고, 새 화면 추가 시 분기문 증가
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (타입 + ID 분리, 라우트 매핑):**
|
|
24
|
+
```dart
|
|
25
|
+
// 서버에서 보내는 데이터 구조
|
|
26
|
+
// {
|
|
27
|
+
// "data": {
|
|
28
|
+
// "type": "match", // 알림 카테고리
|
|
29
|
+
// "id": "match_123", // 리소스 ID
|
|
30
|
+
// "action": "invite", // 세부 액션 (선택)
|
|
31
|
+
// "route": "/match/match_123" // 직접 라우트 (선택, 백업)
|
|
32
|
+
// }
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
/// 페이로드 모델
|
|
36
|
+
class NotificationPayload {
|
|
37
|
+
final String type;
|
|
38
|
+
final String? id;
|
|
39
|
+
final String? action;
|
|
40
|
+
final String? route;
|
|
41
|
+
|
|
42
|
+
const NotificationPayload({
|
|
43
|
+
required this.type,
|
|
44
|
+
this.id,
|
|
45
|
+
this.action,
|
|
46
|
+
this.route,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
factory NotificationPayload.fromMap(Map<String, dynamic> data) {
|
|
50
|
+
return NotificationPayload(
|
|
51
|
+
type: data['type'] as String? ?? 'default',
|
|
52
|
+
id: data['id'] as String?,
|
|
53
|
+
action: data['action'] as String?,
|
|
54
|
+
route: data['route'] as String?,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// 페이로드 → go_router 경로 변환
|
|
59
|
+
String toRoute() {
|
|
60
|
+
// 직접 라우트가 있으면 우선 사용
|
|
61
|
+
if (route != null) return route!;
|
|
62
|
+
|
|
63
|
+
// 타입 + ID 기반 라우트 생성
|
|
64
|
+
return switch (type) {
|
|
65
|
+
'match' => id != null ? '/match/$id' : '/matches',
|
|
66
|
+
'chat' => id != null ? '/chat/$id' : '/chat',
|
|
67
|
+
'profile' => id != null ? '/profile/$id' : '/profile',
|
|
68
|
+
'announcement' => '/announcements',
|
|
69
|
+
'reminder' => id != null ? '/match/$id' : '/matches',
|
|
70
|
+
_ => '/notifications',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### go_router 통합
|
|
77
|
+
|
|
78
|
+
```dart
|
|
79
|
+
/// 알림 딥링크 네비게이션 서비스
|
|
80
|
+
class NotificationNavigator {
|
|
81
|
+
final GoRouter _router;
|
|
82
|
+
|
|
83
|
+
NotificationNavigator({required GoRouter router}) : _router = router;
|
|
84
|
+
|
|
85
|
+
/// FCM RemoteMessage에서 네비게이션
|
|
86
|
+
void navigateFromMessage(RemoteMessage message) {
|
|
87
|
+
final payload = NotificationPayload.fromMap(message.data);
|
|
88
|
+
_navigate(payload);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// flutter_local_notifications 페이로드에서 네비게이션
|
|
92
|
+
void navigateFromLocalPayload(String? payloadJson) {
|
|
93
|
+
if (payloadJson == null) return;
|
|
94
|
+
try {
|
|
95
|
+
final data = jsonDecode(payloadJson) as Map<String, dynamic>;
|
|
96
|
+
final payload = NotificationPayload.fromMap(data);
|
|
97
|
+
_navigate(payload);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
debugPrint('Invalid notification payload: $e');
|
|
100
|
+
_router.go('/notifications');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void _navigate(NotificationPayload payload) {
|
|
105
|
+
final route = payload.toRoute();
|
|
106
|
+
|
|
107
|
+
// push vs go 결정:
|
|
108
|
+
// - go: 스택을 교체 (뒤로가기 시 홈으로)
|
|
109
|
+
// - push: 스택에 추가 (뒤로가기 시 이전 화면으로)
|
|
110
|
+
// 알림에서는 push가 일반적 (사용자가 돌아갈 수 있도록)
|
|
111
|
+
_router.push(route);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Riverpod Provider
|
|
116
|
+
final notificationNavigatorProvider = Provider<NotificationNavigator>((ref) {
|
|
117
|
+
return NotificationNavigator(router: ref.watch(routerProvider));
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3가지 앱 상태에서 딥링크 처리
|
|
122
|
+
|
|
123
|
+
```dart
|
|
124
|
+
class NotificationDeepLinkHandler {
|
|
125
|
+
final NotificationNavigator _navigator;
|
|
126
|
+
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
|
127
|
+
final FlutterLocalNotificationsPlugin _localPlugin;
|
|
128
|
+
|
|
129
|
+
NotificationDeepLinkHandler({
|
|
130
|
+
required NotificationNavigator navigator,
|
|
131
|
+
required FlutterLocalNotificationsPlugin localPlugin,
|
|
132
|
+
}) : _navigator = navigator,
|
|
133
|
+
_localPlugin = localPlugin;
|
|
134
|
+
|
|
135
|
+
Future<void> initialize() async {
|
|
136
|
+
// === 1. 종료 상태 (Terminated) → 알림 탭으로 앱 시작 ===
|
|
137
|
+
final initialMessage = await _messaging.getInitialMessage();
|
|
138
|
+
if (initialMessage != null) {
|
|
139
|
+
// 라우터 초기화 대기
|
|
140
|
+
await Future.delayed(const Duration(milliseconds: 800));
|
|
141
|
+
_navigator.navigateFromMessage(initialMessage);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// === 2. 백그라운드 → 알림 탭으로 앱 복귀 ===
|
|
145
|
+
FirebaseMessaging.onMessageOpenedApp.listen((message) {
|
|
146
|
+
_navigator.navigateFromMessage(message);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// === 3. 로컬 알림 탭 처리 ===
|
|
150
|
+
// flutter_local_notifications의 앱 시작 시 탭 확인
|
|
151
|
+
final launchDetails =
|
|
152
|
+
await _localPlugin.getNotificationAppLaunchDetails();
|
|
153
|
+
if (launchDetails?.didNotificationLaunchApp == true) {
|
|
154
|
+
final response = launchDetails!.notificationResponse;
|
|
155
|
+
if (response != null) {
|
|
156
|
+
await Future.delayed(const Duration(milliseconds: 800));
|
|
157
|
+
_navigator.navigateFromLocalPayload(response.payload);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 인증 가드와 딥링크 조합
|
|
165
|
+
|
|
166
|
+
```dart
|
|
167
|
+
// go_router redirect에서 딥링크 보존
|
|
168
|
+
GoRouter(
|
|
169
|
+
redirect: (context, state) {
|
|
170
|
+
final isLoggedIn = /* auth check */;
|
|
171
|
+
final isAuthRoute = state.matchedLocation.startsWith('/auth');
|
|
172
|
+
|
|
173
|
+
if (!isLoggedIn && !isAuthRoute) {
|
|
174
|
+
// 딥링크 목적지를 쿼리 파라미터로 보존
|
|
175
|
+
final redirectTo = state.matchedLocation;
|
|
176
|
+
return '/auth/login?redirect=${Uri.encodeComponent(redirectTo)}';
|
|
177
|
+
}
|
|
178
|
+
if (isLoggedIn && isAuthRoute) {
|
|
179
|
+
// 로그인 완료 → 원래 딥링크 목적지로 이동
|
|
180
|
+
final redirect = state.uri.queryParameters['redirect'];
|
|
181
|
+
return redirect != null ? Uri.decodeComponent(redirect) : '/';
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
},
|
|
185
|
+
// ...routes
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 알림 분석 (Analytics)
|
|
190
|
+
|
|
191
|
+
```dart
|
|
192
|
+
/// 알림 탭 이벤트 추적
|
|
193
|
+
void _navigate(NotificationPayload payload) {
|
|
194
|
+
// 분석 이벤트 기록
|
|
195
|
+
AnalyticsService.instance.logEvent(
|
|
196
|
+
'notification_opened',
|
|
197
|
+
parameters: {
|
|
198
|
+
'type': payload.type,
|
|
199
|
+
'action': payload.action ?? 'tap',
|
|
200
|
+
'source': 'push',
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
final route = payload.toRoute();
|
|
205
|
+
_router.push(route);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 규칙
|
|
210
|
+
|
|
211
|
+
- 페이로드 구조 → `type` + `id` + `action` 표준화, 서버와 합의
|
|
212
|
+
- `toRoute()` → 중앙 라우트 매핑 (분산 switch 문 방지)
|
|
213
|
+
- 3가지 상태 모두 처리: `getInitialMessage` + `onMessageOpenedApp` + 로컬 알림 launch
|
|
214
|
+
- `getInitialMessage()` → cold start 시 라우터 초기화 대기 후 네비게이션
|
|
215
|
+
- 인증 가드 → 딥링크 목적지 보존 (`?redirect=` 쿼리 파라미터)
|
|
216
|
+
- 알림 탭 시 `push` 사용 (스택 추가 → 뒤로가기 가능)
|
|
217
|
+
- 알림 탭 분석 이벤트 필수 (열림률, 전환율 측정)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: FCM Setup & Token Management
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: "알림 미수신 → 사용자 이탈, 토큰 미갱신 → 전달률 하락"
|
|
5
|
+
tags: fcm, firebase, token, setup
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## FCM Setup & Token Management
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (알림 미수신 → 사용자 이탈, 토큰 미갱신 → 전달률 하락)**
|
|
11
|
+
|
|
12
|
+
FCM 초기화, 토큰 관리, 서버 동기화. 앱 시작 시 반드시 설정해야 하는 핵심 인프라.
|
|
13
|
+
|
|
14
|
+
### 의존성
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
# pubspec.yaml
|
|
18
|
+
dependencies:
|
|
19
|
+
firebase_core: ^3.8.0
|
|
20
|
+
firebase_messaging: ^15.1.0
|
|
21
|
+
flutter_local_notifications: ^18.0.0
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 초기화
|
|
25
|
+
|
|
26
|
+
**Incorrect (순서 무시, 백그라운드 핸들러 미등록):**
|
|
27
|
+
```dart
|
|
28
|
+
void main() {
|
|
29
|
+
runApp(const MyApp());
|
|
30
|
+
// Firebase 초기화 없이 FCM 사용 → 크래시
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Correct (완전한 초기화 순서):**
|
|
35
|
+
```dart
|
|
36
|
+
// top-level 함수 — 클래스 멤버/클로저 불가
|
|
37
|
+
@pragma('vm:entry-point')
|
|
38
|
+
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
39
|
+
await Firebase.initializeApp();
|
|
40
|
+
// 백그라운드 메시지 처리 (DB 저장, 로컬 알림 등)
|
|
41
|
+
// 주의: 여기서 UI 코드, Navigator, BuildContext 접근 불가
|
|
42
|
+
debugPrint('Background message: ${message.messageId}');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Future<void> main() async {
|
|
46
|
+
WidgetsFlutterBinding.ensureInitialized();
|
|
47
|
+
|
|
48
|
+
// 1. Firebase 초기화
|
|
49
|
+
await Firebase.initializeApp();
|
|
50
|
+
|
|
51
|
+
// 2. 백그라운드 메시지 핸들러 등록 (main 에서 1회)
|
|
52
|
+
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
|
53
|
+
|
|
54
|
+
// 3. 로컬 알림 초기화 (포그라운드 표시용)
|
|
55
|
+
await NotificationService.instance.initialize();
|
|
56
|
+
|
|
57
|
+
runApp(const ProviderScope(child: MyApp()));
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### FCM 토큰 관리
|
|
62
|
+
|
|
63
|
+
**Incorrect (토큰 갱신 미처리):**
|
|
64
|
+
```dart
|
|
65
|
+
// 앱 시작 시 토큰 1번만 가져오고 끝
|
|
66
|
+
final token = await FirebaseMessaging.instance.getToken();
|
|
67
|
+
await sendTokenToServer(token!);
|
|
68
|
+
// → 토큰 갱신되면 서버에 잘못된 토큰 → 알림 전달 실패
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Correct (토큰 갱신 구독 + 서버 동기화):**
|
|
72
|
+
```dart
|
|
73
|
+
class FcmTokenManager {
|
|
74
|
+
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
|
75
|
+
final AuthRepository _authRepo;
|
|
76
|
+
|
|
77
|
+
FcmTokenManager({required AuthRepository authRepo}) : _authRepo = authRepo;
|
|
78
|
+
|
|
79
|
+
/// 초기 토큰 등록 + 갱신 구독
|
|
80
|
+
Future<void> initialize() async {
|
|
81
|
+
// 초기 토큰
|
|
82
|
+
final token = await _messaging.getToken();
|
|
83
|
+
if (token != null) {
|
|
84
|
+
await _syncTokenToServer(token);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 토큰 갱신 구독 (앱 재설치, OS 토큰 리프레시 등)
|
|
88
|
+
_messaging.onTokenRefresh.listen(_syncTokenToServer);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Future<void> _syncTokenToServer(String token) async {
|
|
92
|
+
try {
|
|
93
|
+
await _authRepo.updateFcmToken(token);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// 실패 시 로컬 저장 → 다음 앱 시작 시 재시도
|
|
96
|
+
await _saveTokenLocally(token);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// 로그아웃 시 토큰 해제
|
|
101
|
+
Future<void> deleteToken() async {
|
|
102
|
+
await _messaging.deleteToken();
|
|
103
|
+
await _authRepo.removeFcmToken();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Future<void> _saveTokenLocally(String token) async {
|
|
107
|
+
// SharedPreferences 등에 저장, 다음 시작 시 재동기화
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Riverpod Provider
|
|
112
|
+
final fcmTokenManagerProvider = Provider<FcmTokenManager>((ref) {
|
|
113
|
+
return FcmTokenManager(authRepo: ref.watch(authRepositoryProvider));
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### iOS APNs 토큰 (선택)
|
|
118
|
+
|
|
119
|
+
```dart
|
|
120
|
+
// iOS에서 APNs 토큰 직접 필요한 경우 (서버에서 APNs 직접 발송 시)
|
|
121
|
+
final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
|
122
|
+
// 대부분의 경우 FCM이 APNs 토큰을 자동 관리하므로 직접 사용 불필요
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 토픽 구독
|
|
126
|
+
|
|
127
|
+
```dart
|
|
128
|
+
class TopicManager {
|
|
129
|
+
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
|
130
|
+
|
|
131
|
+
/// 사용자 관심사 기반 토픽 구독
|
|
132
|
+
Future<void> subscribeToUserTopics(UserPreferences prefs) async {
|
|
133
|
+
// 전체 공지
|
|
134
|
+
await _messaging.subscribeToTopic('announcements');
|
|
135
|
+
|
|
136
|
+
// 관심 스포츠
|
|
137
|
+
for (final sport in prefs.favoriteSports) {
|
|
138
|
+
await _messaging.subscribeToTopic('sport_${sport.name}');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 지역
|
|
142
|
+
if (prefs.region != null) {
|
|
143
|
+
await _messaging.subscribeToTopic('region_${prefs.region}');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// 로그아웃 시 토픽 해제
|
|
148
|
+
Future<void> unsubscribeAll(UserPreferences prefs) async {
|
|
149
|
+
await _messaging.unsubscribeFromTopic('announcements');
|
|
150
|
+
for (final sport in prefs.favoriteSports) {
|
|
151
|
+
await _messaging.unsubscribeFromTopic('sport_${sport.name}');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 규칙
|
|
158
|
+
|
|
159
|
+
- `Firebase.initializeApp()` → `WidgetsFlutterBinding.ensureInitialized()` 직후
|
|
160
|
+
- `onBackgroundMessage` → `main()` 에서 등록, top-level 함수만 가능
|
|
161
|
+
- `@pragma('vm:entry-point')` → 백그라운드 핸들러에 필수 (트리쉐이킹 방지)
|
|
162
|
+
- FCM 토큰 → 서버 동기화 필수 + `onTokenRefresh` 구독
|
|
163
|
+
- 로그아웃 시 `deleteToken()` + 서버에서 토큰 제거
|
|
164
|
+
- 토픽 구독 → 사용자 설정 변경 시 구독/해제 동기화
|