我一直不太愿意用“能打印 Hello World”证明一门语言已经可用。
对 HHY 这样的系统脚本语言,更有价值的问题是:它能不能接住一个有文件系统、子进程、HTTP、并发、失败隔离、结构化输出和稳定退出码的完整任务?FlowGuard 就是我给出的第一个实战答案。
它接收项目目录、JSON 配置和报告路径,检查必需文件,扫描大文件与疑似凭据,并发执行质量命令与服务健康检查,最后原子写入 JSON 报告。没有失败返回 0,有失败返回 1,参数错误返回 3。
质量门禁不是一串命令,而是一条即使局部失败,也必须完整收集事实的工作流。

入口只负责编排
flowguard.hhy 没有塞进所有业务细节。结构、文件、命令、HTTP 和报告分别放进六个模块,入口只负责组合:
let structure_checks = inspect_structure(root, config.required_files)
let file_checks = inspect_files(root, config.limits.large_file)
let command_checks = inspect_commands(root, config.commands)
let health_checks = inspect_health(config.health_checks)
let checks = combine([structure_checks, file_checks, command_checks, health_checks])
let report = build_report(config.project.name, args[0], checks)
report
|> encode_json({ pretty: true })
|> save_text(output_path, { atomic: true })
这段代码是我希望 HHY 表达出来的形状:输入、变换、副作用和结果边界都清楚。模块不是为了展示语法,而是为了让某一类检查可以独立修改和测试。
attempt 比“一遇错就退出”更重要
门禁最常见的错误,是第一项失败后整个脚本退出。这样 CI 只能告诉你测试失败,却来不及继续检查健康接口、缺失文件和凭据风险。
FlowGuard 把每条外部命令包装进 attempt,将运行错误变成普通数据;HTTP 检查也采用同样方式。失败会形成一条 status: "fail" 的检查记录,而不是终止整份报告。
let execution = attempt {
run(command.argv, {
cwd: root,
timeout: 15s,
max_output: 1mib
})
}
命令参数始终以 argv 数组交给 run,不拼接 shell 字符串;每个命令还有 15 秒和 1 MiB 输出上限。这里的重点不是少写几行,而是默认把注入、失控进程和无限输出排除在正常路径之外。
并发需要上限,扫描需要脱敏
质量命令使用 parallel(3),健康检查使用 parallel(4)。这些任务大多在等待进程或网络,有界并发能缩短总时间,又不会让配置里增加十几个服务后瞬间打满资源。
文件扫描会寻找私钥头、DEMO_TOKEN= 和 password= 等模式,但报告只保存文件名与 content_redacted: true。检测工具发现秘密,不代表它有权复制秘密。
Bytes 也直接进入业务逻辑。文件大小不是模糊的数字,配置里的 256b、1kib、4kib 和 1mib 会被解析成 Bytes 后再比较,避免单位被藏在变量名和约定里。
测试失败场景,才能证明门禁有效
自测准备了两个确定性项目。healthy-project 包含必需文件、成功命令和 2xx 健康接口,八项检查全部通过。risky-project 故意缺少 LICENSE,放入假的 DEMO_TOKEN,执行失败命令并请求 404 接口。
第二个场景返回 1 不是测试失败,而是门禁正确工作。测试脚本随后读取报告,断言五项失败都被记录,敏感内容没有泄露。

sh practical-projects/flowguard/self-test.sh
这个项目验证了什么
FlowGuard 让我确认,HHY 最有辨识度的方向不是替代某一条 shell 命令,而是把文件、进程、HTTP 和 Stream 放进同一套可读的错误与资源模型。
Shell 很适合把几个成功路径连起来;当任务开始要求并发上限、错误聚合、脱敏报告、原子写入和稳定退出码时,脚本已经在长成一个应用。HHY 要做的,就是让这个增长过程仍然保持直接。