mindsystem-cc 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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +501 -0
  3. package/agents/ms-codebase-mapper.md +739 -0
  4. package/agents/ms-debugger.md +1184 -0
  5. package/agents/ms-designer.md +414 -0
  6. package/agents/ms-executor.md +760 -0
  7. package/agents/ms-integration-checker.md +423 -0
  8. package/agents/ms-milestone-auditor.md +448 -0
  9. package/agents/ms-mock-generator.md +182 -0
  10. package/agents/ms-plan-checker.md +746 -0
  11. package/agents/ms-research-synthesizer.md +248 -0
  12. package/agents/ms-researcher.md +962 -0
  13. package/agents/ms-roadmapper.md +606 -0
  14. package/agents/ms-verifier.md +779 -0
  15. package/agents/ms-verify-fixer.md +124 -0
  16. package/bin/install.js +296 -0
  17. package/commands/ms/add-phase.md +207 -0
  18. package/commands/ms/add-todo.md +182 -0
  19. package/commands/ms/audit-milestone.md +318 -0
  20. package/commands/ms/check-phase.md +162 -0
  21. package/commands/ms/check-todos.md +217 -0
  22. package/commands/ms/complete-milestone.md +137 -0
  23. package/commands/ms/create-roadmap.md +273 -0
  24. package/commands/ms/debug.md +149 -0
  25. package/commands/ms/define-requirements.md +121 -0
  26. package/commands/ms/design-phase.md +341 -0
  27. package/commands/ms/discuss-milestone.md +48 -0
  28. package/commands/ms/discuss-phase.md +60 -0
  29. package/commands/ms/do-work.md +90 -0
  30. package/commands/ms/execute-phase.md +289 -0
  31. package/commands/ms/help.md +623 -0
  32. package/commands/ms/insert-phase.md +227 -0
  33. package/commands/ms/list-phase-assumptions.md +50 -0
  34. package/commands/ms/map-codebase.md +71 -0
  35. package/commands/ms/new-milestone.md +193 -0
  36. package/commands/ms/new-project.md +338 -0
  37. package/commands/ms/pause-work.md +123 -0
  38. package/commands/ms/plan-milestone-gaps.md +285 -0
  39. package/commands/ms/plan-phase.md +105 -0
  40. package/commands/ms/progress.md +370 -0
  41. package/commands/ms/remove-phase.md +338 -0
  42. package/commands/ms/research-phase.md +175 -0
  43. package/commands/ms/research-project.md +339 -0
  44. package/commands/ms/resume-work.md +40 -0
  45. package/commands/ms/review-design.md +484 -0
  46. package/commands/ms/simplify-flutter.md +193 -0
  47. package/commands/ms/update.md +159 -0
  48. package/commands/ms/verify-work.md +92 -0
  49. package/commands/ms/whats-new.md +124 -0
  50. package/mindsystem/references/checkpoints.md +788 -0
  51. package/mindsystem/references/continuation-format.md +255 -0
  52. package/mindsystem/references/debugging/debugging-mindset.md +11 -0
  53. package/mindsystem/references/debugging/hypothesis-testing.md +11 -0
  54. package/mindsystem/references/debugging/investigation-techniques.md +11 -0
  55. package/mindsystem/references/debugging/verification-patterns.md +11 -0
  56. package/mindsystem/references/debugging/when-to-research.md +11 -0
  57. package/mindsystem/references/git-integration.md +254 -0
  58. package/mindsystem/references/goal-backward.md +286 -0
  59. package/mindsystem/references/mock-patterns.md +294 -0
  60. package/mindsystem/references/plan-format.md +473 -0
  61. package/mindsystem/references/principles.md +73 -0
  62. package/mindsystem/references/questioning.md +140 -0
  63. package/mindsystem/references/research-pitfalls.md +233 -0
  64. package/mindsystem/references/scope-estimation.md +256 -0
  65. package/mindsystem/references/tdd.md +263 -0
  66. package/mindsystem/references/verification-patterns.md +595 -0
  67. package/mindsystem/templates/DEBUG.md +159 -0
  68. package/mindsystem/templates/UAT.md +403 -0
  69. package/mindsystem/templates/adhoc-summary.md +153 -0
  70. package/mindsystem/templates/codebase/architecture.md +255 -0
  71. package/mindsystem/templates/codebase/concerns.md +310 -0
  72. package/mindsystem/templates/codebase/conventions.md +307 -0
  73. package/mindsystem/templates/codebase/integrations.md +280 -0
  74. package/mindsystem/templates/codebase/stack.md +186 -0
  75. package/mindsystem/templates/codebase/structure.md +285 -0
  76. package/mindsystem/templates/codebase/testing.md +480 -0
  77. package/mindsystem/templates/config.json +26 -0
  78. package/mindsystem/templates/context.md +140 -0
  79. package/mindsystem/templates/continue-here.md +78 -0
  80. package/mindsystem/templates/debug-subagent-prompt.md +91 -0
  81. package/mindsystem/templates/design-iteration.md +208 -0
  82. package/mindsystem/templates/design.md +417 -0
  83. package/mindsystem/templates/discovery.md +146 -0
  84. package/mindsystem/templates/milestone-archive.md +123 -0
  85. package/mindsystem/templates/milestone-context.md +93 -0
  86. package/mindsystem/templates/milestone.md +115 -0
  87. package/mindsystem/templates/phase-prompt.md +574 -0
  88. package/mindsystem/templates/project.md +184 -0
  89. package/mindsystem/templates/requirements.md +231 -0
  90. package/mindsystem/templates/research-project/ARCHITECTURE.md +204 -0
  91. package/mindsystem/templates/research-project/FEATURES.md +147 -0
  92. package/mindsystem/templates/research-project/PITFALLS.md +200 -0
  93. package/mindsystem/templates/research-project/STACK.md +120 -0
  94. package/mindsystem/templates/research-project/SUMMARY.md +170 -0
  95. package/mindsystem/templates/research-subagent-prompt.md +92 -0
  96. package/mindsystem/templates/research.md +529 -0
  97. package/mindsystem/templates/roadmap.md +214 -0
  98. package/mindsystem/templates/state.md +224 -0
  99. package/mindsystem/templates/summary.md +269 -0
  100. package/mindsystem/templates/user-setup.md +323 -0
  101. package/mindsystem/templates/verification-report.md +322 -0
  102. package/mindsystem/workflows/complete-milestone.md +759 -0
  103. package/mindsystem/workflows/create-milestone.md +203 -0
  104. package/mindsystem/workflows/debug.md +14 -0
  105. package/mindsystem/workflows/define-requirements.md +330 -0
  106. package/mindsystem/workflows/diagnose-issues.md +241 -0
  107. package/mindsystem/workflows/discovery-phase.md +293 -0
  108. package/mindsystem/workflows/discuss-milestone.md +310 -0
  109. package/mindsystem/workflows/discuss-phase.md +237 -0
  110. package/mindsystem/workflows/do-work.md +359 -0
  111. package/mindsystem/workflows/execute-phase.md +644 -0
  112. package/mindsystem/workflows/execute-plan.md +1828 -0
  113. package/mindsystem/workflows/generate-mocks.md +187 -0
  114. package/mindsystem/workflows/list-phase-assumptions.md +178 -0
  115. package/mindsystem/workflows/map-codebase.md +289 -0
  116. package/mindsystem/workflows/plan-phase.md +876 -0
  117. package/mindsystem/workflows/research-phase.md +17 -0
  118. package/mindsystem/workflows/research-project.md +23 -0
  119. package/mindsystem/workflows/resume-project.md +311 -0
  120. package/mindsystem/workflows/transition.md +564 -0
  121. package/mindsystem/workflows/verify-phase.md +629 -0
  122. package/mindsystem/workflows/verify-work.md +823 -0
  123. package/package.json +32 -0
  124. package/scripts/generate-phase-patch.sh +169 -0
  125. package/scripts/ms-lookup/README.md +112 -0
  126. package/scripts/ms-lookup/ms_lookup/__init__.py +3 -0
  127. package/scripts/ms-lookup/ms_lookup/__main__.py +6 -0
  128. package/scripts/ms-lookup/ms_lookup/backends/__init__.py +6 -0
  129. package/scripts/ms-lookup/ms_lookup/backends/context7.py +219 -0
  130. package/scripts/ms-lookup/ms_lookup/backends/perplexity.py +145 -0
  131. package/scripts/ms-lookup/ms_lookup/cache.py +48 -0
  132. package/scripts/ms-lookup/ms_lookup/cli.py +219 -0
  133. package/scripts/ms-lookup/ms_lookup/config.py +23 -0
  134. package/scripts/ms-lookup/ms_lookup/errors.py +24 -0
  135. package/scripts/ms-lookup/ms_lookup/output.py +49 -0
  136. package/scripts/ms-lookup/ms_lookup/tokens.py +56 -0
  137. package/scripts/ms-lookup/pyproject.toml +17 -0
  138. package/scripts/ms-lookup/uv.lock +207 -0
  139. package/scripts/ms-lookup-wrapper.sh +21 -0
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "mindsystem-cc",
3
+ "version": "3.0.0",
4
+ "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.",
5
+ "bin": {
6
+ "mindsystem-cc": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "commands",
11
+ "mindsystem",
12
+ "agents",
13
+ "scripts"
14
+ ],
15
+ "keywords": [
16
+ "claude",
17
+ "claude-code",
18
+ "ai",
19
+ "meta-prompting",
20
+ "context-engineering",
21
+ "spec-driven-development"
22
+ ],
23
+ "author": "Roland Tolnay",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/rolandtolnay/mindsystem.git"
28
+ },
29
+ "engines": {
30
+ "node": ">=16.7.0"
31
+ }
32
+ }
@@ -0,0 +1,169 @@
1
+ #!/bin/bash
2
+ #
3
+ # generate-phase-patch.sh
4
+ # Generates a patch file with implementation changes from a phase,
5
+ # excluding documentation and generated files.
6
+ #
7
+ # Usage: ./scripts/generate-phase-patch.sh <phase_number> [--suffix=<suffix>]
8
+ # Example: ./scripts/generate-phase-patch.sh 04
9
+ # ./scripts/generate-phase-patch.sh 4
10
+ # ./scripts/generate-phase-patch.sh 04 --suffix=uat-fixes
11
+ #
12
+ # Options:
13
+ # --suffix=<suffix> Filter commits by message pattern and use custom output filename
14
+ # When --suffix=uat-fixes: looks for commits with "({phase}-uat):" pattern
15
+ # Output: {phase}-{suffix}.patch instead of {phase}-changes.patch
16
+
17
+ set -e
18
+
19
+ # --- Configuration ---
20
+ EXCLUSIONS=(
21
+ # Documentation
22
+ '.planning'
23
+
24
+ # Flutter/Dart generated
25
+ '*.g.dart'
26
+ '*.freezed.dart'
27
+ '*.gr.dart'
28
+ 'generated'
29
+ '.dart_tool'
30
+
31
+ # Next.js/TypeScript generated
32
+ 'node_modules'
33
+ '.next'
34
+ 'dist'
35
+ 'build'
36
+ '*.d.ts'
37
+ '.turbo'
38
+
39
+ # Common build artifacts
40
+ '*.lock'
41
+ )
42
+
43
+ # --- Parse arguments ---
44
+ PHASE_INPUT=""
45
+ SUFFIX=""
46
+
47
+ for arg in "$@"; do
48
+ case $arg in
49
+ --suffix=*)
50
+ SUFFIX="${arg#*=}"
51
+ ;;
52
+ *)
53
+ if [ -z "$PHASE_INPUT" ]; then
54
+ PHASE_INPUT="$arg"
55
+ fi
56
+ ;;
57
+ esac
58
+ done
59
+
60
+ # --- Validation ---
61
+ if [ -z "$PHASE_INPUT" ]; then
62
+ echo "Error: Phase number required"
63
+ echo "Usage: $0 <phase_number> [--suffix=<suffix>]"
64
+ exit 1
65
+ fi
66
+
67
+ # --- Find git root ---
68
+ GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
69
+ if [ -z "$GIT_ROOT" ]; then
70
+ echo "Error: Not in a git repository"
71
+ exit 1
72
+ fi
73
+ cd "$GIT_ROOT"
74
+
75
+ # --- Normalize phase number (zero-pad if needed) ---
76
+ if [[ "$PHASE_INPUT" =~ ^[0-9]$ ]]; then
77
+ PHASE_NUMBER=$(printf "%02d" "$PHASE_INPUT")
78
+ else
79
+ PHASE_NUMBER="$PHASE_INPUT"
80
+ fi
81
+
82
+ # --- Determine commit pattern based on suffix ---
83
+ if [ -n "$SUFFIX" ]; then
84
+ # With suffix: filter by specific pattern
85
+ # For uat-fixes: look for "fix({phase}-uat):" pattern
86
+ if [ "$SUFFIX" = "uat-fixes" ]; then
87
+ COMMIT_PATTERN="\($PHASE_NUMBER-uat\):"
88
+ echo "Generating UAT fixes patch for phase $PHASE_NUMBER..."
89
+ else
90
+ # Generic suffix: look for pattern with suffix in scope
91
+ COMMIT_PATTERN="\($PHASE_NUMBER-$SUFFIX\):"
92
+ echo "Generating $SUFFIX patch for phase $PHASE_NUMBER..."
93
+ fi
94
+ else
95
+ # No suffix: standard phase commits
96
+ COMMIT_PATTERN="\($PHASE_NUMBER-"
97
+ echo "Generating patch for phase $PHASE_NUMBER..."
98
+ fi
99
+
100
+ # --- Find matching commits ---
101
+ PHASE_COMMITS=$(git log --oneline | grep -E "$COMMIT_PATTERN" | cut -d' ' -f1 || true)
102
+
103
+ if [ -z "$PHASE_COMMITS" ]; then
104
+ echo "No commits found matching pattern: $COMMIT_PATTERN"
105
+ echo "Patch skipped"
106
+ exit 0
107
+ fi
108
+
109
+ COMMIT_COUNT=$(echo "$PHASE_COMMITS" | wc -l | tr -d ' ')
110
+ echo "Found $COMMIT_COUNT commit(s)"
111
+
112
+ # --- Determine base commit ---
113
+ EARLIEST_COMMIT=$(echo "$PHASE_COMMITS" | tail -1)
114
+ BASE_COMMIT=$(git rev-parse "${EARLIEST_COMMIT}^" 2>/dev/null || git rev-list --max-parents=0 HEAD)
115
+ echo "Base commit: $(git log --oneline -1 "$BASE_COMMIT")"
116
+
117
+ # --- Find output directory ---
118
+ PHASES_DIR=".planning/phases"
119
+ PHASE_DIR=$(find "$PHASES_DIR" -maxdepth 1 -type d -name "${PHASE_NUMBER}-*" 2>/dev/null | head -1)
120
+
121
+ if [ -z "$PHASE_DIR" ]; then
122
+ PHASE_DIR="$PHASES_DIR"
123
+ echo "No phase directory found, saving to $PHASES_DIR/"
124
+ else
125
+ echo "Output directory: $PHASE_DIR/"
126
+ fi
127
+
128
+ # Ensure directory exists
129
+ mkdir -p "$PHASE_DIR"
130
+
131
+ # --- Build exclusion arguments ---
132
+ EXCLUDE_ARGS=""
133
+ for pattern in "${EXCLUSIONS[@]}"; do
134
+ EXCLUDE_ARGS="$EXCLUDE_ARGS ':!$pattern'"
135
+ done
136
+
137
+ # --- Determine output filename ---
138
+ if [ -n "$SUFFIX" ]; then
139
+ PATCH_FILE="$PHASE_DIR/${PHASE_NUMBER}-${SUFFIX}.patch"
140
+ else
141
+ PATCH_FILE="$PHASE_DIR/${PHASE_NUMBER}-changes.patch"
142
+ fi
143
+
144
+ # --- Generate diff ---
145
+ # For suffix mode: generate diff only for the specific commits
146
+ # For standard mode: generate diff from base to HEAD
147
+ if [ -n "$SUFFIX" ]; then
148
+ # Create combined diff from all matching commits
149
+ LATEST_COMMIT=$(echo "$PHASE_COMMITS" | head -1)
150
+ eval "git diff \"$BASE_COMMIT\" \"$LATEST_COMMIT\" -- . $EXCLUDE_ARGS" > "$PATCH_FILE"
151
+ else
152
+ eval "git diff \"$BASE_COMMIT\" HEAD -- . $EXCLUDE_ARGS" > "$PATCH_FILE"
153
+ fi
154
+
155
+ # --- Check result ---
156
+ if [ ! -s "$PATCH_FILE" ]; then
157
+ rm "$PATCH_FILE"
158
+ echo "No implementation changes outside excluded patterns"
159
+ echo "Patch skipped"
160
+ exit 0
161
+ fi
162
+
163
+ PATCH_LINES=$(wc -l < "$PATCH_FILE" | tr -d ' ')
164
+ echo ""
165
+ echo "Generated: $PATCH_FILE ($PATCH_LINES lines)"
166
+ echo ""
167
+ echo "Review: cat $PATCH_FILE"
168
+ echo "Apply: git apply $PATCH_FILE"
169
+ echo "Discard: rm $PATCH_FILE"
@@ -0,0 +1,112 @@
1
+ # ms-lookup
2
+
3
+ CLI tool for Mindsystem research providing access to Context7 library documentation and Perplexity deep research.
4
+
5
+ ## Installation
6
+
7
+ The tool is installed automatically with Mindsystem. To use it standalone:
8
+
9
+ ```bash
10
+ cd scripts/ms-lookup
11
+ uv sync
12
+ uv run python -m ms_lookup --help
13
+ ```
14
+
15
+ Or with pip:
16
+
17
+ ```bash
18
+ pip install -e .
19
+ ms-lookup --help
20
+ ```
21
+
22
+ ## Environment Variables
23
+
24
+ ```bash
25
+ # Required for docs command
26
+ export CONTEXT7_API_KEY="your-context7-api-key"
27
+
28
+ # Required for deep command
29
+ export PERPLEXITY_API_KEY="your-perplexity-api-key"
30
+ ```
31
+
32
+ Get API keys:
33
+ - Context7: https://context7.com/dashboard
34
+ - Perplexity: https://docs.perplexity.ai/
35
+
36
+ ## Commands
37
+
38
+ ### docs - Library Documentation
39
+
40
+ Query library documentation via Context7. Provides authoritative, version-aware API documentation.
41
+
42
+ ```bash
43
+ ms-lookup docs <library> "<query>"
44
+
45
+ # Examples
46
+ ms-lookup docs nextjs "app router setup"
47
+ ms-lookup docs react "hooks useEffect cleanup"
48
+ ms-lookup docs "react-three-fiber" "physics integration"
49
+ ```
50
+
51
+ ### deep - Deep Research
52
+
53
+ Perform comprehensive multi-source research via Perplexity.
54
+
55
+ **Note:** This costs ~$0.005 per query + tokens. Use sparingly for high-value research questions.
56
+
57
+ ```bash
58
+ ms-lookup deep "<query>"
59
+
60
+ # Examples
61
+ ms-lookup deep "authentication patterns for SaaS applications"
62
+ ms-lookup deep "WebGPU browser support and performance 2026"
63
+ ```
64
+
65
+ ## Options
66
+
67
+ | Option | Description |
68
+ |--------|-------------|
69
+ | `--max-tokens, -t` | Maximum tokens in response (default: 2000) |
70
+ | `--no-cache` | Skip cache lookup |
71
+ | `--json-pretty, -p` | Pretty-print JSON output |
72
+ | `--version, -v` | Show version |
73
+
74
+ ## Output Format
75
+
76
+ All commands return JSON:
77
+
78
+ ```json
79
+ {
80
+ "success": true,
81
+ "command": "docs",
82
+ "query": "app router setup",
83
+ "library": "nextjs",
84
+ "results": [
85
+ {
86
+ "title": "App Router Getting Started",
87
+ "content": "The App Router uses...",
88
+ "source_url": "https://nextjs.org/docs/...",
89
+ "tokens": 450,
90
+ "type": "info"
91
+ }
92
+ ],
93
+ "metadata": {
94
+ "library_id": "/vercel/next.js",
95
+ "total_available": 15,
96
+ "returned": 3,
97
+ "tokens_used": 830,
98
+ "max_tokens": 2000,
99
+ "cache_hit": false,
100
+ "confidence": "HIGH",
101
+ "backend": "context7"
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Caching
107
+
108
+ Results are cached in `~/.cache/ms-lookup/`:
109
+ - docs: 24 hours TTL
110
+ - deep: 6 hours TTL
111
+
112
+ Use `--no-cache` to bypass cache.
@@ -0,0 +1,3 @@
1
+ """Mindsystem Lookup CLI - Context7 docs and Perplexity deep research."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m ms_lookup."""
2
+
3
+ from ms_lookup.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,6 @@
1
+ """API backends for ms-lookup."""
2
+
3
+ from ms_lookup.backends.context7 import Context7Client
4
+ from ms_lookup.backends.perplexity import PerplexityClient
5
+
6
+ __all__ = ["Context7Client", "PerplexityClient"]
@@ -0,0 +1,219 @@
1
+ """Context7 API client for library documentation."""
2
+
3
+ import httpx
4
+
5
+ from ms_lookup.config import CONTEXT7_API_KEY, CONTEXT7_BASE_URL
6
+ from ms_lookup.errors import ErrorCode, MsLookupError
7
+
8
+
9
+ class Context7Client:
10
+ """Client for Context7 documentation API."""
11
+
12
+ def __init__(self, api_key: str | None = None):
13
+ self.api_key = api_key or CONTEXT7_API_KEY
14
+ self.base_url = CONTEXT7_BASE_URL
15
+
16
+ def _check_api_key(self) -> None:
17
+ """Verify API key is configured."""
18
+ if not self.api_key:
19
+ raise MsLookupError(
20
+ ErrorCode.MISSING_API_KEY,
21
+ "CONTEXT7_API_KEY environment variable not set",
22
+ suggestions=[
23
+ "Set CONTEXT7_API_KEY in your environment",
24
+ "Get an API key at https://context7.com/dashboard",
25
+ ],
26
+ )
27
+
28
+ def resolve_library(self, library_name: str, query: str) -> dict:
29
+ """Resolve library name to Context7 library ID.
30
+
31
+ Args:
32
+ library_name: Library name to search for
33
+ query: User's original query (used for relevance ranking)
34
+
35
+ Returns:
36
+ Dict with library_id and library info
37
+
38
+ Raises:
39
+ MsLookupError: If library not found or API error
40
+ """
41
+ self._check_api_key()
42
+
43
+ url = f"{self.base_url}/libs/search"
44
+ headers = {"Authorization": f"Bearer {self.api_key}"}
45
+ params = {"query": library_name, "limit": 5}
46
+
47
+ try:
48
+ with httpx.Client(timeout=30.0) as client:
49
+ response = client.get(url, headers=headers, params=params)
50
+ response.raise_for_status()
51
+ data = response.json()
52
+ except httpx.HTTPStatusError as e:
53
+ if e.response.status_code == 401:
54
+ raise MsLookupError(
55
+ ErrorCode.MISSING_API_KEY,
56
+ "Invalid CONTEXT7_API_KEY",
57
+ suggestions=["Check your API key at https://context7.com/dashboard"],
58
+ )
59
+ elif e.response.status_code == 429:
60
+ raise MsLookupError(
61
+ ErrorCode.RATE_LIMITED,
62
+ "Context7 API rate limit exceeded",
63
+ suggestions=["Wait a moment and try again", "Use --no-cache sparingly"],
64
+ )
65
+ raise MsLookupError(
66
+ ErrorCode.API_ERROR,
67
+ f"Context7 API error: {e.response.status_code}",
68
+ )
69
+ except httpx.RequestError as e:
70
+ raise MsLookupError(
71
+ ErrorCode.NETWORK_ERROR,
72
+ f"Network error connecting to Context7: {str(e)}",
73
+ )
74
+
75
+ # Find best matching library
76
+ libraries = data.get("results", [])
77
+ if not libraries:
78
+ raise MsLookupError(
79
+ ErrorCode.LIBRARY_NOT_FOUND,
80
+ f"Could not find library '{library_name}' in Context7",
81
+ suggestions=self._get_library_suggestions(library_name),
82
+ )
83
+
84
+ # Return first (best) match
85
+ best_match = libraries[0]
86
+ return {
87
+ "library_id": best_match.get("id", ""),
88
+ "name": best_match.get("name", library_name),
89
+ "description": best_match.get("description", ""),
90
+ "url": best_match.get("url", ""),
91
+ }
92
+
93
+ def query_docs(self, library_id: str, query: str) -> dict:
94
+ """Query documentation for a library.
95
+
96
+ Args:
97
+ library_id: Context7 library ID (e.g., "/vercel/next.js")
98
+ query: Documentation query
99
+
100
+ Returns:
101
+ Dict with documentation results
102
+
103
+ Raises:
104
+ MsLookupError: If API error
105
+ """
106
+ self._check_api_key()
107
+
108
+ url = f"{self.base_url}/context"
109
+ headers = {"Authorization": f"Bearer {self.api_key}"}
110
+ params = {"libraryId": library_id, "query": query}
111
+
112
+ try:
113
+ with httpx.Client(timeout=60.0) as client:
114
+ response = client.get(url, headers=headers, params=params)
115
+ response.raise_for_status()
116
+
117
+ # Context7 can return JSON or plain text depending on the endpoint
118
+ content_type = response.headers.get("content-type", "")
119
+ if "application/json" in content_type:
120
+ data = response.json()
121
+ else:
122
+ # Plain text/markdown response - wrap in a structure
123
+ data = {"content": response.text, "format": "markdown"}
124
+ except httpx.HTTPStatusError as e:
125
+ if e.response.status_code == 404:
126
+ raise MsLookupError(
127
+ ErrorCode.LIBRARY_NOT_FOUND,
128
+ f"Library '{library_id}' not found in Context7",
129
+ )
130
+ elif e.response.status_code == 429:
131
+ raise MsLookupError(
132
+ ErrorCode.RATE_LIMITED,
133
+ "Context7 API rate limit exceeded",
134
+ )
135
+ raise MsLookupError(
136
+ ErrorCode.API_ERROR,
137
+ f"Context7 API error: {e.response.status_code}",
138
+ )
139
+ except httpx.RequestError as e:
140
+ raise MsLookupError(
141
+ ErrorCode.NETWORK_ERROR,
142
+ f"Network error connecting to Context7: {str(e)}",
143
+ )
144
+
145
+ return data
146
+
147
+ def format_results(self, raw_response: dict) -> list[dict]:
148
+ """Transform Context7 response to unified format.
149
+
150
+ Args:
151
+ raw_response: Raw API response
152
+
153
+ Returns:
154
+ List of formatted result dicts
155
+ """
156
+ results = []
157
+
158
+ # Process code snippets
159
+ for snippet in raw_response.get("codeSnippets", []):
160
+ results.append({
161
+ "title": snippet.get("title", "Code Example"),
162
+ "content": snippet.get("code", ""),
163
+ "source_url": snippet.get("url", ""),
164
+ "type": "code",
165
+ })
166
+
167
+ # Process info snippets
168
+ for snippet in raw_response.get("infoSnippets", []):
169
+ results.append({
170
+ "title": snippet.get("title", "Documentation"),
171
+ "content": snippet.get("content", ""),
172
+ "source_url": snippet.get("url", ""),
173
+ "type": "info",
174
+ })
175
+
176
+ # If no structured snippets, try to extract from content field
177
+ if not results and "content" in raw_response:
178
+ results.append({
179
+ "title": "Documentation",
180
+ "content": raw_response["content"],
181
+ "source_url": raw_response.get("url", ""),
182
+ "type": "info",
183
+ })
184
+
185
+ return results
186
+
187
+ def _get_library_suggestions(self, query: str) -> list[str]:
188
+ """Get suggestions for similar library names."""
189
+ # Common library name variations
190
+ suggestions = []
191
+ query_lower = query.lower()
192
+
193
+ # Common mappings
194
+ mappings = {
195
+ "react": ["react", "react-dom", "react-router"],
196
+ "next": ["nextjs", "next.js", "vercel/next.js"],
197
+ "nextjs": ["nextjs", "next.js", "vercel/next.js"],
198
+ "vue": ["vue", "vuejs", "vue-router"],
199
+ "angular": ["angular", "@angular/core"],
200
+ "express": ["express", "expressjs"],
201
+ "fastapi": ["fastapi", "tiangolo/fastapi"],
202
+ "django": ["django", "djangoproject"],
203
+ "flask": ["flask", "pallets/flask"],
204
+ "three": ["three.js", "threejs", "mrdoob/three.js"],
205
+ "r3f": ["react-three-fiber", "@react-three/fiber"],
206
+ }
207
+
208
+ for key, values in mappings.items():
209
+ if key in query_lower:
210
+ suggestions.extend(values)
211
+ break
212
+
213
+ if not suggestions:
214
+ suggestions = [
215
+ "Try the exact package name from npm/pypi",
216
+ "Check for common aliases (e.g., 'nextjs' for Next.js)",
217
+ ]
218
+
219
+ return suggestions[:3]
@@ -0,0 +1,145 @@
1
+ """Perplexity API client for deep research."""
2
+
3
+ import re
4
+
5
+ import httpx
6
+
7
+ from ms_lookup.config import PERPLEXITY_API_KEY, PERPLEXITY_BASE_URL, PERPLEXITY_DEEP_MODEL
8
+ from ms_lookup.errors import ErrorCode, MsLookupError
9
+
10
+
11
+ class PerplexityClient:
12
+ """Client for Perplexity deep research API."""
13
+
14
+ def __init__(self, api_key: str | None = None):
15
+ self.api_key = api_key or PERPLEXITY_API_KEY
16
+ self.base_url = PERPLEXITY_BASE_URL
17
+
18
+ def _check_api_key(self) -> None:
19
+ """Verify API key is configured."""
20
+ if not self.api_key:
21
+ raise MsLookupError(
22
+ ErrorCode.MISSING_API_KEY,
23
+ "PERPLEXITY_API_KEY environment variable not set",
24
+ suggestions=[
25
+ "Set PERPLEXITY_API_KEY in your environment",
26
+ "Get an API key at https://docs.perplexity.ai/",
27
+ ],
28
+ )
29
+
30
+ def query(self, query: str) -> dict:
31
+ """Perform deep research query.
32
+
33
+ Args:
34
+ query: Research query
35
+
36
+ Returns:
37
+ Dict with research response
38
+
39
+ Raises:
40
+ MsLookupError: If API error
41
+ """
42
+ self._check_api_key()
43
+
44
+ url = f"{self.base_url}/chat/completions"
45
+ headers = {
46
+ "Authorization": f"Bearer {self.api_key}",
47
+ "Content-Type": "application/json",
48
+ }
49
+ payload = {
50
+ "model": PERPLEXITY_DEEP_MODEL,
51
+ "messages": [
52
+ {
53
+ "role": "system",
54
+ "content": (
55
+ "You are a technical research assistant. Think step-by-step to analyze the query, "
56
+ "search for authoritative sources, and synthesize actionable findings.\n\n"
57
+ "RESEARCH APPROACH:\n"
58
+ "1. Identify the core technical question\n"
59
+ "2. Search for current best practices from official docs and trusted sources\n"
60
+ "3. Verify claims across multiple sources when possible\n"
61
+ "4. Synthesize into practical guidance\n\n"
62
+ "OUTPUT FORMAT:\n"
63
+ "- Lead with the recommended approach or answer\n"
64
+ "- Support with 8-12 key findings as bullet points\n"
65
+ "- Each finding cites its source inline [Source]\n"
66
+ "- Include code examples when relevant\n"
67
+ "- Flag any caveats or edge cases\n"
68
+ "- End with clear next steps if applicable\n"
69
+ ),
70
+ },
71
+ {
72
+ "role": "user",
73
+ "content": query,
74
+ },
75
+ ],
76
+ }
77
+
78
+ try:
79
+ with httpx.Client(timeout=90.0) as client: # Reasoning model is faster
80
+ response = client.post(url, headers=headers, json=payload)
81
+ response.raise_for_status()
82
+ data = response.json()
83
+ except httpx.HTTPStatusError as e:
84
+ if e.response.status_code == 401:
85
+ raise MsLookupError(
86
+ ErrorCode.MISSING_API_KEY,
87
+ "Invalid PERPLEXITY_API_KEY",
88
+ suggestions=["Check your API key at https://docs.perplexity.ai/"],
89
+ )
90
+ elif e.response.status_code == 429:
91
+ raise MsLookupError(
92
+ ErrorCode.RATE_LIMITED,
93
+ "Perplexity API rate limit exceeded",
94
+ suggestions=["Wait a moment and try again"],
95
+ )
96
+ raise MsLookupError(
97
+ ErrorCode.API_ERROR,
98
+ f"Perplexity API error: {e.response.status_code}",
99
+ )
100
+ except httpx.RequestError as e:
101
+ raise MsLookupError(
102
+ ErrorCode.NETWORK_ERROR,
103
+ f"Network error connecting to Perplexity: {str(e)}",
104
+ )
105
+
106
+ return data
107
+
108
+ def _strip_think_tags(self, content: str) -> str:
109
+ """Remove <think>...</think> blocks from response."""
110
+ # Remove think tags and their content (including multiline)
111
+ return re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL).strip()
112
+
113
+ def format_results(self, raw_response: dict) -> tuple[list[dict], list[str]]:
114
+ """Transform Perplexity response to unified format.
115
+
116
+ Args:
117
+ raw_response: Raw API response
118
+
119
+ Returns:
120
+ Tuple of (results list, citations list)
121
+ """
122
+ results = []
123
+ citations = []
124
+
125
+ # Extract content from response
126
+ choices = raw_response.get("choices", [])
127
+ if choices:
128
+ message = choices[0].get("message", {})
129
+ content = message.get("content", "")
130
+
131
+ # Strip think tags
132
+ content = self._strip_think_tags(content)
133
+
134
+ if content:
135
+ results.append({
136
+ "title": "Research Findings",
137
+ "content": content,
138
+ "source_url": "",
139
+ "type": "research",
140
+ })
141
+
142
+ # Extract citations if available
143
+ citations = raw_response.get("citations", [])
144
+
145
+ return results, citations