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.
- package/LICENSE +21 -0
- package/README.md +501 -0
- package/agents/ms-codebase-mapper.md +739 -0
- package/agents/ms-debugger.md +1184 -0
- package/agents/ms-designer.md +414 -0
- package/agents/ms-executor.md +760 -0
- package/agents/ms-integration-checker.md +423 -0
- package/agents/ms-milestone-auditor.md +448 -0
- package/agents/ms-mock-generator.md +182 -0
- package/agents/ms-plan-checker.md +746 -0
- package/agents/ms-research-synthesizer.md +248 -0
- package/agents/ms-researcher.md +962 -0
- package/agents/ms-roadmapper.md +606 -0
- package/agents/ms-verifier.md +779 -0
- package/agents/ms-verify-fixer.md +124 -0
- package/bin/install.js +296 -0
- package/commands/ms/add-phase.md +207 -0
- package/commands/ms/add-todo.md +182 -0
- package/commands/ms/audit-milestone.md +318 -0
- package/commands/ms/check-phase.md +162 -0
- package/commands/ms/check-todos.md +217 -0
- package/commands/ms/complete-milestone.md +137 -0
- package/commands/ms/create-roadmap.md +273 -0
- package/commands/ms/debug.md +149 -0
- package/commands/ms/define-requirements.md +121 -0
- package/commands/ms/design-phase.md +341 -0
- package/commands/ms/discuss-milestone.md +48 -0
- package/commands/ms/discuss-phase.md +60 -0
- package/commands/ms/do-work.md +90 -0
- package/commands/ms/execute-phase.md +289 -0
- package/commands/ms/help.md +623 -0
- package/commands/ms/insert-phase.md +227 -0
- package/commands/ms/list-phase-assumptions.md +50 -0
- package/commands/ms/map-codebase.md +71 -0
- package/commands/ms/new-milestone.md +193 -0
- package/commands/ms/new-project.md +338 -0
- package/commands/ms/pause-work.md +123 -0
- package/commands/ms/plan-milestone-gaps.md +285 -0
- package/commands/ms/plan-phase.md +105 -0
- package/commands/ms/progress.md +370 -0
- package/commands/ms/remove-phase.md +338 -0
- package/commands/ms/research-phase.md +175 -0
- package/commands/ms/research-project.md +339 -0
- package/commands/ms/resume-work.md +40 -0
- package/commands/ms/review-design.md +484 -0
- package/commands/ms/simplify-flutter.md +193 -0
- package/commands/ms/update.md +159 -0
- package/commands/ms/verify-work.md +92 -0
- package/commands/ms/whats-new.md +124 -0
- package/mindsystem/references/checkpoints.md +788 -0
- package/mindsystem/references/continuation-format.md +255 -0
- package/mindsystem/references/debugging/debugging-mindset.md +11 -0
- package/mindsystem/references/debugging/hypothesis-testing.md +11 -0
- package/mindsystem/references/debugging/investigation-techniques.md +11 -0
- package/mindsystem/references/debugging/verification-patterns.md +11 -0
- package/mindsystem/references/debugging/when-to-research.md +11 -0
- package/mindsystem/references/git-integration.md +254 -0
- package/mindsystem/references/goal-backward.md +286 -0
- package/mindsystem/references/mock-patterns.md +294 -0
- package/mindsystem/references/plan-format.md +473 -0
- package/mindsystem/references/principles.md +73 -0
- package/mindsystem/references/questioning.md +140 -0
- package/mindsystem/references/research-pitfalls.md +233 -0
- package/mindsystem/references/scope-estimation.md +256 -0
- package/mindsystem/references/tdd.md +263 -0
- package/mindsystem/references/verification-patterns.md +595 -0
- package/mindsystem/templates/DEBUG.md +159 -0
- package/mindsystem/templates/UAT.md +403 -0
- package/mindsystem/templates/adhoc-summary.md +153 -0
- package/mindsystem/templates/codebase/architecture.md +255 -0
- package/mindsystem/templates/codebase/concerns.md +310 -0
- package/mindsystem/templates/codebase/conventions.md +307 -0
- package/mindsystem/templates/codebase/integrations.md +280 -0
- package/mindsystem/templates/codebase/stack.md +186 -0
- package/mindsystem/templates/codebase/structure.md +285 -0
- package/mindsystem/templates/codebase/testing.md +480 -0
- package/mindsystem/templates/config.json +26 -0
- package/mindsystem/templates/context.md +140 -0
- package/mindsystem/templates/continue-here.md +78 -0
- package/mindsystem/templates/debug-subagent-prompt.md +91 -0
- package/mindsystem/templates/design-iteration.md +208 -0
- package/mindsystem/templates/design.md +417 -0
- package/mindsystem/templates/discovery.md +146 -0
- package/mindsystem/templates/milestone-archive.md +123 -0
- package/mindsystem/templates/milestone-context.md +93 -0
- package/mindsystem/templates/milestone.md +115 -0
- package/mindsystem/templates/phase-prompt.md +574 -0
- package/mindsystem/templates/project.md +184 -0
- package/mindsystem/templates/requirements.md +231 -0
- package/mindsystem/templates/research-project/ARCHITECTURE.md +204 -0
- package/mindsystem/templates/research-project/FEATURES.md +147 -0
- package/mindsystem/templates/research-project/PITFALLS.md +200 -0
- package/mindsystem/templates/research-project/STACK.md +120 -0
- package/mindsystem/templates/research-project/SUMMARY.md +170 -0
- package/mindsystem/templates/research-subagent-prompt.md +92 -0
- package/mindsystem/templates/research.md +529 -0
- package/mindsystem/templates/roadmap.md +214 -0
- package/mindsystem/templates/state.md +224 -0
- package/mindsystem/templates/summary.md +269 -0
- package/mindsystem/templates/user-setup.md +323 -0
- package/mindsystem/templates/verification-report.md +322 -0
- package/mindsystem/workflows/complete-milestone.md +759 -0
- package/mindsystem/workflows/create-milestone.md +203 -0
- package/mindsystem/workflows/debug.md +14 -0
- package/mindsystem/workflows/define-requirements.md +330 -0
- package/mindsystem/workflows/diagnose-issues.md +241 -0
- package/mindsystem/workflows/discovery-phase.md +293 -0
- package/mindsystem/workflows/discuss-milestone.md +310 -0
- package/mindsystem/workflows/discuss-phase.md +237 -0
- package/mindsystem/workflows/do-work.md +359 -0
- package/mindsystem/workflows/execute-phase.md +644 -0
- package/mindsystem/workflows/execute-plan.md +1828 -0
- package/mindsystem/workflows/generate-mocks.md +187 -0
- package/mindsystem/workflows/list-phase-assumptions.md +178 -0
- package/mindsystem/workflows/map-codebase.md +289 -0
- package/mindsystem/workflows/plan-phase.md +876 -0
- package/mindsystem/workflows/research-phase.md +17 -0
- package/mindsystem/workflows/research-project.md +23 -0
- package/mindsystem/workflows/resume-project.md +311 -0
- package/mindsystem/workflows/transition.md +564 -0
- package/mindsystem/workflows/verify-phase.md +629 -0
- package/mindsystem/workflows/verify-work.md +823 -0
- package/package.json +32 -0
- package/scripts/generate-phase-patch.sh +169 -0
- package/scripts/ms-lookup/README.md +112 -0
- package/scripts/ms-lookup/ms_lookup/__init__.py +3 -0
- package/scripts/ms-lookup/ms_lookup/__main__.py +6 -0
- package/scripts/ms-lookup/ms_lookup/backends/__init__.py +6 -0
- package/scripts/ms-lookup/ms_lookup/backends/context7.py +219 -0
- package/scripts/ms-lookup/ms_lookup/backends/perplexity.py +145 -0
- package/scripts/ms-lookup/ms_lookup/cache.py +48 -0
- package/scripts/ms-lookup/ms_lookup/cli.py +219 -0
- package/scripts/ms-lookup/ms_lookup/config.py +23 -0
- package/scripts/ms-lookup/ms_lookup/errors.py +24 -0
- package/scripts/ms-lookup/ms_lookup/output.py +49 -0
- package/scripts/ms-lookup/ms_lookup/tokens.py +56 -0
- package/scripts/ms-lookup/pyproject.toml +17 -0
- package/scripts/ms-lookup/uv.lock +207 -0
- 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,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
|