@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.
- package/CHANGELOG.md +315 -0
- package/CLAUDE.md +154 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/agents/aws-troubleshooter.md +50 -0
- package/agents/claude-architect.md +821 -0
- package/agents/devops-developer.md +92 -0
- package/agents/gcp-troubleshooter.md +50 -0
- package/agents/gitops-operator.md +360 -0
- package/agents/terraform-architect.md +289 -0
- package/bin/gaia-init.js +620 -0
- package/commands/architect.md +97 -0
- package/commands/restore-session.md +87 -0
- package/commands/save-session.md +88 -0
- package/commands/session-status.md +61 -0
- package/commands/speckit.add-task.md +144 -0
- package/commands/speckit.analyze-task.md +65 -0
- package/commands/speckit.implement.md +96 -0
- package/commands/speckit.init.md +237 -0
- package/commands/speckit.plan.md +88 -0
- package/commands/speckit.specify.md +161 -0
- package/commands/speckit.tasks.md +188 -0
- package/config/AGENTS.md +162 -0
- package/config/agent-catalog.md +604 -0
- package/config/context-contracts.md +682 -0
- package/config/git-standards.md +674 -0
- package/config/git_standards.json +69 -0
- package/config/orchestration-workflow.md +735 -0
- package/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
- package/hooks/__pycache__/pre_kubectl_security.cpython-312.pyc +0 -0
- package/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/hooks/__pycache__/subagent_stop.cpython-312.pyc +0 -0
- package/hooks/post_tool_use.py +463 -0
- package/hooks/pre_kubectl_security.py +205 -0
- package/hooks/pre_tool_use.py +530 -0
- package/hooks/session_start.py +315 -0
- package/hooks/subagent_stop.py +549 -0
- package/index.js +92 -0
- package/package.json +59 -0
- package/speckit/README.en.md +648 -0
- package/speckit/README.md +353 -0
- package/speckit/governance.md +169 -0
- package/speckit/scripts/check-prerequisites.sh +194 -0
- package/speckit/scripts/common.sh +126 -0
- package/speckit/scripts/create-new-feature.sh +131 -0
- package/speckit/scripts/init.sh +42 -0
- package/speckit/scripts/setup-plan.sh +95 -0
- package/speckit/scripts/update-agent-context.sh +718 -0
- package/speckit/templates/adr-template.md +118 -0
- package/speckit/templates/agent-file-template.md +23 -0
- package/speckit/templates/plan-template.md +233 -0
- package/speckit/templates/spec-template.md +116 -0
- package/speckit/templates/tasks-template-bkp.md +136 -0
- package/speckit/templates/tasks-template.md +345 -0
- package/templates/CLAUDE.template.md +170 -0
- package/templates/code-examples/approval_gate_workflow.py +141 -0
- package/templates/code-examples/clarification_workflow.py +94 -0
- package/templates/code-examples/commit_validation.py +86 -0
- package/templates/project-context.template.json +126 -0
- package/templates/settings.template.json +307 -0
- package/tools/__pycache__/agent_router.cpython-312.pyc +0 -0
- package/tools/__pycache__/approval_gate.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_engine.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_patterns.cpython-312.pyc +0 -0
- package/tools/__pycache__/commit_validator.cpython-312.pyc +0 -0
- package/tools/__pycache__/context_section_reader.cpython-312.pyc +0 -0
- package/tools/__pycache__/routing_dashboard.cpython-312.pyc +0 -0
- package/tools/__pycache__/routing_feedback.cpython-312.pyc +0 -0
- package/tools/__pycache__/semantic_matcher.cpython-312.pyc +0 -0
- package/tools/__pycache__/task_manager.cpython-312.pyc +0 -0
- package/tools/agent_capabilities.json +231 -0
- package/tools/agent_invoker_helper.py +239 -0
- package/tools/agent_router.py +730 -0
- package/tools/approval_gate.py +318 -0
- package/tools/clarify_engine.py +511 -0
- package/tools/clarify_patterns.py +356 -0
- package/tools/commit_validator.py +338 -0
- package/tools/context_provider.py +181 -0
- package/tools/context_section_reader.py +301 -0
- package/tools/demo_clarify.py +104 -0
- package/tools/generate_embeddings.py +168 -0
- package/tools/quicktriage_aws_troubleshooter.sh +45 -0
- package/tools/quicktriage_devops_developer.sh +38 -0
- package/tools/quicktriage_gcp_troubleshooter.sh +51 -0
- package/tools/quicktriage_gitops_operator.sh +47 -0
- package/tools/quicktriage_terraform_architect.sh +40 -0
- package/tools/semantic_matcher.py +222 -0
- package/tools/task_manager.py +547 -0
- package/tools/task_manager_README.md +395 -0
- package/tools/task_manager_example.py +215 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import argparse
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, List, Any
|
|
6
|
+
|
|
7
|
+
# This script is expected to be run from the root of the repository.
|
|
8
|
+
# The project context file is located at `.claude/project-context.json`.
|
|
9
|
+
# We construct the path relative to the script's assumed execution location.
|
|
10
|
+
DEFAULT_CONTEXT_PATH = Path(".claude/project-context.json")
|
|
11
|
+
|
|
12
|
+
# Defines the mandatory keys that form the "Context Contract" for each agent.
|
|
13
|
+
AGENT_CONTRACTS: Dict[str, List[str]] = {
|
|
14
|
+
"terraform-architect": [
|
|
15
|
+
"project_details",
|
|
16
|
+
"terraform_infrastructure",
|
|
17
|
+
"operational_guidelines",
|
|
18
|
+
],
|
|
19
|
+
"gitops-operator": [
|
|
20
|
+
"project_details",
|
|
21
|
+
"gitops_configuration",
|
|
22
|
+
"infrastructure_topology",
|
|
23
|
+
"cluster_details",
|
|
24
|
+
"operational_guidelines",
|
|
25
|
+
],
|
|
26
|
+
"gcp-troubleshooter": [
|
|
27
|
+
"project_details",
|
|
28
|
+
"infrastructure_topology",
|
|
29
|
+
"terraform_infrastructure",
|
|
30
|
+
"gitops_configuration",
|
|
31
|
+
"application_services",
|
|
32
|
+
"monitoring_observability",
|
|
33
|
+
],
|
|
34
|
+
"aws-troubleshooter": [
|
|
35
|
+
"project_details",
|
|
36
|
+
"infrastructure_topology",
|
|
37
|
+
"terraform_infrastructure",
|
|
38
|
+
"gitops_configuration",
|
|
39
|
+
"application_services",
|
|
40
|
+
"monitoring_observability",
|
|
41
|
+
],
|
|
42
|
+
# devops-developer has a more generic contract, often needing the full view.
|
|
43
|
+
"devops-developer": [
|
|
44
|
+
"project_details",
|
|
45
|
+
"application_architecture",
|
|
46
|
+
"application_services",
|
|
47
|
+
"development_standards",
|
|
48
|
+
"operational_guidelines"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def load_project_context(context_path: Path) -> Dict[str, Any]:
|
|
53
|
+
"""Loads the project context from the specified JSON file."""
|
|
54
|
+
if not context_path.is_file():
|
|
55
|
+
print(f"Error: Context file not found at {context_path}", file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
with open(context_path, 'r', encoding='utf-8') as f:
|
|
58
|
+
return json.load(f)
|
|
59
|
+
|
|
60
|
+
def get_contract_context(project_context: Dict[str, Any], agent_name: str) -> Dict[str, Any]:
|
|
61
|
+
"""Extracts the contract-defined context for a given agent."""
|
|
62
|
+
contract_keys = AGENT_CONTRACTS.get(agent_name)
|
|
63
|
+
if not contract_keys:
|
|
64
|
+
print(
|
|
65
|
+
f"Warning: No contract found for agent '{agent_name}'. Returning empty contract.",
|
|
66
|
+
file=sys.stderr,
|
|
67
|
+
)
|
|
68
|
+
return {}
|
|
69
|
+
|
|
70
|
+
sections = project_context.get("sections", {})
|
|
71
|
+
if not sections:
|
|
72
|
+
raise KeyError("project-context.json must contain a 'sections' object.")
|
|
73
|
+
return {key: sections[key] for key in contract_keys if key in sections}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_semantic_enrichment(
|
|
77
|
+
project_context: Dict[str, Any], contract_keys: List[str], user_task: str
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Performs semantic analysis to find additional relevant context.
|
|
81
|
+
|
|
82
|
+
NOTE: This is a placeholder implementation. A real implementation would use
|
|
83
|
+
vector embeddings (e.g., SentenceTransformers, OpenAI embeddings) to find
|
|
84
|
+
sections of the project_context semantically similar to the user_task.
|
|
85
|
+
|
|
86
|
+
For now, it performs a simple keyword match, excluding keys already in the contract.
|
|
87
|
+
"""
|
|
88
|
+
sections = project_context.get("sections", {})
|
|
89
|
+
if not sections:
|
|
90
|
+
raise KeyError("project-context.json must contain a 'sections' object.")
|
|
91
|
+
enrichment: Dict[str, Any] = {}
|
|
92
|
+
contract_key_set = set(contract_keys)
|
|
93
|
+
potential_keys = set(sections.keys()) - contract_key_set
|
|
94
|
+
task_words = {word.strip(".,:;!?") for word in user_task.lower().split()}
|
|
95
|
+
|
|
96
|
+
for key in potential_keys:
|
|
97
|
+
normalized_key = key.lower()
|
|
98
|
+
if normalized_key in task_words:
|
|
99
|
+
enrichment[key] = sections[key]
|
|
100
|
+
|
|
101
|
+
# Light metadata hints when the user asks about freshness/versioning.
|
|
102
|
+
metadata = project_context.get("metadata")
|
|
103
|
+
if metadata and any(word in {"metadata", "version", "updated"} for word in task_words):
|
|
104
|
+
enrichment.setdefault("metadata", metadata)
|
|
105
|
+
|
|
106
|
+
# Heuristic: include matching services based on task wording.
|
|
107
|
+
def normalize_services(raw: Any) -> List[Dict[str, Any]]:
|
|
108
|
+
normalized: List[Dict[str, Any]] = []
|
|
109
|
+
if isinstance(raw, list):
|
|
110
|
+
for svc in raw:
|
|
111
|
+
if isinstance(svc, dict) and svc.get("name"):
|
|
112
|
+
normalized.append(svc)
|
|
113
|
+
elif isinstance(raw, dict):
|
|
114
|
+
for name, payload in raw.items():
|
|
115
|
+
entry = {"name": name}
|
|
116
|
+
if isinstance(payload, dict):
|
|
117
|
+
entry.update(payload)
|
|
118
|
+
normalized.append(entry)
|
|
119
|
+
return normalized
|
|
120
|
+
|
|
121
|
+
candidate_services: List[Dict[str, Any]] = []
|
|
122
|
+
candidate_services.extend(normalize_services(sections.get("application_services")))
|
|
123
|
+
|
|
124
|
+
service_catalog = sections.get("service_catalog")
|
|
125
|
+
if isinstance(service_catalog, dict):
|
|
126
|
+
candidate_services.extend(
|
|
127
|
+
normalize_services(service_catalog.get("applications"))
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
app_arch = sections.get("application_architecture")
|
|
131
|
+
if isinstance(app_arch, dict):
|
|
132
|
+
candidate_services.extend(normalize_services(app_arch.get("services")))
|
|
133
|
+
|
|
134
|
+
if candidate_services and "application_services" not in contract_key_set:
|
|
135
|
+
matched = [
|
|
136
|
+
svc for svc in candidate_services
|
|
137
|
+
if svc.get("name", "").lower() in user_task.lower()
|
|
138
|
+
]
|
|
139
|
+
if matched:
|
|
140
|
+
enrichment["application_services"] = matched
|
|
141
|
+
|
|
142
|
+
return enrichment
|
|
143
|
+
|
|
144
|
+
def main():
|
|
145
|
+
"""Main function to generate and print the context payload."""
|
|
146
|
+
parser = argparse.ArgumentParser(
|
|
147
|
+
description="""
|
|
148
|
+
Generates a structured context payload for a Claude agent based on its contract
|
|
149
|
+
and a semantic analysis of the user's task.
|
|
150
|
+
"""
|
|
151
|
+
)
|
|
152
|
+
parser.add_argument("agent_name", choices=AGENT_CONTRACTS.keys(), help="The name of the agent being invoked.")
|
|
153
|
+
parser.add_argument("user_task", help="The user's task or query for the agent.")
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--context-file",
|
|
156
|
+
type=Path,
|
|
157
|
+
default=DEFAULT_CONTEXT_PATH,
|
|
158
|
+
help=f"Path to the project-context.json file. Defaults to '{DEFAULT_CONTEXT_PATH}'"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
args = parser.parse_args()
|
|
162
|
+
|
|
163
|
+
project_context = load_project_context(args.context_file)
|
|
164
|
+
|
|
165
|
+
contract_context = get_contract_context(project_context, args.agent_name)
|
|
166
|
+
|
|
167
|
+
enrichment_context = get_semantic_enrichment(
|
|
168
|
+
project_context,
|
|
169
|
+
list(contract_context.keys()),
|
|
170
|
+
args.user_task
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
final_payload = {
|
|
174
|
+
"contract": contract_context,
|
|
175
|
+
"enrichment": enrichment_context
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
print(json.dumps(final_payload, indent=2))
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
main()
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Context Section Reader for Claude Agent System
|
|
4
|
+
|
|
5
|
+
Reads specific sections from project-context.json for selective loading by agents.
|
|
6
|
+
Called by Claude orchestrator BEFORE invoking agents to reduce token usage.
|
|
7
|
+
|
|
8
|
+
Architecture:
|
|
9
|
+
- Claude orchestrator executes this script (NOT agents)
|
|
10
|
+
- Agents receive pre-filtered context in their prompts
|
|
11
|
+
- Reduces token usage by ~70% per agent invocation
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from .claude.tools.context_section_reader import ContextSectionReader
|
|
15
|
+
|
|
16
|
+
reader = ContextSectionReader()
|
|
17
|
+
context = reader.get_for_agent('gitops-operator')
|
|
18
|
+
|
|
19
|
+
# Pass context to agent in Task tool prompt
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import List, Dict, Optional, Any
|
|
24
|
+
import json
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def find_claude_dir() -> Path:
|
|
28
|
+
"""Find the .claude directory by searching upward from current location"""
|
|
29
|
+
current = Path.cwd()
|
|
30
|
+
|
|
31
|
+
# If we're already in a .claude directory, return it
|
|
32
|
+
if current.name == ".claude":
|
|
33
|
+
return current
|
|
34
|
+
|
|
35
|
+
# Look for .claude in current directory
|
|
36
|
+
claude_dir = current / ".claude"
|
|
37
|
+
if claude_dir.exists():
|
|
38
|
+
return claude_dir
|
|
39
|
+
|
|
40
|
+
# Search upward through parent directories
|
|
41
|
+
for parent in current.parents:
|
|
42
|
+
claude_dir = parent / ".claude"
|
|
43
|
+
if claude_dir.exists():
|
|
44
|
+
return claude_dir
|
|
45
|
+
|
|
46
|
+
# Fallback - raise error if not found
|
|
47
|
+
raise FileNotFoundError(
|
|
48
|
+
"No .claude directory found. Please run from a project directory "
|
|
49
|
+
"or specify context_file explicitly."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ContextSectionReader:
|
|
54
|
+
"""
|
|
55
|
+
Read and filter sections from project-context.json for agent-specific loading.
|
|
56
|
+
|
|
57
|
+
Token Optimization:
|
|
58
|
+
- Without filtering: ~328 lines (1,312 tokens)
|
|
59
|
+
- With filtering: ~80-100 lines (320-400 tokens)
|
|
60
|
+
- Savings: ~70% per agent invocation
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# Define which sections each agent needs (JSON keys in snake_case)
|
|
64
|
+
AGENT_SECTIONS = {
|
|
65
|
+
'gitops-operator': [
|
|
66
|
+
'infrastructure_topology',
|
|
67
|
+
'gitops_configuration',
|
|
68
|
+
'operational_guidelines',
|
|
69
|
+
],
|
|
70
|
+
'gcp-troubleshooter': [
|
|
71
|
+
'infrastructure_topology',
|
|
72
|
+
'operational_guidelines',
|
|
73
|
+
'monitoring_observability',
|
|
74
|
+
],
|
|
75
|
+
'terraform-architect': [
|
|
76
|
+
'infrastructure_topology',
|
|
77
|
+
'terraform_infrastructure',
|
|
78
|
+
'operational_guidelines',
|
|
79
|
+
],
|
|
80
|
+
'devops-developer': [
|
|
81
|
+
'application_architecture',
|
|
82
|
+
'development_standards',
|
|
83
|
+
'operational_guidelines',
|
|
84
|
+
],
|
|
85
|
+
'aws-troubleshooter': [
|
|
86
|
+
'infrastructure_topology',
|
|
87
|
+
'operational_guidelines',
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def __init__(self, context_file: Optional[str] = None):
|
|
92
|
+
"""
|
|
93
|
+
Initialize reader with project context file.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
context_file: Path to project-context.json (default: searches for .claude/project-context.json)
|
|
97
|
+
"""
|
|
98
|
+
if context_file is None:
|
|
99
|
+
# Find the .claude directory by searching upward
|
|
100
|
+
claude_dir = find_claude_dir()
|
|
101
|
+
context_file = claude_dir / "project-context.json"
|
|
102
|
+
|
|
103
|
+
self.path = Path(context_file)
|
|
104
|
+
|
|
105
|
+
if not self.path.exists():
|
|
106
|
+
raise FileNotFoundError(f"Context file not found: {self.path}")
|
|
107
|
+
|
|
108
|
+
with open(self.path, 'r', encoding='utf-8') as f:
|
|
109
|
+
self.data = json.load(f)
|
|
110
|
+
|
|
111
|
+
self._parse_sections()
|
|
112
|
+
|
|
113
|
+
def _parse_sections(self) -> None:
|
|
114
|
+
"""Extract sections from JSON data."""
|
|
115
|
+
self.sections: Dict[str, Any] = {}
|
|
116
|
+
|
|
117
|
+
# Extract sections from JSON
|
|
118
|
+
if 'sections' in self.data:
|
|
119
|
+
self.sections = self.data['sections']
|
|
120
|
+
else:
|
|
121
|
+
raise ValueError("Invalid JSON structure: 'sections' key not found")
|
|
122
|
+
|
|
123
|
+
def get_sections(self, section_names: List[str]) -> str:
|
|
124
|
+
"""
|
|
125
|
+
Get specific sections as formatted JSON string.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
section_names: List of section names to retrieve
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Formatted JSON string with requested sections
|
|
132
|
+
"""
|
|
133
|
+
result = {}
|
|
134
|
+
missing = []
|
|
135
|
+
|
|
136
|
+
for name in section_names:
|
|
137
|
+
if name in self.sections:
|
|
138
|
+
result[name] = self.sections[name]
|
|
139
|
+
else:
|
|
140
|
+
missing.append(name)
|
|
141
|
+
|
|
142
|
+
if missing:
|
|
143
|
+
print(f"Warning: Sections not found: {missing}")
|
|
144
|
+
|
|
145
|
+
if not result:
|
|
146
|
+
return json.dumps({
|
|
147
|
+
"error": "No sections found",
|
|
148
|
+
"message": "Requested sections were not available."
|
|
149
|
+
}, indent=2)
|
|
150
|
+
|
|
151
|
+
# Format as JSON for agent consumption
|
|
152
|
+
return json.dumps(result, indent=2, ensure_ascii=False)
|
|
153
|
+
|
|
154
|
+
def get_for_agent(self, agent_name: str) -> str:
|
|
155
|
+
"""
|
|
156
|
+
Get sections needed by specific agent.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
agent_name: Name of the agent (e.g., 'gitops-operator')
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Markdown string with agent-specific context
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ValueError: If agent_name is not recognized
|
|
166
|
+
"""
|
|
167
|
+
if agent_name not in self.AGENT_SECTIONS:
|
|
168
|
+
available = ', '.join(self.AGENT_SECTIONS.keys())
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"Unknown agent: {agent_name}. "
|
|
171
|
+
f"Available agents: {available}"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
sections = self.AGENT_SECTIONS[agent_name]
|
|
175
|
+
return self.get_sections(sections)
|
|
176
|
+
|
|
177
|
+
def list_sections(self) -> List[str]:
|
|
178
|
+
"""Get list of all available sections."""
|
|
179
|
+
return list(self.sections.keys())
|
|
180
|
+
|
|
181
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
182
|
+
"""
|
|
183
|
+
Get statistics about the context file.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Dictionary with size and token estimates
|
|
187
|
+
"""
|
|
188
|
+
# Calculate total JSON size
|
|
189
|
+
total_json = json.dumps(self.data, ensure_ascii=False)
|
|
190
|
+
total_chars = len(total_json)
|
|
191
|
+
total_tokens = total_chars // 4 # Rough estimate: 4 chars per token
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
'total_chars': total_chars,
|
|
195
|
+
'total_tokens_estimated': total_tokens,
|
|
196
|
+
'total_sections': len(self.sections),
|
|
197
|
+
'sections': {
|
|
198
|
+
name: {
|
|
199
|
+
'chars': len(json.dumps(content, ensure_ascii=False)),
|
|
200
|
+
'tokens_estimated': len(json.dumps(content, ensure_ascii=False)) // 4
|
|
201
|
+
}
|
|
202
|
+
for name, content in self.sections.items()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def get_agent_stats(self, agent_name: str) -> Dict[str, Any]:
|
|
207
|
+
"""
|
|
208
|
+
Get statistics for a specific agent's context.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
agent_name: Name of the agent
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dictionary with character and token counts for agent
|
|
215
|
+
"""
|
|
216
|
+
context = self.get_for_agent(agent_name)
|
|
217
|
+
chars = len(context)
|
|
218
|
+
tokens = chars // 4
|
|
219
|
+
|
|
220
|
+
full_stats = self.get_stats()
|
|
221
|
+
savings = {
|
|
222
|
+
'chars': full_stats['total_chars'] - chars,
|
|
223
|
+
'tokens': full_stats['total_tokens_estimated'] - tokens,
|
|
224
|
+
'percentage': round((1 - chars / full_stats['total_chars']) * 100, 1)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
'agent': agent_name,
|
|
229
|
+
'chars_loaded': chars,
|
|
230
|
+
'tokens_estimated': tokens,
|
|
231
|
+
'savings': savings
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main():
|
|
236
|
+
"""CLI interface for testing and debugging."""
|
|
237
|
+
import sys
|
|
238
|
+
import json
|
|
239
|
+
|
|
240
|
+
reader = ContextSectionReader()
|
|
241
|
+
|
|
242
|
+
if len(sys.argv) < 2:
|
|
243
|
+
print("Context Section Reader")
|
|
244
|
+
print("\nUsage:")
|
|
245
|
+
print(" python context_section_reader.py <command> [args]")
|
|
246
|
+
print("\nCommands:")
|
|
247
|
+
print(" list - List all available sections")
|
|
248
|
+
print(" stats - Show statistics for context file")
|
|
249
|
+
print(" agent <name> - Get context for specific agent")
|
|
250
|
+
print(" agent-stats <name> - Show stats for agent's context")
|
|
251
|
+
print(" sections <name1> <name2> - Get specific sections")
|
|
252
|
+
print("\nAvailable agents:")
|
|
253
|
+
for agent in reader.AGENT_SECTIONS.keys():
|
|
254
|
+
print(f" - {agent}")
|
|
255
|
+
sys.exit(0)
|
|
256
|
+
|
|
257
|
+
command = sys.argv[1]
|
|
258
|
+
|
|
259
|
+
if command == 'list':
|
|
260
|
+
print("Available sections:")
|
|
261
|
+
for section in reader.list_sections():
|
|
262
|
+
print(f" - {section}")
|
|
263
|
+
|
|
264
|
+
elif command == 'stats':
|
|
265
|
+
stats = reader.get_stats()
|
|
266
|
+
print(json.dumps(stats, indent=2))
|
|
267
|
+
|
|
268
|
+
elif command == 'agent':
|
|
269
|
+
if len(sys.argv) < 3:
|
|
270
|
+
print("Error: Agent name required")
|
|
271
|
+
sys.exit(1)
|
|
272
|
+
|
|
273
|
+
agent_name = sys.argv[2]
|
|
274
|
+
context = reader.get_for_agent(agent_name)
|
|
275
|
+
print(context)
|
|
276
|
+
|
|
277
|
+
elif command == 'agent-stats':
|
|
278
|
+
if len(sys.argv) < 3:
|
|
279
|
+
print("Error: Agent name required")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
agent_name = sys.argv[2]
|
|
283
|
+
stats = reader.get_agent_stats(agent_name)
|
|
284
|
+
print(json.dumps(stats, indent=2))
|
|
285
|
+
|
|
286
|
+
elif command == 'sections':
|
|
287
|
+
if len(sys.argv) < 3:
|
|
288
|
+
print("Error: Section names required")
|
|
289
|
+
sys.exit(1)
|
|
290
|
+
|
|
291
|
+
section_names = sys.argv[2:]
|
|
292
|
+
context = reader.get_sections(section_names)
|
|
293
|
+
print(context)
|
|
294
|
+
|
|
295
|
+
else:
|
|
296
|
+
print(f"Unknown command: {command}")
|
|
297
|
+
sys.exit(1)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
if __name__ == '__main__':
|
|
301
|
+
main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Demo script for the Clarification Engine
|
|
4
|
+
|
|
5
|
+
Shows how the clarification system works with different types of prompts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
11
|
+
|
|
12
|
+
from clarify_engine import request_clarification
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def demo_prompt(prompt: str, description: str = ""):
|
|
16
|
+
"""Test a single prompt and display results."""
|
|
17
|
+
print("=" * 70)
|
|
18
|
+
if description:
|
|
19
|
+
print(f"📝 {description}")
|
|
20
|
+
print(f"Prompt: \"{prompt}\"")
|
|
21
|
+
print("=" * 70)
|
|
22
|
+
|
|
23
|
+
result = request_clarification(prompt)
|
|
24
|
+
|
|
25
|
+
if not result["needs_clarification"]:
|
|
26
|
+
print("✅ No clarification needed - prompt is specific enough\n")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
print(f"⚠️ Clarification needed (score: {result['clarification_context']['ambiguity_analysis']['ambiguity_score']}/100)\n")
|
|
30
|
+
|
|
31
|
+
# Show summary
|
|
32
|
+
print(result["summary"])
|
|
33
|
+
print()
|
|
34
|
+
|
|
35
|
+
# Show questions
|
|
36
|
+
for i, question in enumerate(result["question_config"]["questions"]):
|
|
37
|
+
print(f"\n{'─' * 70}")
|
|
38
|
+
print(f"{question['header']}")
|
|
39
|
+
print(f"{'─' * 70}")
|
|
40
|
+
print(f"❓ {question['question']}\n")
|
|
41
|
+
|
|
42
|
+
for j, option in enumerate(question["options"], 1):
|
|
43
|
+
print(f" {j}. {option['label']}")
|
|
44
|
+
print(f" → {option['description']}\n")
|
|
45
|
+
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
"""Run demo with various prompt types."""
|
|
51
|
+
|
|
52
|
+
print("\n" + "🎯" * 35)
|
|
53
|
+
print(" CLARIFICATION ENGINE - DEMO")
|
|
54
|
+
print("🎯" * 35 + "\n")
|
|
55
|
+
|
|
56
|
+
# Test 1: Ambiguous service
|
|
57
|
+
demo_prompt(
|
|
58
|
+
"Check the API",
|
|
59
|
+
"Test 1: Ambiguous service reference"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Test 2: Specific service (no clarification)
|
|
63
|
+
demo_prompt(
|
|
64
|
+
"Check tcm-api service status",
|
|
65
|
+
"Test 2: Specific service (should NOT need clarification)"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Test 3: Environment mismatch
|
|
69
|
+
demo_prompt(
|
|
70
|
+
"Deploy to production",
|
|
71
|
+
"Test 3: Environment mismatch warning"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Test 4: Namespace ambiguity
|
|
75
|
+
demo_prompt(
|
|
76
|
+
"Deploy to the cluster",
|
|
77
|
+
"Test 4: Namespace ambiguity"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Test 5: Multiple ambiguities
|
|
81
|
+
demo_prompt(
|
|
82
|
+
"Deploy the API to the cluster",
|
|
83
|
+
"Test 5: Multiple ambiguities (service + namespace)"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Test 6: Spanish prompt
|
|
87
|
+
demo_prompt(
|
|
88
|
+
"Chequea el servicio",
|
|
89
|
+
"Test 6: Spanish keywords"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Test 7: Redis resource
|
|
93
|
+
demo_prompt(
|
|
94
|
+
"Check the Redis instance",
|
|
95
|
+
"Test 7: Resource ambiguity (Redis)"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
print("\n" + "✅" * 35)
|
|
99
|
+
print(" DEMO COMPLETE")
|
|
100
|
+
print("✅" * 35 + "\n")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|