@jaguilar87/gaia-ops 1.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 (91) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/CLAUDE.md +154 -0
  3. package/LICENSE +21 -0
  4. package/README.md +221 -0
  5. package/agents/aws-troubleshooter.md +50 -0
  6. package/agents/claude-architect.md +821 -0
  7. package/agents/devops-developer.md +92 -0
  8. package/agents/gcp-troubleshooter.md +50 -0
  9. package/agents/gitops-operator.md +360 -0
  10. package/agents/terraform-architect.md +289 -0
  11. package/bin/gaia-init.js +620 -0
  12. package/commands/architect.md +97 -0
  13. package/commands/restore-session.md +87 -0
  14. package/commands/save-session.md +88 -0
  15. package/commands/session-status.md +61 -0
  16. package/commands/speckit.add-task.md +144 -0
  17. package/commands/speckit.analyze-task.md +65 -0
  18. package/commands/speckit.implement.md +96 -0
  19. package/commands/speckit.init.md +237 -0
  20. package/commands/speckit.plan.md +88 -0
  21. package/commands/speckit.specify.md +161 -0
  22. package/commands/speckit.tasks.md +188 -0
  23. package/config/AGENTS.md +162 -0
  24. package/config/agent-catalog.md +604 -0
  25. package/config/context-contracts.md +682 -0
  26. package/config/git-standards.md +674 -0
  27. package/config/git_standards.json +69 -0
  28. package/config/orchestration-workflow.md +735 -0
  29. package/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
  30. package/hooks/__pycache__/pre_kubectl_security.cpython-312.pyc +0 -0
  31. package/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
  32. package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
  33. package/hooks/__pycache__/subagent_stop.cpython-312.pyc +0 -0
  34. package/hooks/post_tool_use.py +463 -0
  35. package/hooks/pre_kubectl_security.py +205 -0
  36. package/hooks/pre_tool_use.py +530 -0
  37. package/hooks/session_start.py +315 -0
  38. package/hooks/subagent_stop.py +549 -0
  39. package/index.js +92 -0
  40. package/package.json +59 -0
  41. package/speckit/README.en.md +648 -0
  42. package/speckit/README.md +353 -0
  43. package/speckit/governance.md +169 -0
  44. package/speckit/scripts/check-prerequisites.sh +194 -0
  45. package/speckit/scripts/common.sh +126 -0
  46. package/speckit/scripts/create-new-feature.sh +131 -0
  47. package/speckit/scripts/init.sh +42 -0
  48. package/speckit/scripts/setup-plan.sh +95 -0
  49. package/speckit/scripts/update-agent-context.sh +718 -0
  50. package/speckit/templates/adr-template.md +118 -0
  51. package/speckit/templates/agent-file-template.md +23 -0
  52. package/speckit/templates/plan-template.md +233 -0
  53. package/speckit/templates/spec-template.md +116 -0
  54. package/speckit/templates/tasks-template-bkp.md +136 -0
  55. package/speckit/templates/tasks-template.md +345 -0
  56. package/templates/CLAUDE.template.md +170 -0
  57. package/templates/code-examples/approval_gate_workflow.py +141 -0
  58. package/templates/code-examples/clarification_workflow.py +94 -0
  59. package/templates/code-examples/commit_validation.py +86 -0
  60. package/templates/project-context.template.json +126 -0
  61. package/templates/settings.template.json +307 -0
  62. package/tools/__pycache__/agent_router.cpython-312.pyc +0 -0
  63. package/tools/__pycache__/approval_gate.cpython-312.pyc +0 -0
  64. package/tools/__pycache__/clarify_engine.cpython-312.pyc +0 -0
  65. package/tools/__pycache__/clarify_patterns.cpython-312.pyc +0 -0
  66. package/tools/__pycache__/commit_validator.cpython-312.pyc +0 -0
  67. package/tools/__pycache__/context_section_reader.cpython-312.pyc +0 -0
  68. package/tools/__pycache__/routing_dashboard.cpython-312.pyc +0 -0
  69. package/tools/__pycache__/routing_feedback.cpython-312.pyc +0 -0
  70. package/tools/__pycache__/semantic_matcher.cpython-312.pyc +0 -0
  71. package/tools/__pycache__/task_manager.cpython-312.pyc +0 -0
  72. package/tools/agent_capabilities.json +231 -0
  73. package/tools/agent_invoker_helper.py +239 -0
  74. package/tools/agent_router.py +730 -0
  75. package/tools/approval_gate.py +318 -0
  76. package/tools/clarify_engine.py +511 -0
  77. package/tools/clarify_patterns.py +356 -0
  78. package/tools/commit_validator.py +338 -0
  79. package/tools/context_provider.py +181 -0
  80. package/tools/context_section_reader.py +301 -0
  81. package/tools/demo_clarify.py +104 -0
  82. package/tools/generate_embeddings.py +168 -0
  83. package/tools/quicktriage_aws_troubleshooter.sh +45 -0
  84. package/tools/quicktriage_devops_developer.sh +38 -0
  85. package/tools/quicktriage_gcp_troubleshooter.sh +51 -0
  86. package/tools/quicktriage_gitops_operator.sh +47 -0
  87. package/tools/quicktriage_terraform_architect.sh +40 -0
  88. package/tools/semantic_matcher.py +222 -0
  89. package/tools/task_manager.py +547 -0
  90. package/tools/task_manager_README.md +395 -0
  91. package/tools/task_manager_example.py +215 -0
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate intent embeddings offline using sentence-transformers
4
+
5
+ This script is run ONCE to generate pre-computed embeddings.
6
+ At runtime, we only need numpy for similarity calculations.
7
+
8
+ Usage:
9
+ python3 generate_embeddings.py
10
+
11
+ Output:
12
+ - .claude/configs/intent_embeddings.npy (binary, ~5MB)
13
+ - .claude/configs/intent_embeddings.json (readable metadata)
14
+ """
15
+
16
+ import json
17
+ from pathlib import Path
18
+ import sys
19
+
20
+ # Define intent examples (from agent_router.py IntentClassifier)
21
+ INTENT_EXAMPLES = {
22
+ "infrastructure_creation": [
23
+ "create gke cluster with autopilot",
24
+ "provision new vpc and subnets",
25
+ "deploy terraform infrastructure",
26
+ "setup kubernetes cluster",
27
+ "build cloud resources",
28
+ "create new database instance",
29
+ "provision redis cache",
30
+ "deploy load balancer"
31
+ ],
32
+ "infrastructure_diagnosis": [
33
+ "diagnose cluster connectivity",
34
+ "troubleshoot gke pod crashes",
35
+ "debug network latency problems",
36
+ "check kubernetes node status",
37
+ "analyze infrastructure errors",
38
+ "troubleshoot cloud sql connectivity",
39
+ "debug workload identity issues",
40
+ "diagnose firewall rule problems"
41
+ ],
42
+ "kubernetes_operations": [
43
+ "check pod status in namespace",
44
+ "view deployment logs",
45
+ "verify flux reconciliation",
46
+ "monitor helm release status",
47
+ "inspect kubernetes resources",
48
+ "check service endpoints",
49
+ "scale deployment replicas",
50
+ "update configmap values"
51
+ ],
52
+ "application_development": [
53
+ "build docker image for api",
54
+ "run unit tests for application",
55
+ "validate application configuration",
56
+ "compile typescript code",
57
+ "execute npm build command",
58
+ "lint code with eslint",
59
+ "run integration tests",
60
+ "package application for deployment"
61
+ ],
62
+ "infrastructure_validation": [
63
+ "validate terraform configuration",
64
+ "check hcl syntax errors",
65
+ "run terraform plan",
66
+ "scan infrastructure security",
67
+ "verify module dependencies",
68
+ "check terraform state integrity",
69
+ "scan for policy violations",
70
+ "validate cloudformation template"
71
+ ]
72
+ }
73
+
74
+
75
+ def generate_embeddings():
76
+ """Generate and save embeddings using sentence-transformers"""
77
+ try:
78
+ from sentence_transformers import SentenceTransformer
79
+ import numpy as np
80
+ except ImportError:
81
+ print("❌ Error: sentence-transformers not installed")
82
+ print(" Run: pip install sentence-transformers torch")
83
+ return False
84
+
85
+ print("\n" + "="*70)
86
+ print("🔧 Generating Intent Embeddings (Offline)")
87
+ print("="*70 + "\n")
88
+
89
+ # Load model
90
+ print("📥 Loading sentence-transformers model: all-MiniLM-L6-v2")
91
+ model = SentenceTransformer('all-MiniLM-L6-v2')
92
+ print(f" ✅ Model loaded (embedding dimension: 384)")
93
+
94
+ embeddings_data = {}
95
+ total_examples = 0
96
+
97
+ print("\n📊 Generating embeddings for intents:\n")
98
+
99
+ for intent_name, examples in INTENT_EXAMPLES.items():
100
+ print(f" 🎯 {intent_name}")
101
+
102
+ # Generate embeddings for all examples
103
+ embeddings = model.encode(examples, convert_to_numpy=True)
104
+ mean_embedding = embeddings.mean(axis=0)
105
+
106
+ # Store metadata and mean embedding
107
+ embeddings_data[intent_name] = {
108
+ "embedding": mean_embedding.tolist(), # Convert to list for JSON
109
+ "examples": examples,
110
+ "dimension": len(mean_embedding),
111
+ "count": len(examples)
112
+ }
113
+
114
+ print(f" ✅ {len(examples):2d} examples → 384-dim embedding")
115
+ total_examples += len(examples)
116
+
117
+ print(f"\n Total examples processed: {total_examples}")
118
+
119
+ # Save as JSON (readable metadata)
120
+ output_dir = Path(__file__).parent.parent / "configs"
121
+ output_dir.mkdir(parents=True, exist_ok=True)
122
+
123
+ json_path = output_dir / "intent_embeddings.json"
124
+ with open(json_path, 'w', encoding='utf-8') as f:
125
+ json.dump(embeddings_data, f, indent=2)
126
+
127
+ print(f"\n✅ Saved metadata: {json_path}")
128
+ print(f" Size: {json_path.stat().st_size / 1024:.1f} KB")
129
+
130
+ # Save as numpy array (binary, optimized)
131
+ npy_path = output_dir / "intent_embeddings.npy"
132
+ import numpy as np
133
+ np.save(npy_path, embeddings_data)
134
+
135
+ print(f"✅ Saved embeddings: {npy_path}")
136
+ print(f" Size: {npy_path.stat().st_size / (1024*1024):.1f} MB")
137
+
138
+ # Create runtime loader info
139
+ info_path = output_dir / "embeddings_info.json"
140
+ info = {
141
+ "model": "all-MiniLM-L6-v2",
142
+ "dimension": 384,
143
+ "intents": list(embeddings_data.keys()),
144
+ "total_examples": total_examples,
145
+ "timestamp": str(Path(__file__).stat().st_mtime),
146
+ "note": "Pre-computed offline. At runtime, load with numpy (no torch needed)."
147
+ }
148
+
149
+ with open(info_path, 'w') as f:
150
+ json.dump(info, f, indent=2)
151
+
152
+ print(f"✅ Saved info: {info_path}")
153
+
154
+ print("\n" + "="*70)
155
+ print("🎉 Embeddings Generated Successfully!")
156
+ print("="*70)
157
+ print("\nUsage:")
158
+ print(" from semantic_matcher import SemanticMatcher")
159
+ print(" matcher = SemanticMatcher() # Loads embeddings automatically")
160
+ print(" intent, conf = matcher.find_similar_intent(request, keyword_scores)")
161
+ print()
162
+
163
+ return True
164
+
165
+
166
+ if __name__ == "__main__":
167
+ success = generate_embeddings()
168
+ sys.exit(0 if success else 1)
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ # QuickTriage script for aws-troubleshooter
3
+ # Provides a minimal health snapshot for AWS EKS clusters and supporting services.
4
+
5
+ set -euo pipefail
6
+
7
+ REGION="${AWS_REGION:-${1:-us-east-1}}"
8
+ CLUSTER="${EKS_CLUSTER:-${2:-}}"
9
+ VPC_ID="${VPC_ID:-${3:-}}"
10
+
11
+ info() {
12
+ printf '[quicktriage] %s\n' "$*"
13
+ }
14
+
15
+ run_cmd() {
16
+ local description="$1"
17
+ shift
18
+
19
+ if ! command -v "$1" >/dev/null 2>&1; then
20
+ info "Skipping ${description} (command $1 not available)"
21
+ return
22
+ fi
23
+
24
+ info "$description"
25
+ "$@" || info "Command failed: $*"
26
+ }
27
+
28
+ info "Starting AWS quick triage (region=${REGION}, cluster=${CLUSTER:-unset})"
29
+
30
+ AWS_ARGS=(--region "$REGION")
31
+
32
+ if [[ -n "$CLUSTER" ]]; then
33
+ run_cmd "aws eks describe-cluster ${CLUSTER}" aws eks describe-cluster "${AWS_ARGS[@]}" --name "$CLUSTER" --query "cluster.{name:name,status:status,endpoint:endpoint,version:version}" --output table
34
+ run_cmd "kubectl get nodes (short)" kubectl get nodes -o wide
35
+ fi
36
+
37
+ run_cmd "aws elbv2 describe-target-health (summary)" aws elbv2 describe-target-health "${AWS_ARGS[@]}" --target-group-arn "${TARGET_GROUP_ARN:-}" || info "Set TARGET_GROUP_ARN to include ALB status in triage."
38
+
39
+ run_cmd "aws cloudwatch describe-alarms (ALARM state)" aws cloudwatch describe-alarms "${AWS_ARGS[@]}" --state-value ALARM --max-items 10 --query "MetricAlarms[*].{Name:AlarmName,State:StateValue}" --output table
40
+
41
+ if [[ -n "$VPC_ID" ]]; then
42
+ run_cmd "aws ec2 describe-vpc-endpoints ${VPC_ID}" aws ec2 describe-vpc-endpoints "${AWS_ARGS[@]}" --filters "Name=vpc-id,Values=${VPC_ID}" --query "VpcEndpoints[*].{ServiceName:ServiceName,State:State}" --output table
43
+ fi
44
+
45
+ info "Quick triage completed. Investigate failing components by describing pods/services or reviewing IAM/VPC configuration as next steps."
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # QuickTriage script for devops-developer
3
+ # Checks repository hygiene, linting, and test discoverability quickly.
4
+
5
+ set -euo pipefail
6
+
7
+ WORKDIR="${1:-.}"
8
+ LINT_CMD="${LINT_CMD:-npm run lint -- --max-warnings=0}"
9
+ TEST_DISCOVERY_CMD="${TEST_DISCOVERY_CMD:-npm run test -- --watchAll=false --listTests}"
10
+
11
+ info() {
12
+ printf '[quicktriage] %s\n' "$*"
13
+ }
14
+
15
+ run_in_repo() {
16
+ local description="$1"
17
+ shift
18
+
19
+ info "$description"
20
+ (cd "$WORKDIR" && eval "$*") || info "Command failed: $*"
21
+ }
22
+
23
+ info "Starting devops quick triage (workdir=${WORKDIR})"
24
+
25
+ run_in_repo "git status --short" "git status --short"
26
+
27
+ if command -v npm >/dev/null 2>&1 || command -v pnpm >/dev/null 2>&1; then
28
+ run_in_repo "Lint check" "$LINT_CMD"
29
+ run_in_repo "Test discovery" "$TEST_DISCOVERY_CMD"
30
+ else
31
+ info "Skipping lint/test (npm/pnpm not available)"
32
+ fi
33
+
34
+ if [ -f "${WORKDIR}/package.json" ]; then
35
+ run_in_repo "npm audit --production (summary)" "npm audit --production --json | jq '.metadata.vulnerabilities' || npm audit --production"
36
+ fi
37
+
38
+ info "Quick triage completed. Use full test runs or profiling if deeper analysis is required."
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # QuickTriage script for gcp-troubleshooter
3
+ # Provides a lightweight health snapshot for GKE clusters and key managed services.
4
+
5
+ set -euo pipefail
6
+
7
+ PROJECT="${GCP_PROJECT:-${1:-}}"
8
+ CLUSTER="${GKE_CLUSTER:-${2:-}}"
9
+ REGION="${GKE_REGION:-${3:-us-central1}}"
10
+ SQL_INSTANCE="${CLOUD_SQL_INSTANCE:-${4:-}}"
11
+
12
+ info() {
13
+ printf '[quicktriage] %s\n' "$*"
14
+ }
15
+
16
+ run_cmd() {
17
+ local description="$1"
18
+ shift
19
+
20
+ if ! command -v "$1" >/dev/null 2>&1; then
21
+ info "Skipping ${description} (command $1 not available)"
22
+ return
23
+ fi
24
+
25
+ info "$description"
26
+ "$@" || info "Command failed: $*"
27
+ }
28
+
29
+ info "Starting GCP quick triage (project=${PROJECT:-unset}, cluster=${CLUSTER:-unset})"
30
+
31
+ if [[ -n "$PROJECT" ]]; then
32
+ gcloud config set project "$PROJECT" >/dev/null 2>&1 || true
33
+ fi
34
+
35
+ if [[ -n "$CLUSTER" ]]; then
36
+ run_cmd "gcloud container clusters describe ${CLUSTER}" \
37
+ gcloud container clusters describe "$CLUSTER" --region "$REGION" --format="table(name,status,endpoint,releaseChannel.releaseChannel)"
38
+ fi
39
+
40
+ run_cmd "gcloud container clusters list (summary)" \
41
+ gcloud container clusters list --format="table(name,location,status,nodePools[0].status)"
42
+
43
+ if [[ -n "$SQL_INSTANCE" ]]; then
44
+ run_cmd "gcloud sql instances describe ${SQL_INSTANCE}" \
45
+ gcloud sql instances describe "$SQL_INSTANCE" --format="table(name,state,backendType,availabilityType,ipAddresses.ipAddress)"
46
+ fi
47
+
48
+ run_cmd "gcloud logging read (recent errors)" \
49
+ gcloud logging read 'severity>=ERROR' --limit=5 --format="table(timestamp, resource.labels.cluster_name, textPayload)"
50
+
51
+ info "Quick triage completed. Consider VPC connectivity, IAM bindings, or workload identity if issues persist."
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # QuickTriage script for gitops-operator
3
+ # Provides a fast snapshot of workload health inside a Kubernetes cluster.
4
+
5
+ set -euo pipefail
6
+
7
+ NAMESPACE="${1:-tcm-non-prod}"
8
+ LABEL_SELECTOR="${2:-}"
9
+
10
+ info() {
11
+ printf '[quicktriage] %s\n' "$*"
12
+ }
13
+
14
+ run_cmd() {
15
+ local description="$1"
16
+ shift
17
+
18
+ if ! command -v "$1" >/dev/null 2>&1; then
19
+ info "Skipping ${description} (command $1 not available)"
20
+ return
21
+ fi
22
+
23
+ info "$description"
24
+ "$@" || info "Command failed: $*"
25
+ }
26
+
27
+ info "Starting gitops quick triage (namespace=${NAMESPACE:-all}, selector='${LABEL_SELECTOR}')"
28
+
29
+ KUBECTL_ARGS=(-o wide)
30
+ if [[ -n "$NAMESPACE" ]]; then
31
+ KUBECTL_ARGS=(-n "$NAMESPACE" "${KUBECTL_ARGS[@]}")
32
+ fi
33
+ if [[ -n "$LABEL_SELECTOR" ]]; then
34
+ KUBECTL_ARGS+=(-l "$LABEL_SELECTOR")
35
+ fi
36
+
37
+ run_cmd "kubectl get pods" kubectl get pods "${KUBECTL_ARGS[@]}"
38
+
39
+ if [[ -n "$NAMESPACE" ]]; then
40
+ run_cmd "kubectl get deploy" kubectl get deploy -n "$NAMESPACE"
41
+ run_cmd "kubectl get helmrelease" kubectl get helmrelease -n "$NAMESPACE"
42
+ fi
43
+
44
+ run_cmd "flux get kustomizations" flux get kustomizations
45
+ run_cmd "flux get helmreleases" flux get helmreleases -A
46
+
47
+ info "Quick triage completed. Recommended next steps: describe failing pods or inspect logs if issues were detected."
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # QuickTriage script for terraform-architect
3
+ # Performs fast validation checks on Terraform/Terragrunt directories.
4
+
5
+ set -euo pipefail
6
+
7
+ TARGET_DIR="${1:-.}"
8
+ USE_TERRAGRUNT="${USE_TERRAGRUNT:-false}"
9
+
10
+ info() {
11
+ printf '[quicktriage] %s\n' "$*"
12
+ }
13
+
14
+ run_cmd() {
15
+ local description="$1"
16
+ shift
17
+
18
+ if ! command -v "$1" >/dev/null 2>&1; then
19
+ info "Skipping ${description} (command $1 not available)"
20
+ return
21
+ fi
22
+
23
+ info "$description"
24
+ (cd "$TARGET_DIR" && "$@") || info "Command failed: $*"
25
+ }
26
+
27
+ info "Starting Terraform quick triage (dir=${TARGET_DIR}, terragrunt=${USE_TERRAGRUNT})"
28
+
29
+ if [[ "${USE_TERRAGRUNT}" == "true" ]]; then
30
+ run_cmd "terragrunt fmt -check" terragrunt fmt -check
31
+ run_cmd "terragrunt validate" terragrunt validate
32
+ run_cmd "terragrunt plan (detailed exit code)" terragrunt plan -lock=false -detailed-exitcode || true
33
+ else
34
+ run_cmd "terraform fmt -check" terraform fmt -check
35
+ run_cmd "terraform init -backend=false" terraform init -backend=false
36
+ run_cmd "terraform validate" terraform validate
37
+ run_cmd "terraform plan (detailed exit code)" terraform plan -lock=false -refresh=false -detailed-exitcode || true
38
+ fi
39
+
40
+ info "Quick triage completed. Exit code 1 on plan indicates drift; review the plan output if printed."
@@ -0,0 +1,222 @@
1
+ """
2
+ Semantic matching using pre-computed embeddings
3
+
4
+ CRITICAL: This module does NOT require torch at runtime!
5
+ - Embeddings are pre-computed offline
6
+ - At runtime, we only load numpy + json
7
+ - Similarity is calculated with scipy/sklearn
8
+
9
+ Week 2 Addition
10
+ """
11
+
12
+ import json
13
+ from pathlib import Path
14
+ from typing import Tuple, List, Optional, Dict, Any
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class SemanticMatcher:
21
+ """
22
+ Match requests to intents using pre-computed embeddings
23
+
24
+ - Loads pre-computed embeddings from .npy/.json
25
+ - Calculates similarity using numpy only
26
+ - No torch/transformers needed at runtime
27
+ - Provides fallback to keyword scores
28
+ """
29
+
30
+ def __init__(self, embeddings_dir: Optional[Path] = None):
31
+ """
32
+ Initialize semantic matcher
33
+
34
+ Args:
35
+ embeddings_dir: Directory containing embeddings (defaults to .claude/configs/)
36
+ """
37
+ if embeddings_dir is None:
38
+ embeddings_dir = Path(__file__).parent.parent / "configs"
39
+
40
+ self.embeddings_dir = embeddings_dir
41
+ self.embeddings: Dict[str, Any] = {}
42
+ self.metadata: Dict[str, Any] = {}
43
+ self.available = False
44
+
45
+ self._load_embeddings()
46
+
47
+ def _load_embeddings(self):
48
+ """Load pre-computed embeddings from JSON"""
49
+ json_path = self.embeddings_dir / "intent_embeddings.json"
50
+
51
+ if not json_path.exists():
52
+ logger.warning(
53
+ f"⚠️ Embeddings not found at {json_path}. "
54
+ "Run: python3 .claude/tools/generate_embeddings.py"
55
+ )
56
+ self.available = False
57
+ return
58
+
59
+ try:
60
+ with open(json_path, 'r', encoding='utf-8') as f:
61
+ data = json.load(f)
62
+
63
+ # Convert lists back to numpy arrays
64
+ import numpy as np
65
+
66
+ for intent, info in data.items():
67
+ self.embeddings[intent] = {
68
+ "embedding": np.array(info["embedding"]),
69
+ "examples": info["examples"],
70
+ "dimension": info.get("dimension", 384)
71
+ }
72
+
73
+ self.available = True
74
+ logger.info(f"✅ Loaded {len(self.embeddings)} intent embeddings")
75
+
76
+ except Exception as e:
77
+ logger.error(f"Error loading embeddings: {e}")
78
+ self.available = False
79
+
80
+ def find_similar_intent(
81
+ self,
82
+ text: str,
83
+ keyword_scores: Dict[str, float]
84
+ ) -> Tuple[Optional[str], float]:
85
+ """
86
+ Find most similar intent combining keywords + embeddings
87
+
88
+ Args:
89
+ text: User request
90
+ keyword_scores: Scores from keyword matching {intent: score}
91
+
92
+ Returns:
93
+ (best_intent, confidence)
94
+
95
+ Strategy:
96
+ 1. If embeddings available, calculate text embedding (via TF-IDF approximation)
97
+ 2. Find similarity to each intent embedding
98
+ 3. Combine with keyword scores
99
+ 4. Return best match
100
+ """
101
+ if not keyword_scores:
102
+ return None, 0.0
103
+
104
+ # If embeddings not available, use keyword scores as primary
105
+ if not self.available:
106
+ logger.debug("Embeddings not available, using keyword scores only")
107
+ best_intent = max(keyword_scores, key=keyword_scores.get)
108
+ confidence = keyword_scores[best_intent]
109
+ return best_intent, confidence
110
+
111
+ # If embeddings available, enhance keyword scores with embedding similarity
112
+ try:
113
+ import numpy as np
114
+ from sklearn.feature_extraction.text import TfidfVectorizer
115
+
116
+ # Create TF-IDF approximation of text embedding
117
+ # (lightweight alternative to transformer embeddings)
118
+ all_examples = []
119
+ intent_map = []
120
+
121
+ for intent, info in self.embeddings.items():
122
+ all_examples.extend(info["examples"])
123
+ intent_map.extend([intent] * len(info["examples"]))
124
+
125
+ all_examples.append(text) # Add query at end
126
+
127
+ # Vectorize
128
+ vectorizer = TfidfVectorizer(
129
+ analyzer='char',
130
+ ngram_range=(2, 3),
131
+ max_features=100
132
+ )
133
+ tfidf_matrix = vectorizer.fit_transform(all_examples)
134
+
135
+ # Get query vector (last row)
136
+ query_vector = tfidf_matrix[-1].toarray().flatten()
137
+
138
+ # Calculate similarities to each intent
139
+ embedding_scores = {}
140
+
141
+ for intent in self.embeddings.keys():
142
+ # Get example vectors for this intent
143
+ example_indices = [i for i, x in enumerate(intent_map) if x == intent]
144
+ example_vectors = tfidf_matrix[example_indices].toarray()
145
+
146
+ # Calculate mean similarity to examples
147
+ similarities = []
148
+ for example_vec in example_vectors:
149
+ # Cosine similarity
150
+ dot = np.dot(query_vector, example_vec)
151
+ norm1 = np.linalg.norm(query_vector)
152
+ norm2 = np.linalg.norm(example_vec)
153
+ if norm1 > 0 and norm2 > 0:
154
+ similarity = dot / (norm1 * norm2)
155
+ similarities.append(similarity)
156
+
157
+ # Mean similarity for this intent
158
+ if similarities:
159
+ embedding_scores[intent] = np.mean(similarities)
160
+ else:
161
+ embedding_scores[intent] = 0.0
162
+
163
+ # Combine keyword scores (70%) + embedding scores (30%)
164
+ combined_scores = {}
165
+ for intent in keyword_scores.keys():
166
+ kw_score = keyword_scores[intent]
167
+ emb_score = embedding_scores.get(intent, 0.0)
168
+
169
+ # Normalize both to 0-1
170
+ kw_norm = min(kw_score / 5.0, 1.0) # keyword scores are ~0-5
171
+ emb_norm = max(0.0, min(emb_score, 1.0)) # embedding scores already 0-1
172
+
173
+ # Weighted combination
174
+ combined = (kw_norm * 0.7) + (emb_norm * 0.3)
175
+ combined_scores[intent] = combined
176
+
177
+ # Select best
178
+ best_intent = max(combined_scores, key=combined_scores.get)
179
+ confidence = combined_scores[best_intent]
180
+
181
+ logger.debug(
182
+ f"Combined scores: {best_intent} = {confidence:.3f} "
183
+ f"(kw={keyword_scores.get(best_intent, 0):.2f}, "
184
+ f"emb={embedding_scores.get(best_intent, 0):.3f})"
185
+ )
186
+
187
+ return best_intent, confidence
188
+
189
+ except Exception as e:
190
+ logger.warning(f"Error in embedding similarity: {e}")
191
+ # Fallback to keyword scores
192
+ best_intent = max(keyword_scores, key=keyword_scores.get)
193
+ confidence = keyword_scores[best_intent]
194
+ return best_intent, confidence
195
+
196
+ def get_intent_examples(self, intent: str) -> List[str]:
197
+ """Get example requests for an intent"""
198
+ if intent in self.embeddings:
199
+ return self.embeddings[intent]["examples"]
200
+ return []
201
+
202
+ def list_intents(self) -> List[str]:
203
+ """List all available intents"""
204
+ return list(self.embeddings.keys())
205
+
206
+ def is_available(self) -> bool:
207
+ """Check if embeddings are loaded"""
208
+ return self.available
209
+
210
+ def get_stats(self) -> Dict[str, Any]:
211
+ """Get statistics about loaded embeddings"""
212
+ return {
213
+ "available": self.available,
214
+ "intents": len(self.embeddings),
215
+ "total_examples": sum(
216
+ len(info["examples"]) for info in self.embeddings.values()
217
+ ),
218
+ "embedding_dimension": next(
219
+ (info["dimension"] for info in self.embeddings.values()),
220
+ None
221
+ )
222
+ }