diff --git a/.claude/skills/triage-issue/SKILL.md b/.claude/skills/triage-issue/SKILL.md new file mode 100644 index 00000000..82038a38 --- /dev/null +++ b/.claude/skills/triage-issue/SKILL.md @@ -0,0 +1,145 @@ +# Triage Issue Skill + +Analyze a GitHub issue, verify claims against the codebase, and close invalid issues with a technical response. + +## Trigger + +User provides a GitHub issue URL or number, e.g.: +- `/triage-issue 1970` +- `/triage-issue https://github.com/adenhq/hive/issues/1970` + +## Workflow + +### Step 1: Fetch Issue Details + +```bash +gh issue view --repo adenhq/hive --json title,body,state,labels,author +``` + +Extract: +- Title +- Body (the claim/bug report) +- Current state +- Labels +- Author + +If issue is already closed, inform user and stop. + +### Step 2: Analyze the Claim + +Read the issue body and identify: +1. **The core claim** - What is the user asserting? +2. **Technical specifics** - File paths, function names, code snippets mentioned +3. **Expected behavior** - What do they think should happen? +4. **Severity claimed** - Security issue? Bug? Feature request? + +### Step 3: Investigate the Codebase + +For each technical claim: +1. Find the referenced code using Grep/Glob/Read +2. Understand the actual implementation +3. Check if the claim accurately describes the behavior +4. Look for related tests, documentation, or design decisions + +### Step 4: Evaluate Validity + +Categorize the issue as one of: + +| Category | Action | +|----------|--------| +| **Valid Bug** | Do NOT close. Inform user this is a real issue. | +| **Valid Feature Request** | Do NOT close. Suggest labeling appropriately. | +| **Misunderstanding** | Prepare technical explanation for why behavior is correct. | +| **Fundamentally Flawed** | Prepare critique explaining the technical impossibility or design rationale. | +| **Duplicate** | Find the original issue and prepare duplicate notice. | +| **Incomplete** | Prepare request for more information. | + +### Step 5: Draft Response + +For issues to be closed, draft a response that: + +1. **Acknowledges the concern** - Don't be dismissive +2. **Explains the actual behavior** - With code references +3. **Provides technical rationale** - Why it works this way +4. **References industry standards** - If applicable +5. **Offers alternatives** - If there's a better approach for the user + +Use this template: + +```markdown +## Analysis + +[Brief summary of what was investigated] + +## Technical Details + +[Explanation with code references] + +## Why This Is Working As Designed + +[Rationale] + +## Recommendation + +[What the user should do instead, if applicable] + +--- +*This issue was reviewed and closed by the maintainers.* +``` + +### Step 6: User Review + +Present the draft to the user with: + +``` +## Issue #: + +**Claim:** <summary of claim> + +**Finding:** <valid/invalid/misunderstanding/etc> + +**Draft Response:** +<the markdown response> + +--- +Do you want me to post this comment and close the issue? +``` + +Use AskUserQuestion with options: +- "Post and close" - Post comment, close issue +- "Edit response" - Let user modify the response +- "Skip" - Don't take action + +### Step 7: Execute Action + +If user approves: + +```bash +# Post comment +gh issue comment <number> --repo adenhq/hive --body "<response>" + +# Close issue +gh issue close <number> --repo adenhq/hive --reason "not planned" +``` + +Report success with link to the issue. + +## Important Guidelines + +1. **Never close valid issues** - If there's any merit to the claim, don't close it +2. **Be respectful** - The reporter took time to file the issue +3. **Be technical** - Provide code references and evidence +4. **Be educational** - Help them understand, don't just dismiss +5. **Check twice** - Make sure you understand the code before declaring something invalid +6. **Consider edge cases** - Maybe their environment reveals a real issue + +## Example Critiques + +### Security Misunderstanding +> "The claim that secrets are exposed in plaintext misunderstands the encryption architecture. While `SecretStr` is used for logging protection, actual encryption is provided by Fernet (AES-128-CBC) at the storage layer. The code path is: serialize → encrypt → write. Only encrypted bytes touch disk." + +### Impossible Request +> "The requested feature would require [X] which violates [fundamental constraint]. This is not a limitation of our implementation but a fundamental property of [technology/protocol]." + +### Already Handled +> "This scenario is already handled by [code reference]. The reporter may be using an older version or misconfigured environment." diff --git a/core/framework/credentials/aden/client.py b/core/framework/credentials/aden/client.py index b9bb68d4..8470a8e0 100644 --- a/core/framework/credentials/aden/client.py +++ b/core/framework/credentials/aden/client.py @@ -175,8 +175,8 @@ class AdenIntegrationInfo: return cls( integration_id=data["integration_id"], - integration_type=data["integration_type"], - status=data["status"], + integration_type=data.get("provider", data["integration_id"]), + status=data.get("status", "unknown"), expires_at=expires_at, ) @@ -254,6 +254,9 @@ class AdenCredentialClient: ) -> httpx.Response: """Make a request with retry logic.""" client = self._get_client() + print(client.base_url) + print(client.headers) + print(method, path, kwargs) last_error: Exception | None = None for attempt in range(self.config.retry_attempts): diff --git a/core/framework/credentials/store.py b/core/framework/credentials/store.py index 8202b6d9..27644d9c 100644 --- a/core/framework/credentials/store.py +++ b/core/framework/credentials/store.py @@ -612,3 +612,97 @@ class CredentialStore: providers=providers, **kwargs, ) + + @classmethod + def with_aden_sync( + cls, + base_url: str = "https://hive.adenhq.com", + cache_ttl_seconds: int = 300, + local_path: str | None = None, + auto_sync: bool = True, + **kwargs: Any, + ) -> CredentialStore: + """ + Create a credential store with Aden server sync. + + Automatically syncs OAuth2 tokens from the Aden authentication server. + Falls back to local-only storage if ADEN_API_KEY is not set or Aden + is unreachable. + + Args: + base_url: Aden server URL (default: https://hive.adenhq.com) + cache_ttl_seconds: How long to cache credentials locally (default: 5 min) + local_path: Path for local credential storage (default: ~/.hive/credentials) + auto_sync: Whether to sync all credentials on startup (default: True) + **kwargs: Additional arguments passed to CredentialStore + + Returns: + CredentialStore configured with Aden sync + + Example: + # Simple usage - just set ADEN_API_KEY env var + store = CredentialStore.with_aden_sync() + + # Get HubSpot token (auto-refreshed via Aden) + token = store.get_key("hubspot", "access_token") + """ + import os + from pathlib import Path + + from .storage import EncryptedFileStorage + + # Determine local storage path + if local_path is None: + local_path = str(Path.home() / ".hive" / "credentials") + + local_storage = EncryptedFileStorage(base_path=local_path) + + # Check if Aden is configured + api_key = os.environ.get("ADEN_API_KEY") + if not api_key: + logger.info("ADEN_API_KEY not set, using local-only credential storage") + return cls(storage=local_storage, **kwargs) + + # Try to setup Aden sync + try: + from .aden import ( + AdenCachedStorage, + AdenClientConfig, + AdenCredentialClient, + AdenSyncProvider, + ) + + # Create Aden client + client = AdenCredentialClient(AdenClientConfig(base_url=base_url)) + + # Create sync provider + provider = AdenSyncProvider(client=client) + + # Use cached storage for offline resilience + cached_storage = AdenCachedStorage( + local_storage=local_storage, + aden_provider=provider, + cache_ttl_seconds=cache_ttl_seconds, + ) + + store = cls( + storage=cached_storage, + providers=[provider], + auto_refresh=True, + **kwargs, + ) + + # Initial sync + if auto_sync: + synced = provider.sync_all(store) + logger.info(f"Synced {synced} credentials from Aden server") + + return store + + except ImportError: + logger.warning("Aden components not available, using local storage") + return cls(storage=local_storage, **kwargs) + + except Exception as e: + logger.warning(f"Failed to setup Aden sync: {e}. Using local storage.") + return cls(storage=local_storage, **kwargs)