[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6NVV0ZPC40PSASK9FXCKPWZ":3,"skill-same:01M6NVV0ZPC40PSASK9FXCKPWZ":64},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":37,"linkOnly":38,"markdownContent":39,"markdownBody":40,"frontMatter":41,"refName":42,"commitSha":43,"skillDirectory":44,"forksCount":14,"repositoryHtmlUrl":45,"repositoryHomepageUrl":6,"ownerHtmlUrl":46,"ownerBlogUrl":47,"ownerDescription":48,"updatedAt":49,"install":50,"resources":63},"01M6NVV0ZPC40PSASK9FXCKPWZ","oauth-patterns",null,"OIDC flows, PKCE implementation, token refresh strategies, social login integration, and secure session management.","rubicanjr\u002FFinCognis","rubicanjr","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F234912519?v=4","01M6NT156NND8J474G34SB3923","Rubi","01M6NT15B70RWXE1N3YV0BZY8A",0,"MIT","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Foauth-patterns\u002Fskill.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"patterns","oauth patterns","OAuth","oidc","pkce","refresh","management","session","implementation","secure","social","strategies","token","session management","flows","token refresh","login","integration","2026-11-10 10:12:30",false,"---\nname: oauth-patterns\ndescription: OIDC flows, PKCE implementation, token refresh strategies, social login integration, and secure session management.\n---\n\n# OAuth Patterns\n\nSecure authentication and authorization patterns with OAuth 2.0 and OpenID Connect.\n\n## Authorization Code Flow with PKCE\n\n```typescript\n\u002F\u002F PKCE (Proof Key for Code Exchange): required for public clients (SPA, mobile)\nimport crypto from 'crypto'\n\n\u002F\u002F Step 1: Generate PKCE verifier and challenge\nfunction generatePKCE(): { verifier: string; challenge: string } {\n  const verifier = crypto.randomBytes(32).toString('base64url')\n  const challenge = crypto\n    .createHash('sha256')\n    .update(verifier)\n    .digest('base64url')\n  return { verifier, challenge }\n}\n\n\u002F\u002F Step 2: Build authorization URL\nfunction getAuthorizationUrl(config: OAuthConfig): { url: string; state: string; pkce: PKCE } {\n  const state = crypto.randomBytes(16).toString('hex')\n  const pkce = generatePKCE()\n\n  const params = new URLSearchParams({\n    response_type: 'code',\n    client_id: config.clientId,\n    redirect_uri: config.redirectUri,\n    scope: 'openid profile email',\n    state,\n    code_challenge: pkce.challenge,\n    code_challenge_method: 'S256',\n    prompt: 'consent',            \u002F\u002F Force consent screen\n    nonce: crypto.randomUUID(),   \u002F\u002F Replay protection\n  })\n\n  return {\n    url: `${config.authorizationEndpoint}?${params}`,\n    state,\n    pkce,\n  }\n}\n\n\u002F\u002F Step 3: Exchange code for tokens\nasync function exchangeCodeForTokens(\n  code: string,\n  verifier: string,\n  config: OAuthConfig\n): Promise\u003CTokenSet> {\n  const response = await fetch(config.tokenEndpoint, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fx-www-form-urlencoded' },\n    body: new URLSearchParams({\n      grant_type: 'authorization_code',\n      code,\n      redirect_uri: config.redirectUri,\n      client_id: config.clientId,\n      code_verifier: verifier,\n    }),\n  })\n\n  if (!response.ok) {\n    const error = await response.json()\n    throw new Error(`Token exchange failed: ${error.error_description}`)\n  }\n\n  return response.json() as Promise\u003CTokenSet>\n}\n```\n\n## Token Refresh Strategy\n\n```typescript\ninterface TokenSet {\n  access_token: string\n  refresh_token: string\n  id_token: string\n  expires_in: number        \u002F\u002F seconds\n  token_type: 'Bearer'\n}\n\nclass TokenManager {\n  private refreshTimer: NodeJS.Timeout | null = null\n\n  async setTokens(tokens: TokenSet): Promise\u003Cvoid> {\n    \u002F\u002F Store tokens securely (httpOnly cookies or encrypted storage)\n    await secureStore.set('access_token', tokens.access_token)\n    await secureStore.set('refresh_token', tokens.refresh_token)\n\n    \u002F\u002F Schedule refresh before expiry (refresh at 75% of lifetime)\n    const refreshIn = tokens.expires_in * 0.75 * 1000\n    this.scheduleRefresh(refreshIn)\n  }\n\n  private scheduleRefresh(delayMs: number): void {\n    if (this.refreshTimer) clearTimeout(this.refreshTimer)\n    this.refreshTimer = setTimeout(() => this.refresh(), delayMs)\n  }\n\n  private async refresh(): Promise\u003Cvoid> {\n    const refreshToken = await secureStore.get('refresh_token')\n    if (!refreshToken) {\n      this.emit('session_expired')\n      return\n    }\n\n    try {\n      const response = await fetch(config.tokenEndpoint, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\u002Fx-www-form-urlencoded' },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token: refreshToken,\n          client_id: config.clientId,\n        }),\n      })\n\n      if (!response.ok) throw new Error('Refresh failed')\n\n      const tokens = await response.json() as TokenSet\n      await this.setTokens(tokens)\n    } catch (err) {\n      \u002F\u002F Refresh token expired or revoked\n      await this.clearTokens()\n      this.emit('session_expired')\n    }\n  }\n\n  async clearTokens(): Promise\u003Cvoid> {\n    if (this.refreshTimer) clearTimeout(this.refreshTimer)\n    await secureStore.delete('access_token')\n    await secureStore.delete('refresh_token')\n  }\n}\n```\n\n## Social Login Integration\n\n```typescript\n\u002F\u002F Provider-specific configurations\nconst OAUTH_PROVIDERS: Record\u003Cstring, OAuthConfig> = {\n  google: {\n    clientId: process.env.GOOGLE_CLIENT_ID!,\n    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n    authorizationEndpoint: 'https:\u002F\u002Faccounts.google.com\u002Fo\u002Foauth2\u002Fv2\u002Fauth',\n    tokenEndpoint: 'https:\u002F\u002Foauth2.googleapis.com\u002Ftoken',\n    userInfoEndpoint: 'https:\u002F\u002Fwww.googleapis.com\u002Foauth2\u002Fv3\u002Fuserinfo',\n    scopes: ['openid', 'profile', 'email'],\n  },\n  github: {\n    clientId: process.env.GITHUB_CLIENT_ID!,\n    clientSecret: process.env.GITHUB_CLIENT_SECRET!,\n    authorizationEndpoint: 'https:\u002F\u002Fgithub.com\u002Flogin\u002Foauth\u002Fauthorize',\n    tokenEndpoint: 'https:\u002F\u002Fgithub.com\u002Flogin\u002Foauth\u002Faccess_token',\n    userInfoEndpoint: 'https:\u002F\u002Fapi.github.com\u002Fuser',\n    scopes: ['user:email'],\n  },\n}\n\n\u002F\u002F Callback handler: link social account to internal user\nasync function handleOAuthCallback(\n  provider: string,\n  code: string,\n  state: string\n): Promise\u003C{ user: User; session: Session }> {\n  \u002F\u002F Verify state matches stored state (CSRF protection)\n  const storedState = await sessionStore.get(`oauth_state_${state}`)\n  if (!storedState) throw new Error('Invalid state parameter')\n\n  const config = OAUTH_PROVIDERS[provider]\n  if (!config) throw new Error(`Unknown provider: ${provider}`)\n\n  \u002F\u002F Exchange code for tokens\n  const tokens = await exchangeCodeForTokens(code, storedState.verifier, config)\n\n  \u002F\u002F Fetch user profile from provider\n  const profile = await fetchUserProfile(config.userInfoEndpoint, tokens.access_token)\n\n  \u002F\u002F Find or create user (link social identity)\n  let user = await db.user.findFirst({\n    where: {\n      socialAccounts: {\n        some: { provider, providerUserId: profile.sub }\n      }\n    }\n  })\n\n  if (!user) {\n    user = await db.user.create({\n      data: {\n        email: profile.email,\n        displayName: profile.name,\n        socialAccounts: {\n          create: {\n            provider,\n            providerUserId: profile.sub,\n            email: profile.email,\n          }\n        }\n      }\n    })\n  }\n\n  const session = await createSession(user.id)\n  return { user, session }\n}\n```\n\n## Session Management\n\n```typescript\nimport { SignJWT, jwtVerify } from 'jose'\n\nconst SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET!)\n\nasync function createSession(userId: string): Promise\u003Cstring> {\n  const sessionId = crypto.randomUUID()\n\n  \u002F\u002F Store session server-side (not just JWT)\n  await db.session.create({\n    data: {\n      id: sessionId,\n      userId,\n      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),  \u002F\u002F 24h\n      createdAt: new Date(),\n    }\n  })\n\n  \u002F\u002F Issue signed session token\n  const token = await new SignJWT({ sub: userId, sid: sessionId })\n    .setProtectedHeader({ alg: 'HS256' })\n    .setIssuedAt()\n    .setExpirationTime('24h')\n    .sign(SESSION_SECRET)\n\n  return token\n}\n\n\u002F\u002F Set session cookie (httpOnly, secure, sameSite)\nfunction setSessionCookie(res: Response, token: string): void {\n  res.cookie('session', token, {\n    httpOnly: true,       \u002F\u002F Not accessible via JavaScript\n    secure: true,         \u002F\u002F HTTPS only\n    sameSite: 'lax',      \u002F\u002F CSRF protection\n    maxAge: 24 * 60 * 60 * 1000,\n    path: '\u002F',\n    domain: '.example.com',\n  })\n}\n\n\u002F\u002F Validate session middleware\nasync function validateSession(req: Request, res: Response, next: NextFunction) {\n  const token = req.cookies.session\n  if (!token) return res.status(401).json({ error: 'No session' })\n\n  try {\n    const { payload } = await jwtVerify(token, SESSION_SECRET)\n    const session = await db.session.findUnique({ where: { id: payload.sid as string } })\n\n    if (!session || session.expiresAt \u003C new Date()) {\n      return res.status(401).json({ error: 'Session expired' })\n    }\n\n    req.user = { id: payload.sub as string, sessionId: session.id }\n    next()\n  } catch {\n    return res.status(401).json({ error: 'Invalid session' })\n  }\n}\n```\n\n## Checklist\n\n- [ ] PKCE enabled for all public clients (SPA, mobile, CLI)\n- [ ] State parameter validated on callback (CSRF protection)\n- [ ] Tokens stored in httpOnly secure cookies (not localStorage)\n- [ ] Refresh tokens rotated on each use (detect token theft)\n- [ ] Session validated server-side (not just JWT expiry)\n- [ ] Social login links to internal user account (not provider-dependent)\n- [ ] Nonce in OIDC requests for replay protection\n- [ ] Logout: revoke tokens + clear session + clear cookies\n\n## Anti-Patterns\n\n- Storing tokens in localStorage (XSS exposes all tokens)\n- Implicit grant flow (deprecated, tokens in URL fragment)\n- Not validating state parameter: vulnerable to CSRF\n- Long-lived access tokens without refresh mechanism\n- Trusting JWT without server-side session validation (no revocation)\n- Hardcoding client secrets in frontend code (use backend proxy)\n","# OAuth Patterns\n\nSecure authentication and authorization patterns with OAuth 2.0 and OpenID Connect.\n\n## Authorization Code Flow with PKCE\n\n```typescript\n\u002F\u002F PKCE (Proof Key for Code Exchange): required for public clients (SPA, mobile)\nimport crypto from 'crypto'\n\n\u002F\u002F Step 1: Generate PKCE verifier and challenge\nfunction generatePKCE(): { verifier: string; challenge: string } {\n  const verifier = crypto.randomBytes(32).toString('base64url')\n  const challenge = crypto\n    .createHash('sha256')\n    .update(verifier)\n    .digest('base64url')\n  return { verifier, challenge }\n}\n\n\u002F\u002F Step 2: Build authorization URL\nfunction getAuthorizationUrl(config: OAuthConfig): { url: string; state: string; pkce: PKCE } {\n  const state = crypto.randomBytes(16).toString('hex')\n  const pkce = generatePKCE()\n\n  const params = new URLSearchParams({\n    response_type: 'code',\n    client_id: config.clientId,\n    redirect_uri: config.redirectUri,\n    scope: 'openid profile email',\n    state,\n    code_challenge: pkce.challenge,\n    code_challenge_method: 'S256',\n    prompt: 'consent',            \u002F\u002F Force consent screen\n    nonce: crypto.randomUUID(),   \u002F\u002F Replay protection\n  })\n\n  return {\n    url: `${config.authorizationEndpoint}?${params}`,\n    state,\n    pkce,\n  }\n}\n\n\u002F\u002F Step 3: Exchange code for tokens\nasync function exchangeCodeForTokens(\n  code: string,\n  verifier: string,\n  config: OAuthConfig\n): Promise\u003CTokenSet> {\n  const response = await fetch(config.tokenEndpoint, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fx-www-form-urlencoded' },\n    body: new URLSearchParams({\n      grant_type: 'authorization_code',\n      code,\n      redirect_uri: config.redirectUri,\n      client_id: config.clientId,\n      code_verifier: verifier,\n    }),\n  })\n\n  if (!response.ok) {\n    const error = await response.json()\n    throw new Error(`Token exchange failed: ${error.error_description}`)\n  }\n\n  return response.json() as Promise\u003CTokenSet>\n}\n```\n\n## Token Refresh Strategy\n\n```typescript\ninterface TokenSet {\n  access_token: string\n  refresh_token: string\n  id_token: string\n  expires_in: number        \u002F\u002F seconds\n  token_type: 'Bearer'\n}\n\nclass TokenManager {\n  private refreshTimer: NodeJS.Timeout | null = null\n\n  async setTokens(tokens: TokenSet): Promise\u003Cvoid> {\n    \u002F\u002F Store tokens securely (httpOnly cookies or encrypted storage)\n    await secureStore.set('access_token', tokens.access_token)\n    await secureStore.set('refresh_token', tokens.refresh_token)\n\n    \u002F\u002F Schedule refresh before expiry (refresh at 75% of lifetime)\n    const refreshIn = tokens.expires_in * 0.75 * 1000\n    this.scheduleRefresh(refreshIn)\n  }\n\n  private scheduleRefresh(delayMs: number): void {\n    if (this.refreshTimer) clearTimeout(this.refreshTimer)\n    this.refreshTimer = setTimeout(() => this.refresh(), delayMs)\n  }\n\n  private async refresh(): Promise\u003Cvoid> {\n    const refreshToken = await secureStore.get('refresh_token')\n    if (!refreshToken) {\n      this.emit('session_expired')\n      return\n    }\n\n    try {\n      const response = await fetch(config.tokenEndpoint, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\u002Fx-www-form-urlencoded' },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token: refreshToken,\n          client_id: config.clientId,\n        }),\n      })\n\n      if (!response.ok) throw new Error('Refresh failed')\n\n      const tokens = await response.json() as TokenSet\n      await this.setTokens(tokens)\n    } catch (err) {\n      \u002F\u002F Refresh token expired or revoked\n      await this.clearTokens()\n      this.emit('session_expired')\n    }\n  }\n\n  async clearTokens(): Promise\u003Cvoid> {\n    if (this.refreshTimer) clearTimeout(this.refreshTimer)\n    await secureStore.delete('access_token')\n    await secureStore.delete('refresh_token')\n  }\n}\n```\n\n## Social Login Integration\n\n```typescript\n\u002F\u002F Provider-specific configurations\nconst OAUTH_PROVIDERS: Record\u003Cstring, OAuthConfig> = {\n  google: {\n    clientId: process.env.GOOGLE_CLIENT_ID!,\n    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n    authorizationEndpoint: 'https:\u002F\u002Faccounts.google.com\u002Fo\u002Foauth2\u002Fv2\u002Fauth',\n    tokenEndpoint: 'https:\u002F\u002Foauth2.googleapis.com\u002Ftoken',\n    userInfoEndpoint: 'https:\u002F\u002Fwww.googleapis.com\u002Foauth2\u002Fv3\u002Fuserinfo',\n    scopes: ['openid', 'profile', 'email'],\n  },\n  github: {\n    clientId: process.env.GITHUB_CLIENT_ID!,\n    clientSecret: process.env.GITHUB_CLIENT_SECRET!,\n    authorizationEndpoint: 'https:\u002F\u002Fgithub.com\u002Flogin\u002Foauth\u002Fauthorize',\n    tokenEndpoint: 'https:\u002F\u002Fgithub.com\u002Flogin\u002Foauth\u002Faccess_token',\n    userInfoEndpoint: 'https:\u002F\u002Fapi.github.com\u002Fuser',\n    scopes: ['user:email'],\n  },\n}\n\n\u002F\u002F Callback handler: link social account to internal user\nasync function handleOAuthCallback(\n  provider: string,\n  code: string,\n  state: string\n): Promise\u003C{ user: User; session: Session }> {\n  \u002F\u002F Verify state matches stored state (CSRF protection)\n  const storedState = await sessionStore.get(`oauth_state_${state}`)\n  if (!storedState) throw new Error('Invalid state parameter')\n\n  const config = OAUTH_PROVIDERS[provider]\n  if (!config) throw new Error(`Unknown provider: ${provider}`)\n\n  \u002F\u002F Exchange code for tokens\n  const tokens = await exchangeCodeForTokens(code, storedState.verifier, config)\n\n  \u002F\u002F Fetch user profile from provider\n  const profile = await fetchUserProfile(config.userInfoEndpoint, tokens.access_token)\n\n  \u002F\u002F Find or create user (link social identity)\n  let user = await db.user.findFirst({\n    where: {\n      socialAccounts: {\n        some: { provider, providerUserId: profile.sub }\n      }\n    }\n  })\n\n  if (!user) {\n    user = await db.user.create({\n      data: {\n        email: profile.email,\n        displayName: profile.name,\n        socialAccounts: {\n          create: {\n            provider,\n            providerUserId: profile.sub,\n            email: profile.email,\n          }\n        }\n      }\n    })\n  }\n\n  const session = await createSession(user.id)\n  return { user, session }\n}\n```\n\n## Session Management\n\n```typescript\nimport { SignJWT, jwtVerify } from 'jose'\n\nconst SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET!)\n\nasync function createSession(userId: string): Promise\u003Cstring> {\n  const sessionId = crypto.randomUUID()\n\n  \u002F\u002F Store session server-side (not just JWT)\n  await db.session.create({\n    data: {\n      id: sessionId,\n      userId,\n      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),  \u002F\u002F 24h\n      createdAt: new Date(),\n    }\n  })\n\n  \u002F\u002F Issue signed session token\n  const token = await new SignJWT({ sub: userId, sid: sessionId })\n    .setProtectedHeader({ alg: 'HS256' })\n    .setIssuedAt()\n    .setExpirationTime('24h')\n    .sign(SESSION_SECRET)\n\n  return token\n}\n\n\u002F\u002F Set session cookie (httpOnly, secure, sameSite)\nfunction setSessionCookie(res: Response, token: string): void {\n  res.cookie('session', token, {\n    httpOnly: true,       \u002F\u002F Not accessible via JavaScript\n    secure: true,         \u002F\u002F HTTPS only\n    sameSite: 'lax',      \u002F\u002F CSRF protection\n    maxAge: 24 * 60 * 60 * 1000,\n    path: '\u002F',\n    domain: '.example.com',\n  })\n}\n\n\u002F\u002F Validate session middleware\nasync function validateSession(req: Request, res: Response, next: NextFunction) {\n  const token = req.cookies.session\n  if (!token) return res.status(401).json({ error: 'No session' })\n\n  try {\n    const { payload } = await jwtVerify(token, SESSION_SECRET)\n    const session = await db.session.findUnique({ where: { id: payload.sid as string } })\n\n    if (!session || session.expiresAt \u003C new Date()) {\n      return res.status(401).json({ error: 'Session expired' })\n    }\n\n    req.user = { id: payload.sub as string, sessionId: session.id }\n    next()\n  } catch {\n    return res.status(401).json({ error: 'Invalid session' })\n  }\n}\n```\n\n## Checklist\n\n- [ ] PKCE enabled for all public clients (SPA, mobile, CLI)\n- [ ] State parameter validated on callback (CSRF protection)\n- [ ] Tokens stored in httpOnly secure cookies (not localStorage)\n- [ ] Refresh tokens rotated on each use (detect token theft)\n- [ ] Session validated server-side (not just JWT expiry)\n- [ ] Social login links to internal user account (not provider-dependent)\n- [ ] Nonce in OIDC requests for replay protection\n- [ ] Logout: revoke tokens + clear session + clear cookies\n\n## Anti-Patterns\n\n- Storing tokens in localStorage (XSS exposes all tokens)\n- Implicit grant flow (deprecated, tokens in URL fragment)\n- Not validating state parameter: vulnerable to CSRF\n- Long-lived access tokens without refresh mechanism\n- Trusting JWT without server-side session validation (no revocation)\n- Hardcoding client secrets in frontend code (use backend proxy)\n",{"name":5,"description":7},"main","70e1c350500ee750987420bbae48837afa906b7e","skills\u002Foauth-patterns","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis","https:\u002F\u002Fgithub.com\u002Frubicanjr","linktr.ee\u002Ffinanskodu","Industrial Engineer & CIO @FinansKodu. Architecting AI-driven financial agents and autonomous workflows. 🏗️ Building systems, not just prompts.","2026-06-09 13:50:15",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add rubicanjr\u002FFinCognis --skill oauth-patterns",{"tool":56,"command":57},"bunx","bunx skills add rubicanjr\u002FFinCognis --skill oauth-patterns",{"tool":59,"command":60},"pnpm","pnpm dlx skills add rubicanjr\u002FFinCognis --skill oauth-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Foauth-patterns\u002Fskill.md",true,[],[65,86,95,112,142,161],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":76,"publishedAt":37},"01M6NWS9DAXJTADT4T5MB49X8W","workflow-router","Goal-based workflow orchestration - routes tasks to specialist agents based on user goals","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Fworkflow-router\u002FSKILL.md",[71],{"categoryNo":72,"categoryName":73,"taxonomyType":74,"taxonomyTypeDesc":75},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[77,78,79,80,81,82,83,84,85],"workflow router","workflow","router","orchestration","goals","goal","user","workflow orchestration","tasks",{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":37},"01M6NWS3RETSC0SHP16HP66E14","wiring","Wiring Verification","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Fwiring\u002FSKILL.md",[],[88,93,94],"verification","false",{"skillNo":96,"name":97,"nameZh":6,"description":98,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":104,"publishedAt":37},"01M6NWS0XW9CVQZBDA1EGW0TWC","websocket-patterns","Connection management, room patterns, reconnection strategies, message buffering, and binary protocol design.","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Fwebsocket-patterns\u002Fskill.md",[101],{"categoryNo":102,"categoryName":103,"taxonomyType":74,"taxonomyTypeDesc":75},"01JBSEEDCAT000000000000015","视觉设计",[105,19,106,107,108,25,109,30,110,111],"websocket","websocket patterns","design","protocol","message","binary","connection",{"skillNo":113,"name":114,"nameZh":6,"description":115,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":122,"publishedAt":37},"01M6NWRDQDX9ZNETAWRRMQWV52","visual-verdict","Screenshot comparison QA for frontend development. Takes a screenshot of the current implementation, scores it across multiple visual dimensions, and returns a structured PASS\u002FREVISE\u002FFAIL verdict with concrete fixes. Use when implementing UI from a design reference or verifying visual correctness.","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-verdict\u002FSKILL.md",[118,121],{"categoryNo":119,"categoryName":120,"taxonomyType":74,"taxonomyTypeDesc":75},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":72,"categoryName":73,"taxonomyType":74,"taxonomyTypeDesc":75},[123,124,125,126,127,128,129,130,131,132,133,134,135,107,136,137,138,139,140,141],"visual","verdict","visual verdict","ui","across","revise","comparison","correctness","implementing","for","fixes","dimensions","frontend","development","qa","screenshot","returns","use","frontend development",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":148,"publishedAt":37},"01M6NWR8HSNRS7MNDK2J02QYMN","verification-loop","Comprehensive verification system covering build, types, lint, tests, security, and diff review before a PR.","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Fverification-loop\u002FSKILL.md",[],[149,150,93,151,152,153,154,155,156,157,158,159,160],"verification loop","loop","lint","review","security","pr","system","diff","build","tests","diff review","types",{"skillNo":162,"name":163,"nameZh":6,"description":164,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":165,"categories":166,"keywords":167,"publishedAt":37},"01M6NWR4HHSFR0VR5EXFRWA747","vector-db-patterns","Embedding strategies, ANN algorithms, hybrid search, RAG chunking strategies, and reranking for semantic search and retrieval.","https:\u002F\u002Fgithub.com\u002Frubicanjr\u002FFinCognis\u002Fblob\u002Fmain\u002Fskills\u002Fvector-db-patterns\u002Fskill.md",[],[168,169,170,171,19,172,173,174,175,30,176,177,178,179,180,132,181,182],"vector db patterns","vector db","db","vector","semantic search","search","embedding strategies","algorithms","chunking","semantic","retrieval","embedding","Hybrid Search","hybrid","rag"]