mindsystem-cc 3.17.1 → 3.18.1

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 (37) hide show
  1. package/agents/ms-consolidator.md +4 -4
  2. package/agents/ms-debugger.md +3 -3
  3. package/agents/ms-designer.md +33 -70
  4. package/agents/ms-executor.md +7 -6
  5. package/agents/ms-plan-writer.md +52 -26
  6. package/agents/ms-researcher.md +13 -13
  7. package/commands/ms/check-phase.md +1 -1
  8. package/commands/ms/complete-milestone.md +47 -54
  9. package/commands/ms/design-phase.md +33 -30
  10. package/commands/ms/review-design.md +106 -395
  11. package/mindsystem/references/principles.md +3 -3
  12. package/mindsystem/references/routing/next-phase-routing.md +1 -1
  13. package/mindsystem/references/scope-estimation.md +22 -35
  14. package/mindsystem/templates/design-iteration.md +13 -13
  15. package/mindsystem/templates/design.md +145 -327
  16. package/mindsystem/templates/knowledge.md +1 -1
  17. package/mindsystem/templates/milestone-archive.md +3 -3
  18. package/mindsystem/templates/phase-prompt.md +6 -7
  19. package/mindsystem/templates/research-subagent-prompt.md +2 -2
  20. package/mindsystem/templates/research.md +7 -7
  21. package/mindsystem/templates/roadmap.md +1 -1
  22. package/mindsystem/templates/verification-report.md +1 -1
  23. package/mindsystem/workflows/complete-milestone.md +52 -227
  24. package/mindsystem/workflows/discuss-phase.md +3 -3
  25. package/mindsystem/workflows/execute-plan.md +1 -1
  26. package/mindsystem/workflows/plan-phase.md +22 -50
  27. package/mindsystem/workflows/verify-phase.md +1 -1
  28. package/package.json +1 -1
  29. package/scripts/archive-milestone-files.sh +68 -0
  30. package/scripts/archive-milestone-phases.sh +138 -0
  31. package/scripts/gather-milestone-stats.sh +179 -0
  32. package/scripts/ms-lookup/ms_lookup/backends/context7.py +17 -5
  33. package/scripts/ms-lookup/ms_lookup/backends/perplexity.py +17 -3
  34. package/scripts/ms-lookup-wrapper.sh +1 -1
  35. package/scripts/scan-planning-context.py +186 -36
  36. package/scripts/validate-execution-order.sh +4 -5
  37. package/scripts/cleanup-phase-artifacts.sh +0 -68
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ #
3
+ # archive-milestone-files.sh
4
+ # Moves optional milestone files (audit, context, research) to the
5
+ # milestone archive directory. Skips silently if files don't exist.
6
+ #
7
+ # Usage: ./scripts/archive-milestone-files.sh <version>
8
+ # Example: ./scripts/archive-milestone-files.sh v1.0
9
+
10
+ set -e
11
+
12
+ # --- Validation ---
13
+ if [ -z "$1" ]; then
14
+ echo "Error: Version argument required"
15
+ echo "Usage: $0 <version>"
16
+ exit 1
17
+ fi
18
+
19
+ VERSION="$1"
20
+
21
+ # --- Find .planning from git root ---
22
+ GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
23
+ if [ -z "$GIT_ROOT" ]; then
24
+ echo "Error: Not in a git repository"
25
+ exit 1
26
+ fi
27
+
28
+ PLANNING_DIR="$GIT_ROOT/.planning"
29
+ MILESTONE_DIR="$PLANNING_DIR/milestones/$VERSION"
30
+
31
+ if [ ! -d "$MILESTONE_DIR" ]; then
32
+ echo "Error: Milestone directory not found at $MILESTONE_DIR"
33
+ echo "Run archive_milestone step first to create it"
34
+ exit 1
35
+ fi
36
+
37
+ # --- Move files ---
38
+ ARCHIVED=0
39
+
40
+ # Milestone audit
41
+ if [ -f "$PLANNING_DIR/${VERSION}-MILESTONE-AUDIT.md" ]; then
42
+ mv "$PLANNING_DIR/${VERSION}-MILESTONE-AUDIT.md" "$MILESTONE_DIR/MILESTONE-AUDIT.md"
43
+ echo "Archived: ${VERSION}-MILESTONE-AUDIT.md → MILESTONE-AUDIT.md"
44
+ ARCHIVED=$((ARCHIVED + 1))
45
+ fi
46
+
47
+ # Milestone context
48
+ if [ -f "$PLANNING_DIR/MILESTONE-CONTEXT.md" ]; then
49
+ mv "$PLANNING_DIR/MILESTONE-CONTEXT.md" "$MILESTONE_DIR/CONTEXT.md"
50
+ echo "Archived: MILESTONE-CONTEXT.md → CONTEXT.md"
51
+ ARCHIVED=$((ARCHIVED + 1))
52
+ fi
53
+
54
+ # Research directory
55
+ if [ -d "$PLANNING_DIR/research" ]; then
56
+ mv "$PLANNING_DIR/research" "$MILESTONE_DIR/research"
57
+ echo "Archived: research/ → research/"
58
+ ARCHIVED=$((ARCHIVED + 1))
59
+ fi
60
+
61
+ if [ "$ARCHIVED" -eq 0 ]; then
62
+ echo "No optional files to archive (audit, context, research all absent)"
63
+ else
64
+ echo ""
65
+ echo "Archived $ARCHIVED item(s) to milestones/$VERSION/"
66
+ fi
67
+
68
+ exit 0
@@ -0,0 +1,138 @@
1
+ #!/bin/bash
2
+ #
3
+ # archive-milestone-phases.sh
4
+ # Consolidates phase summaries, deletes raw artifacts, and moves phase
5
+ # directories to the milestone archive.
6
+ #
7
+ # Usage: ./scripts/archive-milestone-phases.sh <start_phase> <end_phase> <version>
8
+ # Example: ./scripts/archive-milestone-phases.sh 1 6 v1.0
9
+
10
+ set -e
11
+
12
+ # --- Validation ---
13
+ if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
14
+ echo "Error: Three arguments required"
15
+ echo "Usage: $0 <start_phase> <end_phase> <version>"
16
+ exit 1
17
+ fi
18
+
19
+ START="$1"
20
+ END="$2"
21
+ VERSION="$3"
22
+
23
+ if ! [[ "$START" =~ ^[0-9]+$ ]] || ! [[ "$END" =~ ^[0-9]+$ ]]; then
24
+ echo "Error: start_phase and end_phase must be numeric"
25
+ echo "Usage: $0 <start_phase> <end_phase> <version>"
26
+ exit 1
27
+ fi
28
+
29
+ if [ "$START" -gt "$END" ]; then
30
+ echo "Error: Start phase ($START) cannot exceed end phase ($END)"
31
+ exit 1
32
+ fi
33
+
34
+ # --- Find .planning from git root ---
35
+ GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
36
+ if [ -z "$GIT_ROOT" ]; then
37
+ echo "Error: Not in a git repository"
38
+ exit 1
39
+ fi
40
+
41
+ PHASES_DIR="$GIT_ROOT/.planning/phases"
42
+ if [ ! -d "$PHASES_DIR" ]; then
43
+ echo "Error: Phases directory not found at $PHASES_DIR"
44
+ exit 1
45
+ fi
46
+
47
+ MILESTONE_DIR="$GIT_ROOT/.planning/milestones/$VERSION"
48
+ if [ ! -d "$MILESTONE_DIR" ]; then
49
+ echo "Error: Milestone directory not found at $MILESTONE_DIR"
50
+ echo "Run archive_milestone step first to create it"
51
+ exit 1
52
+ fi
53
+
54
+ # --- Helper: check if phase number is in range (supports decimals like 02.1) ---
55
+ in_range() {
56
+ local phase_num="$1"
57
+ echo "$phase_num" | awk -v s="$START" -v e="$END" '{
58
+ # Strip leading zeros for comparison
59
+ val = $1 + 0
60
+ if (val >= s && val <= e + 0.999) exit 0
61
+ else exit 1
62
+ }'
63
+ }
64
+
65
+ # --- Stage 1: Consolidate summaries ---
66
+ SUMMARIES_FILE="$MILESTONE_DIR/PHASE-SUMMARIES.md"
67
+ SUMMARY_COUNT=0
68
+
69
+ echo "# Phase Summaries: $VERSION" > "$SUMMARIES_FILE"
70
+ echo "" >> "$SUMMARIES_FILE"
71
+
72
+ for dir in "$PHASES_DIR"/*/; do
73
+ [ -d "$dir" ] || continue
74
+ dirname=$(basename "$dir")
75
+ phase_num="${dirname%%-*}"
76
+ phase_name="${dirname#*-}"
77
+
78
+ if in_range "$phase_num"; then
79
+ has_summaries=false
80
+ for f in "$dir"/*-SUMMARY.md; do
81
+ [ -f "$f" ] || continue
82
+ if [ "$has_summaries" = false ]; then
83
+ echo "## Phase $phase_num: $phase_name" >> "$SUMMARIES_FILE"
84
+ echo "" >> "$SUMMARIES_FILE"
85
+ has_summaries=true
86
+ fi
87
+ plan_file=$(basename "$f")
88
+ plan_id="${plan_file%-SUMMARY.md}"
89
+ echo "### $plan_id" >> "$SUMMARIES_FILE"
90
+ echo "" >> "$SUMMARIES_FILE"
91
+ cat "$f" >> "$SUMMARIES_FILE"
92
+ echo "" >> "$SUMMARIES_FILE"
93
+ SUMMARY_COUNT=$((SUMMARY_COUNT + 1))
94
+ done
95
+ fi
96
+ done
97
+
98
+ echo "Stage 1: Consolidated $SUMMARY_COUNT summaries to PHASE-SUMMARIES.md"
99
+
100
+ # --- Stage 2: Delete artifacts ---
101
+ DELETED=0
102
+ for dir in "$PHASES_DIR"/*/; do
103
+ [ -d "$dir" ] || continue
104
+ dirname=$(basename "$dir")
105
+ phase_num="${dirname%%-*}"
106
+
107
+ if in_range "$phase_num"; then
108
+ for f in "$dir"/*-CONTEXT.md "$dir"/*-DESIGN.md "$dir"/*-RESEARCH.md \
109
+ "$dir"/*-SUMMARY.md "$dir"/*-UAT.md "$dir"/*-VERIFICATION.md \
110
+ "$dir"/*-EXECUTION-ORDER.md; do
111
+ if [ -f "$f" ]; then
112
+ rm -f "$f"
113
+ DELETED=$((DELETED + 1))
114
+ fi
115
+ done
116
+ fi
117
+ done
118
+
119
+ echo "Stage 2: Deleted $DELETED artifact files"
120
+
121
+ # --- Stage 3: Move phase directories ---
122
+ mkdir -p "$MILESTONE_DIR/phases"
123
+ MOVED=0
124
+ for dir in "$PHASES_DIR"/*/; do
125
+ [ -d "$dir" ] || continue
126
+ dirname=$(basename "$dir")
127
+ phase_num="${dirname%%-*}"
128
+
129
+ if in_range "$phase_num"; then
130
+ mv "$dir" "$MILESTONE_DIR/phases/$dirname"
131
+ MOVED=$((MOVED + 1))
132
+ fi
133
+ done
134
+
135
+ echo "Stage 3: Moved $MOVED phase directories to milestones/$VERSION/phases/"
136
+ echo ""
137
+ echo "Archive complete: $SUMMARY_COUNT summaries, $DELETED artifacts deleted, $MOVED dirs moved"
138
+ exit 0
@@ -0,0 +1,179 @@
1
+ #!/bin/bash
2
+ #
3
+ # gather-milestone-stats.sh
4
+ # Gathers milestone readiness status and statistics from the filesystem
5
+ # and git history. Outputs structured text for the LLM to present.
6
+ #
7
+ # Usage: ./scripts/gather-milestone-stats.sh <start_phase> <end_phase>
8
+ # Example: ./scripts/gather-milestone-stats.sh 1 6
9
+
10
+ set -e
11
+
12
+ # --- Validation ---
13
+ if [ -z "$1" ] || [ -z "$2" ]; then
14
+ echo "Error: Two arguments required"
15
+ echo "Usage: $0 <start_phase> <end_phase>"
16
+ exit 1
17
+ fi
18
+
19
+ START="$1"
20
+ END="$2"
21
+
22
+ if ! [[ "$START" =~ ^[0-9]+$ ]] || ! [[ "$END" =~ ^[0-9]+$ ]]; then
23
+ echo "Error: Both arguments must be numeric"
24
+ echo "Usage: $0 <start_phase> <end_phase>"
25
+ exit 1
26
+ fi
27
+
28
+ if [ "$START" -gt "$END" ]; then
29
+ echo "Error: Start phase ($START) cannot exceed end phase ($END)"
30
+ exit 1
31
+ fi
32
+
33
+ # --- Find .planning from git root ---
34
+ GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
35
+ if [ -z "$GIT_ROOT" ]; then
36
+ echo "Error: Not in a git repository"
37
+ exit 1
38
+ fi
39
+
40
+ PHASES_DIR="$GIT_ROOT/.planning/phases"
41
+ if [ ! -d "$PHASES_DIR" ]; then
42
+ echo "Error: Phases directory not found at $PHASES_DIR"
43
+ exit 1
44
+ fi
45
+
46
+ # --- Helper: check if phase number is in range (supports decimals like 02.1) ---
47
+ in_range() {
48
+ local phase_num="$1"
49
+ echo "$phase_num" | awk -v s="$START" -v e="$END" '{
50
+ val = $1 + 0
51
+ if (val >= s && val <= e + 0.999) exit 0
52
+ else exit 1
53
+ }'
54
+ }
55
+
56
+ # ============================================================
57
+ # READINESS
58
+ # ============================================================
59
+ echo "=== Readiness ==="
60
+ echo ""
61
+
62
+ PHASE_COUNT=0
63
+ PLAN_COUNT=0
64
+ COMPLETE=0
65
+ INCOMPLETE_LIST=""
66
+ PHASE_DETAILS=""
67
+
68
+ for dir in "$PHASES_DIR"/*/; do
69
+ [ -d "$dir" ] || continue
70
+ dirname=$(basename "$dir")
71
+ phase_num="${dirname%%-*}"
72
+ phase_name="${dirname#*-}"
73
+
74
+ if in_range "$phase_num"; then
75
+ PHASE_COUNT=$((PHASE_COUNT + 1))
76
+ phase_plans=0
77
+ phase_complete=0
78
+
79
+ for plan in "$dir"/*-PLAN.md; do
80
+ [ -f "$plan" ] || continue
81
+ PLAN_COUNT=$((PLAN_COUNT + 1))
82
+ phase_plans=$((phase_plans + 1))
83
+ plan_base=$(basename "$plan" -PLAN.md)
84
+ summary="$dir/${plan_base}-SUMMARY.md"
85
+ if [ -f "$summary" ]; then
86
+ COMPLETE=$((COMPLETE + 1))
87
+ phase_complete=$((phase_complete + 1))
88
+ else
89
+ INCOMPLETE_LIST+=" $(basename "$dir")/$(basename "$plan")"$'\n'
90
+ fi
91
+ done
92
+
93
+ PHASE_DETAILS+="- Phase $phase_num: $phase_name ($phase_complete/$phase_plans plans)"$'\n'
94
+ fi
95
+ done
96
+
97
+ echo "Phases: $PHASE_COUNT (range $START-$END)"
98
+ echo "Plans: $PLAN_COUNT total, $COMPLETE complete"
99
+ echo ""
100
+ echo "$PHASE_DETAILS"
101
+
102
+ if [ "$COMPLETE" -eq "$PLAN_COUNT" ] && [ "$PLAN_COUNT" -gt 0 ]; then
103
+ echo "Status: READY"
104
+ else
105
+ INCOMPLETE=$((PLAN_COUNT - COMPLETE))
106
+ echo "Incomplete ($INCOMPLETE):"
107
+ echo "$INCOMPLETE_LIST"
108
+ echo "Status: NOT READY"
109
+ fi
110
+
111
+ # ============================================================
112
+ # GIT STATS
113
+ # ============================================================
114
+ echo ""
115
+ echo "=== Git Stats ==="
116
+ echo ""
117
+
118
+ # Collect commits matching Mindsystem phase convention: feat(XX-YY), fix(XX-YY), etc.
119
+ ALL_COMMITS=""
120
+ for i in $(seq "$START" "$END"); do
121
+ phase=$(printf "%02d" "$i")
122
+ commits=$(git log --all --format="%H %ai %s" --grep="($phase-" 2>/dev/null || true)
123
+ if [ -n "$commits" ]; then
124
+ ALL_COMMITS+="$commits"$'\n'
125
+ fi
126
+ done
127
+
128
+ # Also capture decimal phase commits (e.g., 02.1 inserted phases)
129
+ for dir in "$PHASES_DIR"/*/; do
130
+ [ -d "$dir" ] || continue
131
+ dirname=$(basename "$dir")
132
+ phase_num="${dirname%%-*}"
133
+ case "$phase_num" in
134
+ *.*) # Decimal phase — not captured by seq
135
+ if in_range "$phase_num"; then
136
+ commits=$(git log --all --format="%H %ai %s" --grep="($phase_num-" 2>/dev/null || true)
137
+ if [ -n "$commits" ]; then
138
+ ALL_COMMITS+="$commits"$'\n'
139
+ fi
140
+ fi
141
+ ;;
142
+ esac
143
+ done
144
+
145
+ # Remove empty lines, deduplicate, sort by date
146
+ ALL_COMMITS=$(echo "$ALL_COMMITS" | grep -v '^$' | sort -u -k2,3)
147
+
148
+ if [ -n "$ALL_COMMITS" ]; then
149
+ COMMIT_COUNT=$(echo "$ALL_COMMITS" | wc -l | tr -d ' ')
150
+ FIRST_LINE=$(echo "$ALL_COMMITS" | head -1)
151
+ LAST_LINE=$(echo "$ALL_COMMITS" | tail -1)
152
+ FIRST_HASH=$(echo "$FIRST_LINE" | awk '{print $1}')
153
+ LAST_HASH=$(echo "$LAST_LINE" | awk '{print $1}')
154
+ FIRST_DATE=$(echo "$FIRST_LINE" | awk '{print $2}')
155
+ LAST_DATE=$(echo "$LAST_LINE" | awk '{print $2}')
156
+ FIRST_MSG=$(echo "$FIRST_LINE" | cut -d' ' -f4-)
157
+ LAST_MSG=$(echo "$LAST_LINE" | cut -d' ' -f4-)
158
+
159
+ # Calculate days
160
+ DAYS=$(python3 -c "from datetime import date; print((date.fromisoformat('$LAST_DATE') - date.fromisoformat('$FIRST_DATE')).days)" 2>/dev/null || echo "?")
161
+
162
+ echo "Commits: $COMMIT_COUNT"
163
+ echo "Git range: ${FIRST_HASH:0:7}..${LAST_HASH:0:7}"
164
+ echo "First: $FIRST_DATE — $FIRST_MSG"
165
+ echo "Last: $LAST_DATE — $LAST_MSG"
166
+ echo "Timeline: $DAYS days ($FIRST_DATE → $LAST_DATE)"
167
+
168
+ # Diff stats for the range
169
+ DIFFSTAT=$(git diff --shortstat "${FIRST_HASH}^..${LAST_HASH}" 2>/dev/null || true)
170
+ if [ -n "$DIFFSTAT" ]; then
171
+ echo "Changes:$DIFFSTAT"
172
+ fi
173
+ else
174
+ echo "No commits found matching phase patterns (expected 'feat(XX-YY): ...')"
175
+ echo "Determine git range manually from git log"
176
+ fi
177
+
178
+ echo ""
179
+ exit 0
@@ -25,6 +25,16 @@ class Context7Client:
25
25
  ],
26
26
  )
27
27
 
28
+ @staticmethod
29
+ def _extract_error_body(response: httpx.Response) -> str:
30
+ """Extract human-readable error message from response."""
31
+ try:
32
+ data = response.json()
33
+ # Context7 style: {"error": "code", "message": "..."}
34
+ return data.get("message", data.get("error", response.text[:200]))
35
+ except Exception:
36
+ return response.text[:200]
37
+
28
38
  def resolve_library(self, library_name: str, query: str) -> dict:
29
39
  """Resolve library name to Context7 library ID.
30
40
 
@@ -50,21 +60,22 @@ class Context7Client:
50
60
  response.raise_for_status()
51
61
  data = response.json()
52
62
  except httpx.HTTPStatusError as e:
63
+ body = self._extract_error_body(e.response)
53
64
  if e.response.status_code == 401:
54
65
  raise MsLookupError(
55
66
  ErrorCode.MISSING_API_KEY,
56
- "Invalid CONTEXT7_API_KEY",
67
+ f"Invalid CONTEXT7_API_KEY — {body}",
57
68
  suggestions=["Check your API key at https://context7.com/dashboard"],
58
69
  )
59
70
  elif e.response.status_code == 429:
60
71
  raise MsLookupError(
61
72
  ErrorCode.RATE_LIMITED,
62
- "Context7 API rate limit exceeded",
73
+ f"Context7 API rate limited — {body}",
63
74
  suggestions=["Wait a moment and try again", "Use --no-cache sparingly"],
64
75
  )
65
76
  raise MsLookupError(
66
77
  ErrorCode.API_ERROR,
67
- f"Context7 API error: {e.response.status_code}",
78
+ f"Context7 API error ({e.response.status_code}): {body}",
68
79
  )
69
80
  except httpx.RequestError as e:
70
81
  raise MsLookupError(
@@ -122,6 +133,7 @@ class Context7Client:
122
133
  # Plain text/markdown response - wrap in a structure
123
134
  data = {"content": response.text, "format": "markdown"}
124
135
  except httpx.HTTPStatusError as e:
136
+ body = self._extract_error_body(e.response)
125
137
  if e.response.status_code == 404:
126
138
  raise MsLookupError(
127
139
  ErrorCode.LIBRARY_NOT_FOUND,
@@ -130,11 +142,11 @@ class Context7Client:
130
142
  elif e.response.status_code == 429:
131
143
  raise MsLookupError(
132
144
  ErrorCode.RATE_LIMITED,
133
- "Context7 API rate limit exceeded",
145
+ f"Context7 API rate limited — {body}",
134
146
  )
135
147
  raise MsLookupError(
136
148
  ErrorCode.API_ERROR,
137
- f"Context7 API error: {e.response.status_code}",
149
+ f"Context7 API error ({e.response.status_code}): {body}",
138
150
  )
139
151
  except httpx.RequestError as e:
140
152
  raise MsLookupError(
@@ -81,21 +81,22 @@ class PerplexityClient:
81
81
  response.raise_for_status()
82
82
  data = response.json()
83
83
  except httpx.HTTPStatusError as e:
84
+ body = self._extract_error_body(e.response)
84
85
  if e.response.status_code == 401:
85
86
  raise MsLookupError(
86
87
  ErrorCode.MISSING_API_KEY,
87
- "Invalid PERPLEXITY_API_KEY",
88
+ f"Invalid PERPLEXITY_API_KEY — {body}",
88
89
  suggestions=["Check your API key at https://docs.perplexity.ai/"],
89
90
  )
90
91
  elif e.response.status_code == 429:
91
92
  raise MsLookupError(
92
93
  ErrorCode.RATE_LIMITED,
93
- "Perplexity API rate limit exceeded",
94
+ f"Perplexity API rate limited — {body}",
94
95
  suggestions=["Wait a moment and try again"],
95
96
  )
96
97
  raise MsLookupError(
97
98
  ErrorCode.API_ERROR,
98
- f"Perplexity API error: {e.response.status_code}",
99
+ f"Perplexity API error ({e.response.status_code}): {body}",
99
100
  )
100
101
  except httpx.RequestError as e:
101
102
  raise MsLookupError(
@@ -105,6 +106,19 @@ class PerplexityClient:
105
106
 
106
107
  return data
107
108
 
109
+ @staticmethod
110
+ def _extract_error_body(response: httpx.Response) -> str:
111
+ """Extract human-readable error message from response."""
112
+ try:
113
+ data = response.json()
114
+ # OpenAI-style: {"error": {"message": "..."}}
115
+ if isinstance(data.get("error"), dict):
116
+ return data["error"].get("message", str(data["error"]))
117
+ # Simple: {"error": "...", "message": "..."}
118
+ return data.get("message", data.get("error", response.text[:200]))
119
+ except Exception:
120
+ return response.text[:200]
121
+
108
122
  def _strip_think_tags(self, content: str) -> str:
109
123
  """Remove <think>...</think> blocks from response."""
110
124
  # Remove think tags and their content (including multiline)
@@ -10,7 +10,7 @@ fi
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  cd "$SCRIPT_DIR/ms-lookup"
12
12
 
13
- if command -v uv &> /dev/null; then
13
+ if command -v uv > /dev/null 2>&1; then
14
14
  # Prefer uv if available (faster)
15
15
  uv sync --quiet 2>/dev/null
16
16
  uv run python -m ms_lookup "$@"