📈 GH Trending Explorer 🎲 Surprise me

← all repos

konggu2077 / MealScript-

git clone https://github.com/konggu2077/MealScript-.git

MealScript 把「吃什么」写成一门语言,再把它编译成一份有官方价格证据的方案

—
stars, 24h
+34
stars, 7 days · #1,201
+34
stars, 30 days · #8,183
34
★ total · +34% this week
0
forks · 1 open issues

Star history starts 2026-10-20; the chart appears after the second daily snapshot.

LanguageTypeScript
License—
Created2026-10-15 (5d ago)
Last pushtoday
Watchers0
OwnerUser

Links

README excerpt
### 立刻试试
- **在线 Demo**:https://konggu2077.github.io/MealScript-/dist/mealscript.html —— 不用装任何东西,打开即用,断网也能跑
 (仓库设置里开启 Pages:`Settings → Pages → Deploy from a branch → main / (root)` 后生效)
- **下载单文件网页**:`dist/mealscript.html`(265 KB,双击即开,CSS/JS 全内联、零外部请求)
### 目录
- 界面一览
- 这不是一个聊天点餐助手
- 快速开始
- 目录结构
- DSL v0.1 语法
- 真实性规则(我们的红线)
- Live 与 Replay 的区别
- 测试
- 与官方标准的偏差说明(如实记录)
- 免责声明
> Code Your Meal. Compile Your Hunger.
**MealScript 是一门面向真实麦当劳点餐场景的轻量 DSL,配一个真正自己写的编译器。**
**自己写的编译器 · 官方 MCP 真实结算价 · 行列级可解释诊断**
你用可编译的约束语句描述预算与偏好,系统用麦当劳官方 MCP 查询真实门店、真实菜单与真实结算价,产出带调用证据的候选方案,以及能精确到行列的编译诊断。
 [code] 
打开页面后有 5 个内置示例,其中 3 个是**故意写错的**——用来看结构化错误长什么样。
### 单文件离线版(双击即开,无需安装任何东西)
 [code] 
`dist/mealscript.html` 是一个**自包含的单文件网页**:CSS 与 JS 全部内联,**零外部请求、零凭据、离线可用**。
编译器、语义检查、组合搜索、价格校验全部在浏览器里真实运行;门店与价格来自一次真实会话的**脱敏录制样例**,
所以它的状态恒为 `REPLAY_RESULT`,永远不会伪装成 `BUILD_PASSED`。
编辑器带**自动补全**:输入字段名、枚举取值(`pickup` / `dry_run` …)时有候选弹窗;
在 `require_keyword` / `exclude_keywords` 的引号里输入时,会从菜单数据里实时联想**分类、商品、标签**
(如 `小食`、`麦旋风`、`爆款`),↑↓ 选择、Tab/Enter 确认、Esc 关闭。
写多关键词时只按**最后一个分隔符之后的片段**联想,并自动把这一行里已经写过的关键词从候选里去掉。
它已提交进仓库,也可以直接从 GitHub 下载后双击打开。构建时 `@mealscript/mcp-adapter` 被强制解析为
`browser.ts`,因此 `node:fs`、MCP SDK 与任何 Token 都不可能进入产物(有测试守着这条边界)。
### 打开 Live 模式(真实 MCP)
 [code] 
`.env` 已在 `.gitignore` 中,Token **不会**进入仓库。没有 Token 也能完整体验 Replay 模式。
### 刷新产品目录与价格(录制新 fixture)
拿到 Token 后,一条命令就能把门店、菜单、分类、标签和官方价全部重新抓一遍:
 [code] 
它做的事,以及它**不做**的事:
- 走的就是管线用的那个**只读**适配器 —— 写操作(下单/领券/抽奖)在适配器里被硬阻断,一次都调不到
- 逐件调 `calculate-price` 核对官方价,**只记录「官方价 = 菜单价」的非优惠商品**;有折扣的商品直接跳过,不做猜测
- 递归脱敏:门店地址 / 经纬度 / 距离 / 图片 / traceId / 个人信息全部剔除,并在终端**逐字段打印删了什么**
- 结束时打印与上一版的差异(商品数、新增、下架、价格变动),先 `git diff fixtures/` 复核再提交
`location` 的写法与 `query-nearby-stores` 的对应关系(编译期拆分,见 `packages/compiler/src/location.ts`):
| `location` 写法 | 拆成 |
|---|---|
| `北京市海淀区五道口` | city=`北京市`,keyword=`海淀区五道口` |
| `上海市浦东新区陆家嘴` | city=`上海市`,keyword=`浦东新区陆家嘴` |
| `深圳市南山区科技园` | city=`深圳市`,keyword=`南山区科技园` |
| `北京 五道口` | city=`北京`,keyword=`五道口` |
编辑器里输入 `location` 的引号时会给出真实地点候选(北京/上海/深圳/广州/成都/杭州共 14 个);
换地点只影响 Live 查询 —— Replay 的门店来自录制样例,改 `location` 不会换门店,trace 里仍会如实记录你请求的城市与关键词。
---
## 目录结构
 [code] 
---
## DSL v0.1 语法
| 字段 | 类型 / 规则 | 违反时的错误码 |
|---|---|---|
| `location` | 字符串,门店发现用的位置文本 | 缺失或空 → E001 |
| `mode` | v0.1 仅 `pickup` | `delivery` → E102 |
| `budget` | ` `max_items` → E103 |
| `exclude_keywords` | 字符串数组(元素内也可用逗号写多个) | 非法数组 → E001 |
| `min_items` / `max_items` | 整数 1–3 | 越界或 `min > max` → E103 |
| `optimize` | v0.1 仅 `min(total_price)` | `max(protein)` → E104 |
| `execution` | v0.1 固定 `dry_run` | `place_order` → E105 |
**省略时的默认值**:`mode=pickup`、`min_items=1`、`max_items=2`、`optimize=min(total_price)`、`execution=dry_run`。
### 多关键词:AND 语义
`require_keyword` 可以写多个关键词,**每一个都必须命中一件商品**(AND)。三种写法完全等价:
 [code] 
去空白、去重后得到 `["巨无霸牛鱼肉堡", "麦旋风"]`,编译器要求套餐里同时出现这两类商品各一件。
因为每个关键词都要占一个名额,关键词个数超过 `max_items` 时直接给 **E103** 并告诉你该把 `max_items` 调到多少,
而不是等到搜索阶段报一个含糊的「无可行组合」。
注意语义边界:`require_keyword` 指的是**至少一件**命中商品,其余名额可以来自任意可售品类 ——
所以 `require_keyword: "汉堡"` + `min_items: 2` 组出