trip-optimizer 0.1.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/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # trip-optimizer
2
+
3
+ Autonomously optimize travel plans using the autoresearch pattern -- an AI-powered CLI that researches, scores, and iteratively improves your itinerary.
4
+
5
+ 支持 **English** 和 **中文(简体中文)** -- 在初始化时选择语言,整个体验随之适配:提示语、生成的行程、研究搜索、评分系统全部使用您选择的语言。
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g trip-optimizer
11
+ ```
12
+
13
+ Or run directly:
14
+
15
+ ```bash
16
+ npx trip-optimizer
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ trip-optimizer init "Japan 2027"
23
+ cd japan-2027
24
+ trip-optimizer run # agent mode (default, interactive Claude Code)
25
+ trip-optimizer run --standalone # direct API calls
26
+ trip-optimizer run --headless # agent mode, non-interactive
27
+ trip-optimizer dashboard --watch
28
+ trip-optimizer plan --pdf # generate a formatted PDF itinerary
29
+ ```
30
+
31
+ ## Commands
32
+
33
+ | Command | Description |
34
+ |---------|-------------|
35
+ | `init <name>` | Create a new trip project |
36
+ | `config` | Manage API keys and settings |
37
+ | `profile` | View travel profile |
38
+ | `score` | One-off absolute scoring |
39
+ | `research [city]` | Research sprint |
40
+ | `run` | Start optimization loop (agent mode) |
41
+ | `run --standalone` | Optimization via direct API calls |
42
+ | `run --headless` | Agent mode, non-interactive |
43
+ | `status` | Show progress |
44
+ | `dashboard` | Live optimization dashboard |
45
+ | `chart` | ASCII score chart |
46
+ | `plan` | Pretty-print travel plan |
47
+ | `plan --pdf` | Generate a PDF document |
48
+ | `debrief` | Post-trip feedback |
49
+ | `history` | View past trips |
50
+
51
+ ## How It Works
52
+
53
+ Trip-optimizer follows the **autoresearch pattern**: it autonomously researches destinations, generates plan mutations, scores results, and keeps only improvements. Each optimization iteration proposes targeted changes -- swapping a restaurant, adjusting timing, adding a hidden-gem activity -- then evaluates whether the change improved the overall plan. Bad mutations are discarded; good ones accumulate.
54
+
55
+ Scoring uses a **3-pass pipeline**. First, the plan is evaluated across seven weighted dimensions (experience, logistics, food, time management, budget, accommodation, and transit). Then an adversarial critic searches for concrete flaws -- unconfirmed bookings, chain restaurants, vague transit -- and applies penalties. Finally, a holistic adjustment reconciles the dimension scores with the critic's findings into a single composite score.
56
+
57
+ The system builds **persistent memory** across trips. After each trip, a debrief captures what worked and what didn't. These learnings are stored in `learned.json` and feed back into scoring rubrics and research priorities for future trips, so the optimizer gets smarter over time.
58
+
59
+ ### 语言与本地化
60
+
61
+ `init` 的第一个问题是语言选择。选择 **中文** 后:
62
+
63
+ - 所有命令行提示和消息以中文显示
64
+ - 生成的行程、评分标准和计划均以简体中文撰写
65
+ - 研究优先使用中文平台(小红书、大众点评、马蜂窝、携程),而非英文来源
66
+ - 搜索关键词使用中文(本地人推荐、避雷指南、苍蝇馆子),同时辅以英文补充搜索
67
+ - PDF 输出正确渲染中文内容
68
+
69
+ ### Custom Model Support
70
+
71
+ During `init`, you can optionally configure a custom LLM instead of the default Anthropic/Vertex provider. Any OpenAI-compatible API works -- Kimi (Moonshot), DeepSeek, and others. Custom models run in `--standalone` mode; agent mode always uses Claude Code.
72
+
73
+ ## Requirements
74
+
75
+ - Node.js 22+
76
+ - One of:
77
+ - Anthropic API key (via `trip-optimizer config` or `ANTHROPIC_API_KEY`)
78
+ - Google Cloud Vertex AI (`CLAUDE_CODE_USE_VERTEX=1` + `GOOGLE_CLOUD_PROJECT`)
79
+ - Custom OpenAI-compatible API (configured during `init`)
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/data/config.ts
4
+ import fs from "fs";
5
+ import path2 from "path";
6
+
7
+ // src/data/paths.ts
8
+ import path from "path";
9
+ import os from "os";
10
+ function getGlobalDir() {
11
+ return path.join(os.homedir(), ".trip-optimizer");
12
+ }
13
+ function getTripHistoryPath() {
14
+ return path.join(getGlobalDir(), "trip-history.json");
15
+ }
16
+ function getLearnedPath() {
17
+ return path.join(getGlobalDir(), "learned.json");
18
+ }
19
+
20
+ // src/data/config.ts
21
+ var DEFAULT_CONFIG = {
22
+ provider: "anthropic",
23
+ api_key: "",
24
+ language: "en"
25
+ };
26
+ function loadConfig(dir) {
27
+ const configPath = path2.join(dir ?? getGlobalDir(), "config.json");
28
+ if (!fs.existsSync(configPath)) return { ...DEFAULT_CONFIG };
29
+ const loaded = JSON.parse(fs.readFileSync(configPath, "utf-8"));
30
+ return { ...DEFAULT_CONFIG, ...loaded };
31
+ }
32
+ function saveConfig(config, dir) {
33
+ const d = dir ?? getGlobalDir();
34
+ fs.mkdirSync(d, { recursive: true });
35
+ fs.writeFileSync(path2.join(d, "config.json"), JSON.stringify(config, null, 2));
36
+ }
37
+
38
+ // src/i18n.ts
39
+ var messages = {
40
+ // Init flow
41
+ "init.title": { en: "trip-optimizer", zh: "trip-optimizer" },
42
+ "init.language": { en: "Language / \u8BED\u8A00:", zh: "\u8BED\u8A00 / Language:" },
43
+ "init.first_time": { en: "First time? Let's set up your profile.", zh: "\u9996\u6B21\u4F7F\u7528\uFF1F\u8BA9\u6211\u4EEC\u8BBE\u7F6E\u60A8\u7684\u4E2A\u4EBA\u8D44\u6599\u3002" },
44
+ "init.vertex_detected": { en: "Using Vertex AI (detected from environment).", zh: "\u4F7F\u7528 Vertex AI\uFF08\u4ECE\u73AF\u5883\u53D8\u91CF\u68C0\u6D4B\uFF09\u3002" },
45
+ "init.api_key": { en: "Anthropic API key:", zh: "Anthropic API \u5BC6\u94A5\uFF1A" },
46
+ "init.api_key_saved": { en: "API key saved.", zh: "API \u5BC6\u94A5\u5DF2\u4FDD\u5B58\u3002" },
47
+ "init.api_key_required": { en: "API key is required", zh: "API \u5BC6\u94A5\u4E3A\u5FC5\u586B\u9879" },
48
+ "init.model_override": { en: "Use a different LLM? (e.g., Kimi, DeepSeek)", zh: "\u4F7F\u7528\u5176\u4ED6\u5927\u6A21\u578B\uFF1F\uFF08\u5982 Kimi\u3001DeepSeek\uFF09" },
49
+ "init.model_override_yes": { en: "Yes, configure a custom model", zh: "\u662F\uFF0C\u914D\u7F6E\u81EA\u5B9A\u4E49\u6A21\u578B" },
50
+ "init.model_override_no": { en: "No, use default", zh: "\u5426\uFF0C\u4F7F\u7528\u9ED8\u8BA4" },
51
+ "init.model_name": { en: "Model name (e.g., moonshot-v1-128k):", zh: "\u6A21\u578B\u540D\u79F0\uFF08\u5982 moonshot-v1-128k\uFF09\uFF1A" },
52
+ "init.model_base_url": { en: "API base URL (e.g., https://api.moonshot.cn/v1):", zh: "API \u5730\u5740\uFF08\u5982 https://api.moonshot.cn/v1\uFF09\uFF1A" },
53
+ "init.model_api_key": { en: "API key for this model:", zh: "\u8BE5\u6A21\u578B\u7684 API \u5BC6\u94A5\uFF1A" },
54
+ "init.model_saved": { en: "Custom model configured.", zh: "\u81EA\u5B9A\u4E49\u6A21\u578B\u5DF2\u914D\u7F6E\u3002" },
55
+ "init.model_removed": { en: "Custom model removed. Using default provider.", zh: "\u81EA\u5B9A\u4E49\u6A21\u578B\u5DF2\u79FB\u9664\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u63D0\u4F9B\u8005\u3002" },
56
+ "init.model_keep_or_change": { en: "Custom model settings:", zh: "\u81EA\u5B9A\u4E49\u6A21\u578B\u8BBE\u7F6E\uFF1A" },
57
+ "init.model_keep": { en: "Keep current settings", zh: "\u4FDD\u6301\u5F53\u524D\u8BBE\u7F6E" },
58
+ "init.model_edit": { en: "Edit model / API key / URL", zh: "\u7F16\u8F91\u6A21\u578B / API \u5BC6\u94A5 / \u5730\u5740" },
59
+ "init.model_remove": { en: "Remove custom model (use default)", zh: "\u79FB\u9664\u81EA\u5B9A\u4E49\u6A21\u578B\uFF08\u4F7F\u7528\u9ED8\u8BA4\uFF09" },
60
+ "init.model_note": { en: "Note: Custom models only work in standalone mode (trip-optimizer run --standalone).", zh: "\u6CE8\u610F\uFF1A\u81EA\u5B9A\u4E49\u6A21\u578B\u4EC5\u5728\u72EC\u7ACB\u6A21\u5F0F\u4E0B\u8FD0\u884C\uFF08trip-optimizer run --standalone\uFF09\u3002" },
61
+ // Profile setup
62
+ "profile.loyalty": { en: "Hotel loyalty program:", zh: "\u9152\u5E97\u4F1A\u5458\u8BA1\u5212\uFF1A" },
63
+ "profile.dietary": { en: "Dietary restrictions (Space to select, Enter to confirm):", zh: "\u996E\u98DF\u9650\u5236\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A" },
64
+ // Trip questions
65
+ "trip.start_date": { en: "Start date (YYYY-MM-DD):", zh: "\u51FA\u53D1\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1A" },
66
+ "trip.end_date": { en: "End date (YYYY-MM-DD):", zh: "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1A" },
67
+ "trip.travelers": { en: "Number of travelers:", zh: "\u51FA\u884C\u4EBA\u6570\uFF1A" },
68
+ "trip.origin": { en: "Departing from (city):", zh: "\u51FA\u53D1\u57CE\u5E02\uFF1A" },
69
+ "trip.cities": { en: "Cities in order (comma-separated):", zh: "\u57CE\u5E02\u987A\u5E8F\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1A" },
70
+ "trip.cities_validate": { en: "Enter at least one city", zh: "\u8BF7\u81F3\u5C11\u8F93\u5165\u4E00\u4E2A\u57CE\u5E02" },
71
+ "trip.budget": { en: "Total budget (USD):", zh: "\u603B\u9884\u7B97\uFF08USD\uFF09\uFF1A" },
72
+ "trip.vibes": { en: "Pick your vibes (Space to select, Enter to confirm):", zh: "\u9009\u62E9\u65C5\u884C\u98CE\u683C\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A" },
73
+ "trip.anti_patterns": { en: "Anything to avoid? (comma-separated, or press Enter to skip):", zh: "\u6709\u4EC0\u4E48\u8981\u907F\u514D\u7684\uFF1F\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u6216\u6309\u56DE\u8F66\u8DF3\u8FC7\uFF09\uFF1A" },
74
+ // Vibe choices
75
+ "vibe.wandering": { en: "Wandering & exploring", zh: "\u6F2B\u6B65\u63A2\u7D22" },
76
+ "vibe.food": { en: "Food & culinary", zh: "\u7F8E\u98DF\u4E4B\u65C5" },
77
+ "vibe.culture": { en: "Culture & arts", zh: "\u6587\u5316\u827A\u672F" },
78
+ "vibe.nature": { en: "Nature & outdoors", zh: "\u81EA\u7136\u6237\u5916" },
79
+ "vibe.adventure": { en: "Adventure & thrills", zh: "\u5192\u9669\u523A\u6FC0" },
80
+ "vibe.relaxation": { en: "Relaxation & wellness", zh: "\u4F11\u95F2\u517B\u751F" },
81
+ "vibe.nightlife": { en: "Nightlife & entertainment", zh: "\u591C\u751F\u6D3B\u5A31\u4E50" },
82
+ "vibe.history": { en: "History & heritage", zh: "\u5386\u53F2\u4EBA\u6587" },
83
+ "vibe.shopping": { en: "Shopping", zh: "\u8D2D\u7269" },
84
+ "vibe.family": { en: "Family-friendly", zh: "\u4EB2\u5B50\u6E38" },
85
+ "vibe.romantic": { en: "Romantic", zh: "\u6D6A\u6F2B\u4E4B\u65C5" },
86
+ // Edit flow
87
+ "edit.current_settings": { en: "Current settings:", zh: "\u5F53\u524D\u8BBE\u7F6E\uFF1A" },
88
+ "edit.what_to_do": { en: "What would you like to do?", zh: "\u60A8\u60F3\u505A\u4EC0\u4E48\uFF1F" },
89
+ "edit.regenerate": { en: "Regenerate with same settings", zh: "\u4F7F\u7528\u76F8\u540C\u8BBE\u7F6E\u91CD\u65B0\u751F\u6210" },
90
+ "edit.edit_fields": { en: "Edit specific fields", zh: "\u7F16\u8F91\u7279\u5B9A\u5B57\u6BB5" },
91
+ "edit.restart": { en: "Start over from scratch", zh: "\u4ECE\u5934\u5F00\u59CB" },
92
+ "edit.which_fields": { en: "Which fields to edit? (Space to select, Enter to confirm):", zh: "\u7F16\u8F91\u54EA\u4E9B\u5B57\u6BB5\uFF1F\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A" },
93
+ // Field labels
94
+ "field.dates": { en: "Dates", zh: "\u65E5\u671F" },
95
+ "field.travelers": { en: "Travelers", zh: "\u4EBA\u6570" },
96
+ "field.origin": { en: "Origin", zh: "\u51FA\u53D1\u5730" },
97
+ "field.cities": { en: "Cities", zh: "\u57CE\u5E02" },
98
+ "field.budget": { en: "Budget", zh: "\u9884\u7B97" },
99
+ "field.vibes": { en: "Vibes", zh: "\u98CE\u683C" },
100
+ "field.anti_patterns": { en: "Anti-patterns", zh: "\u907F\u514D\u4E8B\u9879" },
101
+ // Progress
102
+ "progress.generating_rubrics": { en: "Generating scoring rubrics...", zh: "\u6B63\u5728\u751F\u6210\u8BC4\u5206\u6807\u51C6..." },
103
+ "progress.rubrics_done": { en: "Scoring rubrics generated", zh: "\u8BC4\u5206\u6807\u51C6\u5DF2\u751F\u6210" },
104
+ "progress.rubrics_fail": { en: "Failed to generate scoring rubrics", zh: "\u8BC4\u5206\u6807\u51C6\u751F\u6210\u5931\u8D25" },
105
+ "progress.generating_plan": { en: "Generating initial plan...", zh: "\u6B63\u5728\u751F\u6210\u521D\u59CB\u884C\u7A0B..." },
106
+ "progress.plan_done": { en: "Initial plan generated", zh: "\u521D\u59CB\u884C\u7A0B\u5DF2\u751F\u6210" },
107
+ "progress.plan_fail": { en: "Failed to generate initial plan", zh: "\u521D\u59CB\u884C\u7A0B\u751F\u6210\u5931\u8D25" },
108
+ "progress.creating_project": { en: "Creating trip project...", zh: "\u6B63\u5728\u521B\u5EFA\u65C5\u884C\u9879\u76EE..." },
109
+ "progress.project_created": { en: "Trip project created at", zh: "\u65C5\u884C\u9879\u76EE\u5DF2\u521B\u5EFA\u4E8E" },
110
+ // Next steps
111
+ "next.title": { en: "Next steps:", zh: "\u4E0B\u4E00\u6B65\uFF1A" },
112
+ "next.review": { en: "# Review constraints.yaml and rubrics.yaml", zh: "# \u67E5\u770B constraints.yaml \u548C rubrics.yaml" },
113
+ // Errors
114
+ "error.provider_fail": { en: "Failed to create LLM provider", zh: "LLM \u63D0\u4F9B\u8005\u521B\u5EFA\u5931\u8D25" },
115
+ "error.model_check": { en: "Check that your model is available", zh: "\u8BF7\u68C0\u67E5\u60A8\u7684\u6A21\u578B\u662F\u5426\u53EF\u7528" },
116
+ "error.auth_check": { en: 'Check your authentication: run "gcloud auth application-default login"', zh: '\u8BF7\u68C0\u67E5\u8BA4\u8BC1\uFF1A\u8FD0\u884C "gcloud auth application-default login"' }
117
+ };
118
+ var currentLanguage = "en";
119
+ function setLanguage(lang) {
120
+ currentLanguage = lang;
121
+ }
122
+ function getLanguage() {
123
+ return currentLanguage;
124
+ }
125
+ function t(key) {
126
+ const entry = messages[key];
127
+ return entry[currentLanguage] || entry.en;
128
+ }
129
+ function getLlmLanguageInstruction() {
130
+ if (currentLanguage === "zh") {
131
+ return "\n\nIMPORTANT: Generate ALL output in Chinese (Simplified Chinese / \u7B80\u4F53\u4E2D\u6587). All descriptions, recommendations, and commentary must be written in Chinese.";
132
+ }
133
+ return "";
134
+ }
135
+
136
+ // src/generators/program.ts
137
+ function generateProgram(constraints, config) {
138
+ const hasSearchApi = !!config.search_api?.api_key;
139
+ const isZh = getLanguage() === "zh";
140
+ const researchSources = isZh ? `### \u7814\u7A76\u6765\u6E90\uFF08\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF09
141
+
142
+ 1. **\u6D4F\u89C8\u5668\u7814\u7A76**\uFF08\u5982\u679C agent-browser \u6280\u80FD\u53EF\u7528\uFF09\uFF1A
143
+ - \u4F7F\u7528 \`agent-browser\` \u6280\u80FD\u8BBF\u95EE\u5927\u4F17\u70B9\u8BC4\u3001\u5C0F\u7EA2\u4E66\u3001\u9A6C\u8702\u7A9D\u3001\u643A\u7A0B\u3001Google Maps
144
+ - \u63D0\u53D6\u8BC4\u5206\u3001\u8BC4\u8BBA\u6570\u91CF\u3001\u5F53\u5B63\u83DC\u5355\u3001\u672C\u5730\u4EBA\u63A8\u8350
145
+ - \u8FD9\u662F\u6700\u771F\u5B9E\u7684\u5B9E\u65F6\u6570\u636E\u6765\u6E90
146
+ - \u4F18\u5148\u641C\u7D22\u4E2D\u6587\u5E73\u53F0\uFF08\u5C0F\u7EA2\u4E66\u3001\u5927\u4F17\u70B9\u8BC4\u3001\u9A6C\u8702\u7A9D\uFF09\uFF0C\u56E0\u4E3A\u672C\u5730\u4EBA\u7684\u63A8\u8350\u66F4\u53EF\u9760
147
+
148
+ ${hasSearchApi ? `2. **\u7F51\u7EDC\u641C\u7D22 API**\uFF08\u5DF2\u914D\u7F6E\uFF09\uFF1A
149
+ - \u641C\u7D22 "[\u57CE\u5E02] \u672C\u5730\u4EBA\u63A8\u8350 \u9690\u85CF\u7F8E\u98DF"
150
+ - \u641C\u7D22 "[\u57CE\u5E02] \u907F\u96F7 \u8E29\u5751 \u6E38\u5BA2\u9677\u9631"
151
+ - \u641C\u7D22 "[\u57CE\u5E02] \u5C0F\u4F17\u666F\u70B9 \u5F53\u5730\u4EBA\u624D\u77E5\u9053"
152
+ - \u641C\u7D22 "[\u57CE\u5E02] \u5F53\u5B63\u7279\u8272 \u65F6\u4EE4\u7F8E\u98DF"
153
+ - \u540C\u65F6\u641C\u7D22\u4E2D\u82F1\u6587\u5173\u952E\u8BCD\u4EE5\u83B7\u53D6\u66F4\u5168\u9762\u7684\u7ED3\u679C
154
+
155
+ 3. **LLM \u77E5\u8BC6**\uFF08\u5907\u9009\uFF09\uFF1A` : `2. **LLM \u77E5\u8BC6**\uFF08\u4E3B\u8981\u6765\u6E90\uFF09\uFF1A
156
+ `} - \u4F7F\u7528\u8BAD\u7EC3\u6570\u636E\u83B7\u53D6\u6D3B\u52A8\u548C\u9910\u5385\u63A8\u8350
157
+ - \u6807\u8BB0\u6765\u6E90\u4E3A "llm_knowledge" \u4EE5\u4FBF\u8BC4\u5206\u5668\u964D\u4F4E\u6743\u91CD
158
+ - \u5BF9\u77E5\u540D\u76EE\u7684\u5730\u6548\u679C\u597D\uFF0C\u5BF9\u51B7\u95E8\u5730\u70B9\u6548\u679C\u8F83\u5DEE` : `### Research Sources (in priority order)
159
+
160
+ 1. **Browser research** (if agent-browser skill is available):
161
+ - Invoke the \`agent-browser\` skill to visit Google Maps, Dianping, Xiaohongshu, TripAdvisor
162
+ - Extract ratings, review counts, seasonal menus
163
+ - This gives real-time, ground-truth data
164
+
165
+ ${hasSearchApi ? `2. **Web search API** (configured):
166
+ - Use WebSearch tool for "[city] hidden gems locals recommend"
167
+ - Search for "[city] overrated tourist traps to skip"
168
+ - Search for "[city] best street food locals recommend"
169
+ - Search for seasonal factors
170
+
171
+ 3. **LLM knowledge** (fallback):` : `2. **LLM knowledge** (primary):
172
+ `} - Use training data for activity and restaurant recommendations
173
+ - Flag entries as source: "llm_knowledge" so scorer weights them lower
174
+ - Good for major destinations, weaker for obscure ones`;
175
+ const researchQueries = isZh ? `### \u7814\u7A76\u641C\u7D22\u5173\u952E\u8BCD
176
+ - "[\u57CE\u5E02] \u672C\u5730\u4EBA\u63A8\u8350 \u5C0F\u4F17\u666F\u70B9"
177
+ - "[\u57CE\u5E02] \u907F\u96F7\u6307\u5357 \u6E38\u5BA2\u9677\u9631"
178
+ - "[\u57CE\u5E02] \u5FC5\u5403\u7F8E\u98DF \u82CD\u8747\u9986\u5B50 \u672C\u5730\u4EBA\u6392\u961F"
179
+ - "[\u57CE\u5E02] \u503C\u5F97\u901B\u7684\u8857\u533A City Walk"
180
+ - "[\u57CE\u5E02] [\u5B63\u8282] \u5F53\u5B63\u7279\u8272 \u65F6\u4EE4\u63A8\u8350"
181
+ - "[\u57CE\u5E02] \u6DF1\u5EA6\u6E38 \u4E0D\u5728\u653B\u7565\u4E0A\u7684\u4F53\u9A8C"
182
+ - "[City] hidden gems locals only"\uFF08\u82F1\u6587\u8865\u5145\u641C\u7D22\uFF09
183
+ - "[City] authentic local food NOT tourist"\uFF08\u82F1\u6587\u8865\u5145\u641C\u7D22\uFF09` : `### Research Query Patterns
184
+ - "[City] hidden gems locals only"
185
+ - "[City] overrated tourist traps to skip"
186
+ - "[City] best street food locals recommend"
187
+ - "[City] atmospheric neighborhoods to walk"
188
+ - "[City] what to do in [season] seasonal"
189
+ - "[City] authentic experiences not on TripAdvisor"`;
190
+ const outputLanguage = isZh ? `
191
+ ## \u8F93\u51FA\u8BED\u8A00
192
+
193
+ \u6240\u6709\u8F93\u51FA\uFF08plan.md\u3001activities_db.json \u4E2D\u7684\u63CF\u8FF0\u3001commit messages\uFF09\u90FD\u5FC5\u987B\u4F7F\u7528\u7B80\u4F53\u4E2D\u6587\u3002
194
+ ` : "";
195
+ return `# trip-optimizer Agent Instructions
196
+
197
+ ## Setup
198
+ 1. Read \`constraints.yaml\` for fixed parameters and preferences
199
+ 2. Read the current \`plan.md\` as baseline
200
+ 3. Read \`activities_db.json\` (may be empty on first run)
201
+ 4. Run scoring on the baseline plan, record as initial score
202
+ 5. Confirm setup, then begin
203
+
204
+ ## Phase 1: Research Sprint
205
+
206
+ Before mutating anything, build knowledge. For each city in the plan:
207
+
208
+ ${researchSources}
209
+
210
+ ${researchQueries}
211
+
212
+ After researching, add all findings to \`activities_db.json\` with scores and sources.
213
+ Git commit the updated database.
214
+ ${outputLanguage}
215
+ ## Phase 2: Optimization Loop
216
+
217
+ \`\`\`
218
+ LOOP FOREVER:
219
+
220
+ 1. Pick a mutation type (rotate through these):
221
+ a. SWAP \u2014 replace an activity with a higher-scored alternative from activities_db
222
+ b. REALLOCATE \u2014 move a day between cities (within min/max bounds)
223
+ c. REORDER \u2014 rearrange a day's activities for better geographic clustering
224
+ d. UPGRADE \u2014 replace a restaurant with a more authentic option
225
+ e. SIMPLIFY \u2014 remove a low-scoring activity, leave free wandering time
226
+ f. RESEARCH \u2014 search for new options in the weakest-scoring city,
227
+ add to activities_db, then attempt a swap
228
+
229
+ 2. Make ONE change to plan.md
230
+ 3. Git commit with descriptive message
231
+ 4. Score the new plan (comparative mode for most iterations, absolute every 10th)
232
+ 5. If score improved \u2192 keep the commit
233
+ 6. If score equal or worse \u2192 git reset --hard HEAD~1
234
+ 7. Log to results.tsv (tab-separated):
235
+ iteration, commit, score_before, score_after, delta, status, mutation_type, description
236
+ 8. NEVER STOP \u2014 run until interrupted
237
+ \`\`\`
238
+
239
+ ## Mutation Guidelines
240
+
241
+ - **One mutation per iteration.** Don't change multiple things at once.
242
+ - **Free time is valuable.** Empty afternoons for wandering can score higher than mediocre activities.
243
+ - **Respect geographic clustering.** Don't zigzag across the city.
244
+ - **Respect day bounds.** When reallocating days, stay within min/max in constraints.yaml.
245
+ - **Transit days are partial days.** A day with 5+ hours transit should not have full activities.
246
+ - **Research when stuck.** If 5+ consecutive discards, trigger a RESEARCH mutation.
247
+
248
+ ## What You CANNOT Do
249
+
250
+ - Add or remove cities from the route
251
+ - Change the city ordering
252
+ - Modify constraints.yaml or rubrics.yaml
253
+ - Exceed min/max day bounds per city
254
+ - Schedule activities during transit time
255
+ ${constraints.hard_requirements.map((r) => `- Violate: ${r}`).join("\n")}
256
+
257
+ ## Crash/Failure Handling
258
+
259
+ - If scoring fails, revert and log as crash
260
+ - If 10 consecutive discards, try REALLOCATE or RESEARCH
261
+ - If score plateaus for 20+ iterations, shift to RESEARCH phase
262
+ - results.tsv survives git resets (it's gitignored)
263
+
264
+ ## Context Management
265
+
266
+ - Write long outputs to files, don't flood context
267
+ - Write research to activities_db.json immediately
268
+ - Read results.tsv periodically to avoid repeating failed mutations
269
+ `;
270
+ }
271
+
272
+ export {
273
+ getGlobalDir,
274
+ getTripHistoryPath,
275
+ getLearnedPath,
276
+ loadConfig,
277
+ saveConfig,
278
+ setLanguage,
279
+ t,
280
+ getLlmLanguageInstruction,
281
+ generateProgram
282
+ };