adelie-ai 0.2.6 → 0.2.7
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 -21
- package/adelie/.python-version +1 -1
- package/adelie/__init__.py +16 -16
- package/adelie/agents/__init__.py +1 -1
- package/adelie/agents/analyst_ai.py +247 -247
- package/adelie/agents/coder_ai.py +294 -294
- package/adelie/agents/coder_manager.py +349 -349
- package/adelie/agents/expert_ai.py +547 -547
- package/adelie/agents/inform_ai.py +138 -138
- package/adelie/agents/monitor_ai.py +251 -251
- package/adelie/agents/research_ai.py +224 -224
- package/adelie/agents/reviewer_ai.py +165 -165
- package/adelie/agents/runner_ai.py +597 -597
- package/adelie/agents/scanner_ai.py +380 -380
- package/adelie/agents/tester_ai.py +361 -361
- package/adelie/agents/writer_ai.py +328 -328
- package/adelie/browser_search.py +417 -417
- package/adelie/cli.py +2293 -2273
- package/adelie/command_loader.py +137 -137
- package/adelie/config.py +92 -92
- package/adelie/context_compactor.py +360 -360
- package/adelie/context_engine.py +445 -445
- package/adelie/env_strategy.py +523 -523
- package/adelie/feedback_queue.py +159 -159
- package/adelie/git_ops.py +170 -170
- package/adelie/hooks.py +236 -236
- package/adelie/i18n.py +122 -122
- package/adelie/integrations/__init__.py +1 -1
- package/adelie/integrations/telegram_bot.py +471 -471
- package/adelie/interactive.py +559 -559
- package/adelie/kb/__init__.py +1 -1
- package/adelie/kb/embedding_store.py +249 -249
- package/adelie/kb/retriever.py +313 -313
- package/adelie/llm_client.py +536 -536
- package/adelie/log_rotation.py +67 -67
- package/adelie/loop_detector.py +554 -554
- package/adelie/loop_manual.md +141 -141
- package/adelie/main.py +6 -6
- package/adelie/metrics.py +307 -307
- package/adelie/orchestrator.py +1514 -1514
- package/adelie/phases.py +305 -305
- package/adelie/plan_mode.py +245 -245
- package/adelie/process_supervisor.py +351 -351
- package/adelie/project_context.py +217 -217
- package/adelie/prompt_loader.py +175 -175
- package/adelie/prompts/coder.md +30 -30
- package/adelie/prompts/expert.md +104 -104
- package/adelie/prompts/reviewer.md +32 -32
- package/adelie/pyproject.toml +15 -15
- package/adelie/registry.py +88 -88
- package/adelie/rules_loader.py +112 -112
- package/adelie/sandbox.py +388 -388
- package/adelie/scheduler.py +265 -265
- package/adelie/skill_manager.py +422 -422
- package/adelie/spec_chunker.py +241 -241
- package/adelie/spec_loader.py +384 -384
- package/adelie/tool_registry.py +369 -369
- package/adelie/ui_logger.py +337 -337
- package/adelie/utils/dep_sync.py +193 -193
- package/adelie/utils/import_checker.py +279 -279
- package/adelie/web_search.py +245 -245
- package/bin/adelie.js +84 -84
- package/package.json +46 -46
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 kimhyunbin
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kimhyunbin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/adelie/.python-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.10
|
|
1
|
+
3.10
|
package/adelie/__init__.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"""Adelie — Self-communicating autonomous AI loop system."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path as _Path
|
|
4
|
-
import json as _json
|
|
5
|
-
|
|
6
|
-
def _get_version() -> str:
|
|
7
|
-
"""Read version from package.json (single source of truth)."""
|
|
8
|
-
try:
|
|
9
|
-
pkg = _Path(__file__).resolve().parent.parent / "package.json"
|
|
10
|
-
if pkg.exists():
|
|
11
|
-
return _json.loads(pkg.read_text(encoding="utf-8")).get("version", "0.0.0")
|
|
12
|
-
except Exception:
|
|
13
|
-
pass
|
|
14
|
-
return "0.0.0"
|
|
15
|
-
|
|
16
|
-
__version__ =
|
|
1
|
+
"""Adelie — Self-communicating autonomous AI loop system."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path as _Path
|
|
4
|
+
import json as _json
|
|
5
|
+
|
|
6
|
+
def _get_version() -> str:
|
|
7
|
+
"""Read version from package.json (single source of truth)."""
|
|
8
|
+
try:
|
|
9
|
+
pkg = _Path(__file__).resolve().parent.parent / "package.json"
|
|
10
|
+
if pkg.exists():
|
|
11
|
+
return _json.loads(pkg.read_text(encoding="utf-8")).get("version", "0.0.0")
|
|
12
|
+
except Exception:
|
|
13
|
+
pass
|
|
14
|
+
return "0.0.0"
|
|
15
|
+
|
|
16
|
+
__version__ = "0.2.7"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"""adelie/agents package."""
|
|
1
|
+
"""adelie/agents package."""
|
|
@@ -1,247 +1,247 @@
|
|
|
1
|
-
"""
|
|
2
|
-
adelie/agents/analyst_ai.py
|
|
3
|
-
|
|
4
|
-
Analyst AI — strategic analysis for market fit, revenue, and growth.
|
|
5
|
-
|
|
6
|
-
Uses LLM to analyze the entire KB and produce actionable reports:
|
|
7
|
-
- Market analysis & competitive landscape
|
|
8
|
-
- Revenue strategy & monetization optimization
|
|
9
|
-
- Growth opportunities & feature prioritization
|
|
10
|
-
- User feedback synthesis (when available)
|
|
11
|
-
|
|
12
|
-
Reports saved to .adelie/analysis/
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import re
|
|
19
|
-
from datetime import datetime
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
from rich.console import Console
|
|
23
|
-
|
|
24
|
-
from adelie.config import WORKSPACE_PATH
|
|
25
|
-
from adelie.kb import retriever
|
|
26
|
-
from adelie.llm_client import generate
|
|
27
|
-
|
|
28
|
-
console = Console()
|
|
29
|
-
|
|
30
|
-
ANALYSIS_ROOT = WORKSPACE_PATH.parent / "analysis"
|
|
31
|
-
|
|
32
|
-
SYSTEM_PROMPT = """You are Analyst AI — a business strategist and market analyst in an autonomous AI loop.
|
|
33
|
-
|
|
34
|
-
You receive the project's entire Knowledge Base and must produce strategic analysis.
|
|
35
|
-
|
|
36
|
-
Output a single valid JSON object:
|
|
37
|
-
{
|
|
38
|
-
"market_analysis": {
|
|
39
|
-
"target_market": "Description of target market and users",
|
|
40
|
-
"competitors": ["competitor1", "competitor2"],
|
|
41
|
-
"differentiators": ["unique value prop 1", "unique value prop 2"],
|
|
42
|
-
"market_size_estimate": "small/medium/large with reasoning"
|
|
43
|
-
},
|
|
44
|
-
"revenue_strategy": {
|
|
45
|
-
"model": "SaaS subscription / freemium / one-time / etc",
|
|
46
|
-
"pricing_tiers": [
|
|
47
|
-
{"name": "Free", "price": "$0", "features": ["feature1"]},
|
|
48
|
-
{"name": "Pro", "price": "$X/mo", "features": ["feature1", "feature2"]}
|
|
49
|
-
],
|
|
50
|
-
"revenue_timeline": "When to expect first revenue"
|
|
51
|
-
},
|
|
52
|
-
"growth_opportunities": [
|
|
53
|
-
{
|
|
54
|
-
"opportunity": "Description",
|
|
55
|
-
"effort": "low/medium/high",
|
|
56
|
-
"impact": "low/medium/high",
|
|
57
|
-
"priority": 1
|
|
58
|
-
}
|
|
59
|
-
],
|
|
60
|
-
"risks": [
|
|
61
|
-
{
|
|
62
|
-
"risk": "Description",
|
|
63
|
-
"severity": "low/medium/high",
|
|
64
|
-
"mitigation": "How to address"
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
"recommendations": ["Top 3 actionable recommendations"],
|
|
68
|
-
"overall_health": "Score 1-10 with brief explanation"
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
RULES:
|
|
72
|
-
- Base analysis on actual KB content — don't invent product details
|
|
73
|
-
- Be realistic about market size and revenue projections
|
|
74
|
-
- Prioritize actionable, specific recommendations over generic advice
|
|
75
|
-
- Consider the current project phase when making recommendations
|
|
76
|
-
- Identify concrete risks, not hypothetical concerns
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def run_analysis(
|
|
81
|
-
analysis_type: str = "full",
|
|
82
|
-
) -> dict:
|
|
83
|
-
"""
|
|
84
|
-
Run strategic analysis based on the KB.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
analysis_type: "full", "market", "revenue", or "growth"
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
Analysis result dict.
|
|
91
|
-
"""
|
|
92
|
-
console.print(f"[bold magenta]📊 Analyst AI[/bold magenta] — {analysis_type} analysis")
|
|
93
|
-
|
|
94
|
-
# Read entire KB for context
|
|
95
|
-
kb_content = []
|
|
96
|
-
for cat_dir in WORKSPACE_PATH.iterdir():
|
|
97
|
-
if not cat_dir.is_dir():
|
|
98
|
-
continue
|
|
99
|
-
for f in sorted(cat_dir.glob("*.md")):
|
|
100
|
-
try:
|
|
101
|
-
content = f.read_text(encoding="utf-8")
|
|
102
|
-
rel = f.relative_to(WORKSPACE_PATH)
|
|
103
|
-
kb_content.append(f"--- {rel} ---\n{content[:1000]}")
|
|
104
|
-
except Exception:
|
|
105
|
-
pass
|
|
106
|
-
|
|
107
|
-
if not kb_content:
|
|
108
|
-
console.print("[dim] No KB content to analyze.[/dim]")
|
|
109
|
-
return {}
|
|
110
|
-
|
|
111
|
-
# Also check for coder logs
|
|
112
|
-
coder_root = WORKSPACE_PATH.parent / "coder"
|
|
113
|
-
if coder_root.exists():
|
|
114
|
-
for log_file in coder_root.rglob("log.md"):
|
|
115
|
-
try:
|
|
116
|
-
content = log_file.read_text(encoding="utf-8")
|
|
117
|
-
rel = log_file.relative_to(WORKSPACE_PATH.parent)
|
|
118
|
-
kb_content.append(f"--- {rel} ---\n{content[:500]}")
|
|
119
|
-
except Exception:
|
|
120
|
-
pass
|
|
121
|
-
|
|
122
|
-
# Also check for test results and health reports
|
|
123
|
-
for subdir in ["tests/results", "monitor", "reviews"]:
|
|
124
|
-
check_dir = WORKSPACE_PATH.parent / subdir
|
|
125
|
-
if check_dir.exists():
|
|
126
|
-
for f in sorted(check_dir.glob("*.md"))[-3:]: # Last 3
|
|
127
|
-
try:
|
|
128
|
-
content = f.read_text(encoding="utf-8")
|
|
129
|
-
rel = f.relative_to(WORKSPACE_PATH.parent)
|
|
130
|
-
kb_content.append(f"--- {rel} ---\n{content[:500]}")
|
|
131
|
-
except Exception:
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
user_prompt = (
|
|
135
|
-
f"## Analysis Type: {analysis_type}\n\n"
|
|
136
|
-
f"## Knowledge Base Content\n\n"
|
|
137
|
-
+ "\n\n".join(kb_content)
|
|
138
|
-
+ "\n\nPerform the analysis. Output a JSON object."
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
raw = generate(
|
|
143
|
-
system_prompt=SYSTEM_PROMPT,
|
|
144
|
-
user_prompt=user_prompt,
|
|
145
|
-
temperature=0.4,
|
|
146
|
-
)
|
|
147
|
-
except Exception as e:
|
|
148
|
-
console.print(f"[red]❌ Analyst AI LLM error: {e}[/red]")
|
|
149
|
-
return {}
|
|
150
|
-
|
|
151
|
-
# Parse
|
|
152
|
-
try:
|
|
153
|
-
result = json.loads(raw)
|
|
154
|
-
except json.JSONDecodeError:
|
|
155
|
-
match = re.search(r"\{.*\}", raw, re.DOTALL)
|
|
156
|
-
if match:
|
|
157
|
-
try:
|
|
158
|
-
result = json.loads(match.group())
|
|
159
|
-
except json.JSONDecodeError:
|
|
160
|
-
console.print("[yellow]⚠️ Analyst AI — invalid JSON[/yellow]")
|
|
161
|
-
return {}
|
|
162
|
-
else:
|
|
163
|
-
return {}
|
|
164
|
-
|
|
165
|
-
# Display key findings
|
|
166
|
-
health = result.get("overall_health", "N/A")
|
|
167
|
-
console.print(f" Project Health: {health}")
|
|
168
|
-
|
|
169
|
-
recommendations = result.get("recommendations", [])
|
|
170
|
-
if recommendations:
|
|
171
|
-
console.print(" Top Recommendations:")
|
|
172
|
-
for i, rec in enumerate(recommendations[:3], 1):
|
|
173
|
-
console.print(f" {i}. {rec}")
|
|
174
|
-
|
|
175
|
-
growth = result.get("growth_opportunities", [])
|
|
176
|
-
if growth:
|
|
177
|
-
top = sorted(growth, key=lambda x: x.get("priority", 99))[:3]
|
|
178
|
-
console.print(f" Growth Opportunities: {len(growth)} identified")
|
|
179
|
-
|
|
180
|
-
# Save report
|
|
181
|
-
ANALYSIS_ROOT.mkdir(parents=True, exist_ok=True)
|
|
182
|
-
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
183
|
-
report_path = ANALYSIS_ROOT / f"{analysis_type}_{ts}.md"
|
|
184
|
-
|
|
185
|
-
report = (
|
|
186
|
-
f"# {analysis_type.title()} Analysis — "
|
|
187
|
-
f"{datetime.now().isoformat(timespec='seconds')}\n\n"
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
# Market
|
|
191
|
-
market = result.get("market_analysis", {})
|
|
192
|
-
if market:
|
|
193
|
-
report += (
|
|
194
|
-
f"## Market Analysis\n"
|
|
195
|
-
f"- **Target Market**: {market.get('target_market', 'N/A')}\n"
|
|
196
|
-
f"- **Market Size**: {market.get('market_size_estimate', 'N/A')}\n"
|
|
197
|
-
f"- **Competitors**: {', '.join(market.get('competitors', []))}\n"
|
|
198
|
-
f"- **Differentiators**: {', '.join(market.get('differentiators', []))}\n\n"
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
# Revenue
|
|
202
|
-
revenue = result.get("revenue_strategy", {})
|
|
203
|
-
if revenue:
|
|
204
|
-
report += (
|
|
205
|
-
f"## Revenue Strategy\n"
|
|
206
|
-
f"- **Model**: {revenue.get('model', 'N/A')}\n"
|
|
207
|
-
f"- **Timeline**: {revenue.get('revenue_timeline', 'N/A')}\n"
|
|
208
|
-
)
|
|
209
|
-
tiers = revenue.get("pricing_tiers", [])
|
|
210
|
-
if tiers:
|
|
211
|
-
report += "- **Pricing**:\n"
|
|
212
|
-
for t in tiers:
|
|
213
|
-
report += f" - {t.get('name', '?')}: {t.get('price', '?')} — {', '.join(t.get('features', []))}\n"
|
|
214
|
-
report += "\n"
|
|
215
|
-
|
|
216
|
-
# Growth
|
|
217
|
-
if growth:
|
|
218
|
-
report += "## Growth Opportunities\n"
|
|
219
|
-
for g in growth:
|
|
220
|
-
report += (
|
|
221
|
-
f"- **{g.get('opportunity', '?')}** "
|
|
222
|
-
f"(effort: {g.get('effort', '?')}, impact: {g.get('impact', '?')}, "
|
|
223
|
-
f"priority: {g.get('priority', '?')})\n"
|
|
224
|
-
)
|
|
225
|
-
report += "\n"
|
|
226
|
-
|
|
227
|
-
# Risks
|
|
228
|
-
risks = result.get("risks", [])
|
|
229
|
-
if risks:
|
|
230
|
-
report += "## Risks\n"
|
|
231
|
-
for r in risks:
|
|
232
|
-
report += (
|
|
233
|
-
f"- **{r.get('risk', '?')}** [{r.get('severity', '?')}] — "
|
|
234
|
-
f"Mitigation: {r.get('mitigation', '?')}\n"
|
|
235
|
-
)
|
|
236
|
-
report += "\n"
|
|
237
|
-
|
|
238
|
-
# Recommendations
|
|
239
|
-
if recommendations:
|
|
240
|
-
report += "## Recommendations\n"
|
|
241
|
-
for i, rec in enumerate(recommendations, 1):
|
|
242
|
-
report += f"{i}. {rec}\n"
|
|
243
|
-
|
|
244
|
-
report_path.write_text(report, encoding="utf-8")
|
|
245
|
-
console.print(f"[bold magenta]📊 Analyst AI[/bold magenta] — report saved")
|
|
246
|
-
|
|
247
|
-
return result
|
|
1
|
+
"""
|
|
2
|
+
adelie/agents/analyst_ai.py
|
|
3
|
+
|
|
4
|
+
Analyst AI — strategic analysis for market fit, revenue, and growth.
|
|
5
|
+
|
|
6
|
+
Uses LLM to analyze the entire KB and produce actionable reports:
|
|
7
|
+
- Market analysis & competitive landscape
|
|
8
|
+
- Revenue strategy & monetization optimization
|
|
9
|
+
- Growth opportunities & feature prioritization
|
|
10
|
+
- User feedback synthesis (when available)
|
|
11
|
+
|
|
12
|
+
Reports saved to .adelie/analysis/
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
|
|
24
|
+
from adelie.config import WORKSPACE_PATH
|
|
25
|
+
from adelie.kb import retriever
|
|
26
|
+
from adelie.llm_client import generate
|
|
27
|
+
|
|
28
|
+
console = Console()
|
|
29
|
+
|
|
30
|
+
ANALYSIS_ROOT = WORKSPACE_PATH.parent / "analysis"
|
|
31
|
+
|
|
32
|
+
SYSTEM_PROMPT = """You are Analyst AI — a business strategist and market analyst in an autonomous AI loop.
|
|
33
|
+
|
|
34
|
+
You receive the project's entire Knowledge Base and must produce strategic analysis.
|
|
35
|
+
|
|
36
|
+
Output a single valid JSON object:
|
|
37
|
+
{
|
|
38
|
+
"market_analysis": {
|
|
39
|
+
"target_market": "Description of target market and users",
|
|
40
|
+
"competitors": ["competitor1", "competitor2"],
|
|
41
|
+
"differentiators": ["unique value prop 1", "unique value prop 2"],
|
|
42
|
+
"market_size_estimate": "small/medium/large with reasoning"
|
|
43
|
+
},
|
|
44
|
+
"revenue_strategy": {
|
|
45
|
+
"model": "SaaS subscription / freemium / one-time / etc",
|
|
46
|
+
"pricing_tiers": [
|
|
47
|
+
{"name": "Free", "price": "$0", "features": ["feature1"]},
|
|
48
|
+
{"name": "Pro", "price": "$X/mo", "features": ["feature1", "feature2"]}
|
|
49
|
+
],
|
|
50
|
+
"revenue_timeline": "When to expect first revenue"
|
|
51
|
+
},
|
|
52
|
+
"growth_opportunities": [
|
|
53
|
+
{
|
|
54
|
+
"opportunity": "Description",
|
|
55
|
+
"effort": "low/medium/high",
|
|
56
|
+
"impact": "low/medium/high",
|
|
57
|
+
"priority": 1
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"risks": [
|
|
61
|
+
{
|
|
62
|
+
"risk": "Description",
|
|
63
|
+
"severity": "low/medium/high",
|
|
64
|
+
"mitigation": "How to address"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"recommendations": ["Top 3 actionable recommendations"],
|
|
68
|
+
"overall_health": "Score 1-10 with brief explanation"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
RULES:
|
|
72
|
+
- Base analysis on actual KB content — don't invent product details
|
|
73
|
+
- Be realistic about market size and revenue projections
|
|
74
|
+
- Prioritize actionable, specific recommendations over generic advice
|
|
75
|
+
- Consider the current project phase when making recommendations
|
|
76
|
+
- Identify concrete risks, not hypothetical concerns
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def run_analysis(
|
|
81
|
+
analysis_type: str = "full",
|
|
82
|
+
) -> dict:
|
|
83
|
+
"""
|
|
84
|
+
Run strategic analysis based on the KB.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
analysis_type: "full", "market", "revenue", or "growth"
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Analysis result dict.
|
|
91
|
+
"""
|
|
92
|
+
console.print(f"[bold magenta]📊 Analyst AI[/bold magenta] — {analysis_type} analysis")
|
|
93
|
+
|
|
94
|
+
# Read entire KB for context
|
|
95
|
+
kb_content = []
|
|
96
|
+
for cat_dir in WORKSPACE_PATH.iterdir():
|
|
97
|
+
if not cat_dir.is_dir():
|
|
98
|
+
continue
|
|
99
|
+
for f in sorted(cat_dir.glob("*.md")):
|
|
100
|
+
try:
|
|
101
|
+
content = f.read_text(encoding="utf-8")
|
|
102
|
+
rel = f.relative_to(WORKSPACE_PATH)
|
|
103
|
+
kb_content.append(f"--- {rel} ---\n{content[:1000]}")
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
if not kb_content:
|
|
108
|
+
console.print("[dim] No KB content to analyze.[/dim]")
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
# Also check for coder logs
|
|
112
|
+
coder_root = WORKSPACE_PATH.parent / "coder"
|
|
113
|
+
if coder_root.exists():
|
|
114
|
+
for log_file in coder_root.rglob("log.md"):
|
|
115
|
+
try:
|
|
116
|
+
content = log_file.read_text(encoding="utf-8")
|
|
117
|
+
rel = log_file.relative_to(WORKSPACE_PATH.parent)
|
|
118
|
+
kb_content.append(f"--- {rel} ---\n{content[:500]}")
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
# Also check for test results and health reports
|
|
123
|
+
for subdir in ["tests/results", "monitor", "reviews"]:
|
|
124
|
+
check_dir = WORKSPACE_PATH.parent / subdir
|
|
125
|
+
if check_dir.exists():
|
|
126
|
+
for f in sorted(check_dir.glob("*.md"))[-3:]: # Last 3
|
|
127
|
+
try:
|
|
128
|
+
content = f.read_text(encoding="utf-8")
|
|
129
|
+
rel = f.relative_to(WORKSPACE_PATH.parent)
|
|
130
|
+
kb_content.append(f"--- {rel} ---\n{content[:500]}")
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
user_prompt = (
|
|
135
|
+
f"## Analysis Type: {analysis_type}\n\n"
|
|
136
|
+
f"## Knowledge Base Content\n\n"
|
|
137
|
+
+ "\n\n".join(kb_content)
|
|
138
|
+
+ "\n\nPerform the analysis. Output a JSON object."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
raw = generate(
|
|
143
|
+
system_prompt=SYSTEM_PROMPT,
|
|
144
|
+
user_prompt=user_prompt,
|
|
145
|
+
temperature=0.4,
|
|
146
|
+
)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
console.print(f"[red]❌ Analyst AI LLM error: {e}[/red]")
|
|
149
|
+
return {}
|
|
150
|
+
|
|
151
|
+
# Parse
|
|
152
|
+
try:
|
|
153
|
+
result = json.loads(raw)
|
|
154
|
+
except json.JSONDecodeError:
|
|
155
|
+
match = re.search(r"\{.*\}", raw, re.DOTALL)
|
|
156
|
+
if match:
|
|
157
|
+
try:
|
|
158
|
+
result = json.loads(match.group())
|
|
159
|
+
except json.JSONDecodeError:
|
|
160
|
+
console.print("[yellow]⚠️ Analyst AI — invalid JSON[/yellow]")
|
|
161
|
+
return {}
|
|
162
|
+
else:
|
|
163
|
+
return {}
|
|
164
|
+
|
|
165
|
+
# Display key findings
|
|
166
|
+
health = result.get("overall_health", "N/A")
|
|
167
|
+
console.print(f" Project Health: {health}")
|
|
168
|
+
|
|
169
|
+
recommendations = result.get("recommendations", [])
|
|
170
|
+
if recommendations:
|
|
171
|
+
console.print(" Top Recommendations:")
|
|
172
|
+
for i, rec in enumerate(recommendations[:3], 1):
|
|
173
|
+
console.print(f" {i}. {rec}")
|
|
174
|
+
|
|
175
|
+
growth = result.get("growth_opportunities", [])
|
|
176
|
+
if growth:
|
|
177
|
+
top = sorted(growth, key=lambda x: x.get("priority", 99))[:3]
|
|
178
|
+
console.print(f" Growth Opportunities: {len(growth)} identified")
|
|
179
|
+
|
|
180
|
+
# Save report
|
|
181
|
+
ANALYSIS_ROOT.mkdir(parents=True, exist_ok=True)
|
|
182
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
183
|
+
report_path = ANALYSIS_ROOT / f"{analysis_type}_{ts}.md"
|
|
184
|
+
|
|
185
|
+
report = (
|
|
186
|
+
f"# {analysis_type.title()} Analysis — "
|
|
187
|
+
f"{datetime.now().isoformat(timespec='seconds')}\n\n"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Market
|
|
191
|
+
market = result.get("market_analysis", {})
|
|
192
|
+
if market:
|
|
193
|
+
report += (
|
|
194
|
+
f"## Market Analysis\n"
|
|
195
|
+
f"- **Target Market**: {market.get('target_market', 'N/A')}\n"
|
|
196
|
+
f"- **Market Size**: {market.get('market_size_estimate', 'N/A')}\n"
|
|
197
|
+
f"- **Competitors**: {', '.join(market.get('competitors', []))}\n"
|
|
198
|
+
f"- **Differentiators**: {', '.join(market.get('differentiators', []))}\n\n"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Revenue
|
|
202
|
+
revenue = result.get("revenue_strategy", {})
|
|
203
|
+
if revenue:
|
|
204
|
+
report += (
|
|
205
|
+
f"## Revenue Strategy\n"
|
|
206
|
+
f"- **Model**: {revenue.get('model', 'N/A')}\n"
|
|
207
|
+
f"- **Timeline**: {revenue.get('revenue_timeline', 'N/A')}\n"
|
|
208
|
+
)
|
|
209
|
+
tiers = revenue.get("pricing_tiers", [])
|
|
210
|
+
if tiers:
|
|
211
|
+
report += "- **Pricing**:\n"
|
|
212
|
+
for t in tiers:
|
|
213
|
+
report += f" - {t.get('name', '?')}: {t.get('price', '?')} — {', '.join(t.get('features', []))}\n"
|
|
214
|
+
report += "\n"
|
|
215
|
+
|
|
216
|
+
# Growth
|
|
217
|
+
if growth:
|
|
218
|
+
report += "## Growth Opportunities\n"
|
|
219
|
+
for g in growth:
|
|
220
|
+
report += (
|
|
221
|
+
f"- **{g.get('opportunity', '?')}** "
|
|
222
|
+
f"(effort: {g.get('effort', '?')}, impact: {g.get('impact', '?')}, "
|
|
223
|
+
f"priority: {g.get('priority', '?')})\n"
|
|
224
|
+
)
|
|
225
|
+
report += "\n"
|
|
226
|
+
|
|
227
|
+
# Risks
|
|
228
|
+
risks = result.get("risks", [])
|
|
229
|
+
if risks:
|
|
230
|
+
report += "## Risks\n"
|
|
231
|
+
for r in risks:
|
|
232
|
+
report += (
|
|
233
|
+
f"- **{r.get('risk', '?')}** [{r.get('severity', '?')}] — "
|
|
234
|
+
f"Mitigation: {r.get('mitigation', '?')}\n"
|
|
235
|
+
)
|
|
236
|
+
report += "\n"
|
|
237
|
+
|
|
238
|
+
# Recommendations
|
|
239
|
+
if recommendations:
|
|
240
|
+
report += "## Recommendations\n"
|
|
241
|
+
for i, rec in enumerate(recommendations, 1):
|
|
242
|
+
report += f"{i}. {rec}\n"
|
|
243
|
+
|
|
244
|
+
report_path.write_text(report, encoding="utf-8")
|
|
245
|
+
console.print(f"[bold magenta]📊 Analyst AI[/bold magenta] — report saved")
|
|
246
|
+
|
|
247
|
+
return result
|