harnessmith 0.1.0__tar.gz
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.
- harnessmith-0.1.0/.claude/settings.local.json +16 -0
- harnessmith-0.1.0/.github/workflows/release.yml +22 -0
- harnessmith-0.1.0/.gitignore +22 -0
- harnessmith-0.1.0/AGENTS.md +100 -0
- harnessmith-0.1.0/CLAUDE.md +127 -0
- harnessmith-0.1.0/HarnessSmith.bat +177 -0
- harnessmith-0.1.0/HarnessSmith.sh +139 -0
- harnessmith-0.1.0/LICENSE +21 -0
- harnessmith-0.1.0/PKG-INFO +431 -0
- harnessmith-0.1.0/README.md +394 -0
- harnessmith-0.1.0/docs/02-development/00-overview.md +265 -0
- harnessmith-0.1.0/docs/02-development/01-slice-0-scaffold.md +48 -0
- harnessmith-0.1.0/docs/02-development/02-slice-1-golden-path.md +61 -0
- harnessmith-0.1.0/docs/02-development/03-slice-2-routing-and-context.md +49 -0
- harnessmith-0.1.0/docs/02-development/04-slice-3-product-web.md +95 -0
- harnessmith-0.1.0/docs/02-development/05-slice-4-mcp-tools.md +72 -0
- harnessmith-0.1.0/docs/02-development/06-slice-5-paradigms.md +75 -0
- harnessmith-0.1.0/docs/02-development/07-slice-6-tools-and-skills.md +79 -0
- harnessmith-0.1.0/docs/02-development/075-slice-6b-global-rules.md +55 -0
- harnessmith-0.1.0/docs/02-development/08-slice-7-wizard.md +77 -0
- harnessmith-0.1.0/docs/02-development/09-slice-8-sessions.md +59 -0
- harnessmith-0.1.0/docs/02-development/095-slice-8b-memory.md +76 -0
- harnessmith-0.1.0/docs/02-development/10-slice-9-stop-resume-reask.md +71 -0
- harnessmith-0.1.0/docs/02-development/11-slice-10-hitl-and-ask.md +91 -0
- harnessmith-0.1.0/docs/02-development/12-slice-11-mcp-management.md +123 -0
- harnessmith-0.1.0/docs/02-development/125-slice-12-anthropic-dual-spec.md +101 -0
- harnessmith-0.1.0/docs/02-development/13-slice-13-product-tui.md +108 -0
- harnessmith-0.1.0/docs/02-development/14-forge-add-incremental-regeneration.md +130 -0
- harnessmith-0.1.0/docs/02-development/15-llm-robustness-and-context.md +80 -0
- harnessmith-0.1.0/docs/README.md +17 -0
- harnessmith-0.1.0/examples/spec.yaml +45 -0
- harnessmith-0.1.0/harnessmith/__init__.py +3 -0
- harnessmith-0.1.0/harnessmith/catalog/__init__.py +166 -0
- harnessmith-0.1.0/harnessmith/catalog/mcp_servers.yaml +185 -0
- harnessmith-0.1.0/harnessmith/cli.py +348 -0
- harnessmith-0.1.0/harnessmith/cli_wizard.py +174 -0
- harnessmith-0.1.0/harnessmith/debuglog.py +59 -0
- harnessmith-0.1.0/harnessmith/generator.py +507 -0
- harnessmith-0.1.0/harnessmith/node_bootstrap.py +197 -0
- harnessmith-0.1.0/harnessmith/presets/__init__.py +50 -0
- harnessmith-0.1.0/harnessmith/presets/coding-assistant/mcp_prefill.yaml +13 -0
- harnessmith-0.1.0/harnessmith/presets/coding-assistant/spec.yaml +54 -0
- harnessmith-0.1.0/harnessmith/scaffold.py +145 -0
- harnessmith-0.1.0/harnessmith/spec.py +239 -0
- harnessmith-0.1.0/harnessmith/templates/.devcontainer/devcontainer.json.j2 +12 -0
- harnessmith-0.1.0/harnessmith/templates/.dockerignore.j2 +11 -0
- harnessmith-0.1.0/harnessmith/templates/.env.example.j2 +5 -0
- harnessmith-0.1.0/harnessmith/templates/.gitignore.j2 +17 -0
- harnessmith-0.1.0/harnessmith/templates/.python-version.j2 +1 -0
- harnessmith-0.1.0/harnessmith/templates/AGENTS.md.j2 +914 -0
- harnessmith-0.1.0/harnessmith/templates/Dockerfile.j2 +30 -0
- harnessmith-0.1.0/harnessmith/templates/LICENSE.j2 +21 -0
- harnessmith-0.1.0/harnessmith/templates/README.md.j2 +325 -0
- harnessmith-0.1.0/harnessmith/templates/RULES.md.j2 +10 -0
- harnessmith-0.1.0/harnessmith/templates/__launch_name__.bat.j2 +173 -0
- harnessmith-0.1.0/harnessmith/templates/__launch_name__.sh.j2 +143 -0
- harnessmith-0.1.0/harnessmith/templates/config.yaml.j2 +270 -0
- harnessmith-0.1.0/harnessmith/templates/pyproject.toml.j2 +50 -0
- harnessmith-0.1.0/harnessmith/templates/skills/example-skill/SKILL.md.j2 +30 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/__init__.py.j2 +3 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/__init__.py.j2 +17 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/config.py.j2 +681 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/context.py.j2 +471 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/debuglog.py.j2 +72 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/extensions.py.j2 +188 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/hooks.py.j2 +116 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/interaction.py.j2 +266 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/llm.py.j2 +425 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/llm_anthropic.py.j2 +422 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/loop.py.j2 +85 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/mcp.py.j2 +1251 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/memory.py.j2 +353 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/mock.py.j2 +109 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/paradigms/__init__.py.j2 +359 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/paradigms/agent.py.j2 +236 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/paradigms/ask.py.j2 +236 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/paradigms/plan.py.j2 +240 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/prompts.py.j2 +153 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/session.py.j2 +316 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/skills.py.j2 +143 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/tools.py.j2 +357 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/trace.py.j2 +110 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/harness/usage.py.j2 +207 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/interfaces/__init__.py.j2 +1 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/interfaces/cli.py.j2 +1261 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/interfaces/web.py.j2 +1456 -0
- harnessmith-0.1.0/harnessmith/templates/src/__project_slug__/interfaces/web_index.html.j2 +3296 -0
- harnessmith-0.1.0/harnessmith/templates/tests/_mcp_dummy_server.py.j2 +36 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_harness.py.j2 +2539 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_llm_anthropic.py.j2 +324 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_mcp.py.j2 +1126 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_memory.py.j2 +251 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_sessions.py.j2 +364 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_skills.py.j2 +112 -0
- harnessmith-0.1.0/harnessmith/templates/tests/test_web.py.j2 +1706 -0
- harnessmith-0.1.0/harnessmith/wizard/__init__.py +11 -0
- harnessmith-0.1.0/harnessmith/wizard/app.py +682 -0
- harnessmith-0.1.0/harnessmith/wizard/static/index.html +430 -0
- harnessmith-0.1.0/pyproject.toml +84 -0
- harnessmith-0.1.0/tests/test_catalog.py +109 -0
- harnessmith-0.1.0/tests/test_cli.py +102 -0
- harnessmith-0.1.0/tests/test_cli_wizard.py +165 -0
- harnessmith-0.1.0/tests/test_debuglog.py +61 -0
- harnessmith-0.1.0/tests/test_generator.py +1223 -0
- harnessmith-0.1.0/tests/test_golden.py +354 -0
- harnessmith-0.1.0/tests/test_node_bootstrap.py +149 -0
- harnessmith-0.1.0/tests/test_presets.py +53 -0
- harnessmith-0.1.0/tests/test_spec.py +176 -0
- harnessmith-0.1.0/tests/test_wizard.py +732 -0
- harnessmith-0.1.0/uv.lock +614 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(fuser -k 8001/tcp)",
|
|
5
|
+
"Bash(fuser -k 8841/tcp)",
|
|
6
|
+
"Bash(curl -s --max-time 2 http://127.0.0.1:8001/ -o /dev/null -w \"product :8001 -> %{http_code}\\\\n\")",
|
|
7
|
+
"Bash(curl -s --max-time 2 http://127.0.0.1:8841/ -o /dev/null -w \"wizard :8841 -> %{http_code}\\\\n\")",
|
|
8
|
+
"Bash(rm -rf /home/s1yu/HarnessForge/generate/obs_demo)",
|
|
9
|
+
"Read(//home/s1yu/longhaul-eval/product/**)",
|
|
10
|
+
"Bash(rm -rf .harness/sessions/* traces/* logs/* .harness/usage.json)",
|
|
11
|
+
"Bash(rm -rf /home/s1yu/longhaul-eval/workspace)",
|
|
12
|
+
"Bash(mkdir -p /home/s1yu/longhaul-eval/workspace)",
|
|
13
|
+
"Bash(chmod +x /home/s1yu/longhaul-eval/run_scenario_a.sh)"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
# Must match the environment registered with the PyPI trusted publisher.
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Check tag matches project version
|
|
17
|
+
run: |
|
|
18
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
19
|
+
version=$(sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml)
|
|
20
|
+
[ "$tag" = "$version" ] || { echo "tag $tag != pyproject version $version"; exit 1; }
|
|
21
|
+
- run: pipx run build
|
|
22
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
|
|
7
|
+
# uv / virtualenv
|
|
8
|
+
.venv/
|
|
9
|
+
|
|
10
|
+
# Build artifacts (sdist/wheel) — produced by `uv build`, never tracked.
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
|
|
15
|
+
# Transient runtime/debug logs (serve, wizard, debuglog) — local-only, never shipped.
|
|
16
|
+
*.log
|
|
17
|
+
|
|
18
|
+
# Real API keys (human-managed, see CLAUDE.md §0.1) — never tracked.
|
|
19
|
+
.env
|
|
20
|
+
|
|
21
|
+
# Products generated by the wizard's one-click "generate" (default target dir).
|
|
22
|
+
generate/
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# AGENTS.md — HarnessSmith 快速上手
|
|
2
|
+
|
|
3
|
+
> 面向**在本仓库里干活的 Agent** 的导航地图:30 秒认清这是什么、代码在哪、怎么跑、怎么算"做完"。
|
|
4
|
+
> 这里只讲"怎么上手";**硬约束与红线看 [`CLAUDE.md`](./CLAUDE.md),定位 / 范围 / 决策总表看 [`docs/02-development/00-overview.md`](./docs/02-development/00-overview.md)**。两者冲突时以那两份为准。
|
|
5
|
+
|
|
6
|
+
## 1. 这是什么(一句话)
|
|
7
|
+
|
|
8
|
+
HarnessSmith 是 **agent harness 的代码生成器**(`create-next-app` for agent harnesses):吃一份 `HarnessSpec`(YAML / preset / 向导采集),渲染出一个**完整、独立、无 agent 框架锁定**的 Python 仓库。生成的产物与本生成器**零运行期关系**——生成即脱离。
|
|
9
|
+
|
|
10
|
+
## 2. 两层心智(最重要,先记住)
|
|
11
|
+
|
|
12
|
+
任何改动,先问自己**改的是哪一层**:
|
|
13
|
+
|
|
14
|
+
| 层 | 位置 | 你在改什么 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **生成器本体** | `harnessmith/*.py`(`spec` / `generator` / `cli` / `scaffold` / `wizard` …) | spec schema、渲染引擎、CLI、向导、catalog、preset |
|
|
17
|
+
| **产物模板** | `harnessmith/templates/**/*.j2` | **渲染后**才是用户拿到的代码。改这里 = 改所有未来产物 |
|
|
18
|
+
|
|
19
|
+
推论:**测试必须覆盖"生成产物"本身**,不能只测生成器。黄金路径 = 生成 → `uv sync` → `pytest` → mock LLM 跑通一次 function-calling(见 §5)。
|
|
20
|
+
|
|
21
|
+
## 3. 仓库地图
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
harnessmith/ # ← 生成器本体(这个包发布到 PyPI)
|
|
25
|
+
spec.py # HarnessSpec(Pydantic v2 + YAML,extra=forbid)
|
|
26
|
+
generator.py # 渲染模板 → 写仓库 + git init + uv lock + 冒烟自检
|
|
27
|
+
cli.py # Typer 入口:new / wizard / doctor
|
|
28
|
+
scaffold.py # 生成器与 CLI 向导共享的烤默认 / slug 派生(纯 stdlib)
|
|
29
|
+
cli_wizard.py # 终端交互向导(questionary)
|
|
30
|
+
node_bootstrap.py # Node 系 MCP server 的离线预热 / node 直跑
|
|
31
|
+
debuglog.py # 生成器侧 debug 日志
|
|
32
|
+
catalog/mcp_servers.yaml # 精选静态 MCP catalog(向导/CLI 预填数据源)
|
|
33
|
+
presets/coding-assistant/ # 内置 preset(spec.yaml + mcp_prefill.yaml)
|
|
34
|
+
wizard/ # Web 向导(FastAPI 单页,[wizard] extra,绝不进产物)
|
|
35
|
+
templates/ # ← 产物模板(.j2)。渲染出的才是用户的代码
|
|
36
|
+
src/__project_slug__/harness/ # 产物核心:loop/llm/llm_anthropic/tools/context/
|
|
37
|
+
# session/interaction/hooks/usage/trace/prompts/
|
|
38
|
+
# paradigms/ + mcp/skills/memory(opt-in)
|
|
39
|
+
src/__project_slug__/interfaces/ # cli.py(+ web.py / web_index.html opt-in)
|
|
40
|
+
tests/ # 产物自带测试(mock LLM)
|
|
41
|
+
docs/02-development/ # 设计与切片文档(00-overview = 唯一口径)
|
|
42
|
+
tests/ # ← 生成器自身的测试(test_spec / test_generator / test_golden …)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 4. 常用命令(已验证)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv sync # 装生成器依赖
|
|
49
|
+
uv sync --extra wizard # 额外装 Web 向导依赖(FastAPI/uvicorn)
|
|
50
|
+
|
|
51
|
+
# 生成产物
|
|
52
|
+
uv run harnessmith new my-agent --preset coding-assistant # 从 preset 非交互生成
|
|
53
|
+
uv run harnessmith new my-agent --spec ./harness.spec.yaml # 从手写 spec 生成
|
|
54
|
+
uv run harnessmith new my-agent --no-verify # 跳过生成后冒烟(离线/快迭代)
|
|
55
|
+
uv run harnessmith wizard # Web 向导
|
|
56
|
+
uv run harnessmith doctor # 本机工具链预检
|
|
57
|
+
|
|
58
|
+
# 测试(golden 默认被 addopts 的 -m 'not golden' 排除)
|
|
59
|
+
uv run pytest -q # 快测(生成器单元 + 产物渲染,~200 例,约 20s)
|
|
60
|
+
uv run pytest -m golden # 黄金快照:真生成 + uv sync + 产物 pytest(慢)
|
|
61
|
+
uv run pytest -m docker # 需要 Docker daemon
|
|
62
|
+
|
|
63
|
+
# 发布前
|
|
64
|
+
uv build # 产 sdist + wheel 到 dist/
|
|
65
|
+
uvx twine check dist/* # 校验元数据 + README 渲染
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> 默认开发用 **mock LLM**,不需要真 key(CLI `--mock`)。真 key / 联网 / 花钱属"只能人做",见 `CLAUDE.md §0.1 / §6`。
|
|
69
|
+
|
|
70
|
+
## 5. "完成"的硬门槛(详见 `CLAUDE.md §5`)
|
|
71
|
+
|
|
72
|
+
宣称做完前,至少:
|
|
73
|
+
|
|
74
|
+
- [ ] 新增 / 改动的生成器或模板代码**有自动化测试**。
|
|
75
|
+
- [ ] **黄金路径绿**:preset/示例 spec 生成 → `uv sync && pytest` 全绿 → mock 跑通一次 function-calling(含一次工具调用)。
|
|
76
|
+
- [ ] 断言生成的 `pyproject.toml` **不含 `langchain`/`langgraph`/`adk`**。
|
|
77
|
+
- [ ] 生成后冒烟自检绿;`ReadLints` 无新增告警。
|
|
78
|
+
- [ ] 动了 `HarnessSpec` / 模板核心 / 跨 ≥3 文件 → 额外跑 §5.2 回归(全量黄金 + Docker 冒烟 + `uvx harnessmith new` 冒烟)。
|
|
79
|
+
|
|
80
|
+
## 6. 红线速记(命中即停,详见 `CLAUDE.md §6`)
|
|
81
|
+
|
|
82
|
+
- **绝不**在产物里引入任何 agent 编排框架(LangChain/LangGraph/ADK)——定位红线。
|
|
83
|
+
- **薄优先**:默认产物核心循环 150–300 行;重能力(MCP/Web/skills/memory)只走 **spec 开关**,关闭 = 代码与依赖中均不存在。
|
|
84
|
+
- 改 `HarnessSpec` 字段语义、给**默认产物**加运行期依赖、改 LLM API 面、任何让**密钥进 git/spec/trace/日志**的路径 → 先停下请人审。
|
|
85
|
+
- **密钥**只进 gitignored `.env`;`config.yaml` / spec 只存 env 引用名。
|
|
86
|
+
|
|
87
|
+
## 7. HarnessSpec 速览(结构旋钮,决定生成什么)
|
|
88
|
+
|
|
89
|
+
`harnessmith/spec.py` 的 `HarnessSpec`(顶层字段):
|
|
90
|
+
|
|
91
|
+
`version` · `project_slug`(产物包名,默认 `agent_harness`)· `display_name`(产物 UI/README 显示名)· `language`(产物 Web 默认语言 `en`/`zh`)· `llms`(每 profile 选 `provider: openai|anthropic`)· `roles` · `prompts`(`system` + `rules_files`)· `tools` · `paradigms`(`agent`/`plan`/`ask` 多选,默认 `["agent"]`)· `interfaces`(`cli`/`web`/`tui`)· `mcp.enabled` · `skills.enabled` · `memory.enabled` · `observability` · `context`(种子)· `rag`/`secrets`(预留)。
|
|
92
|
+
|
|
93
|
+
口径:**spec = 配方(生成什么 + 初值,结构轴);产物 `config.yaml` = 运行期权威(行为轴)**。结构变更要重新生成,行为变更不用。
|
|
94
|
+
|
|
95
|
+
## 8. 入场阅读顺序
|
|
96
|
+
|
|
97
|
+
1. `README.md` —— 速览定位与产物能力。
|
|
98
|
+
2. `CLAUDE.md` —— 协作硬约束与红线(**必读**)。
|
|
99
|
+
3. `docs/02-development/00-overview.md` —— 定位 / 范围 / 决策总表 / 切片地图(**唯一口径**)。
|
|
100
|
+
4. 按当前任务**只读**相关 slice 子文档(`docs/02-development/` 下),别把全部塞进上下文。
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# CLAUDE.md — HarnessSmith 项目守则
|
|
2
|
+
|
|
3
|
+
> Agent 进入本项目(**vibe coding 模式**)入场必读。本文件是 Agent 工作的**硬约束**。
|
|
4
|
+
> 项目定位 / 范围 / 决策 / 开发节奏与切片门禁见 `docs/02-development/00-overview.md`。
|
|
5
|
+
>
|
|
6
|
+
> **Tradeoff**:偏向"先把事情做对"。显然小到不值得讨论的操作自己判断,别让流程把简单任务搞复杂。
|
|
7
|
+
|
|
8
|
+
## 0. 协作模式与角色边界
|
|
9
|
+
|
|
10
|
+
本项目**主要由 Agent 开发**,人只负责决策、验证、以及"只能人做"的环境配置。
|
|
11
|
+
|
|
12
|
+
| 角色 | 负责 | 不做 |
|
|
13
|
+
|------|------|------|
|
|
14
|
+
| **Agent(你)** | 写生成器与模板代码、写/跑测试、调试、维护文档与变更记录、按 slice 自驱推进、**测试全绿后直接 commit + push `main`**、写完成报告 | 不替人拍板产品决策、不动用钱/联网批量/破坏定位的事、不 `--force` push、不在测试未绿时推 `main` |
|
|
15
|
+
| **人** | 方向与验收、slice 关键决策点签字、**环境与密钥配置(见 §0.1)**、对外发布 | 不写代码、不做 Agent 已能自动化的事 |
|
|
16
|
+
|
|
17
|
+
**一句话**:Agent 负责"怎么做",人负责"做不做、装环境、给 key、何时发布"。
|
|
18
|
+
开发文档里写"开发者要做 X"一律理解为"**你(Agent)要做 X**,由人按切片节奏验收"。
|
|
19
|
+
|
|
20
|
+
### 0.1 只能由人配置的环境(Agent 不代劳)
|
|
21
|
+
|
|
22
|
+
- 安装本机工具链:`uv`、`docker`(Agent 不改系统级配置、不装系统包)。
|
|
23
|
+
- **API key / `.env` 真实值**:Agent 只维护 `.env.example`,**绝不写入真实 `.env`、绝不把 key 写进任何被 git 跟踪的文件**。日常开发用 **mock LLM**,不需要真 key;只有"真实 LLM 冒烟 / 对外演示"才需要人提供 key。
|
|
24
|
+
- PyPI 发布凭证、GitHub 仓库设置、对外发布。
|
|
25
|
+
|
|
26
|
+
> Agent 需要某个 key / 环境时:在 plan 阶段列出所需 `.env` key 名与用途,**停下来请人配置**,不要自己编造或跳过。
|
|
27
|
+
|
|
28
|
+
## 1. Think Before Coding
|
|
29
|
+
|
|
30
|
+
**不要假设。不要藏起困惑。把权衡显式说出来。** 动手前:声明假设,不确定就问;多种合理解读时列出来让人选;有更简单的方案先说一声。宁可开工前多问 1 个问题,不要跑了 30 分钟才发现方向错了。
|
|
31
|
+
|
|
32
|
+
## 2. Thin First(薄优先 —— 本项目核心卖点,硬约束)
|
|
33
|
+
|
|
34
|
+
- 默认产物模板保持**极薄**:核心循环目标 150–300 行,整体远小于一个框架。
|
|
35
|
+
- 不做没要求的功能;一次性代码不先抽象;不为"以后可能要"加灵活性。
|
|
36
|
+
- 高级能力(RAG / MCP / context 策略 / Web)只通过 **spec 开关**生成,不塞进默认产物。
|
|
37
|
+
- 200 行能压到 50 行就重写。
|
|
38
|
+
|
|
39
|
+
## 3. Surgical Changes
|
|
40
|
+
|
|
41
|
+
只动该动的,只清理自己制造的烂摊子。不"顺手优化"无关代码或格式;不重构没坏的东西;沿用现有风格;发现 dead code **提一句**,别擅自删。
|
|
42
|
+
|
|
43
|
+
## 4. 两层心智:生成器 vs 生成产物(本项目特有)
|
|
44
|
+
|
|
45
|
+
你写的是一个**生成器**,它渲染出**独立的生成产物仓库**。任何时候分清你在改哪一层:
|
|
46
|
+
|
|
47
|
+
- 改 `harnessmith/`(生成器本体):spec / 渲染引擎 / CLI / 向导 / catalog / presets。
|
|
48
|
+
- 改 `harnessmith/templates/`(产物模板):渲染后才是用户拿到的代码。
|
|
49
|
+
- **测试必须覆盖"生成产物"本身**:生成 → `uv sync` → `pytest` → mock LLM 跑通一次工具调用,而不只是测生成器。
|
|
50
|
+
|
|
51
|
+
## 5. 目标驱动 + 测试硬门槛(按生成器项目定制)
|
|
52
|
+
|
|
53
|
+
"完成"的硬门槛见 §5.1,没达到 = 没完成,别用"逻辑简单不用测"或"先合后补"做借口。
|
|
54
|
+
例外:纯文档 / 纯注释 / 纯重命名(改名工具已覆盖全部引用)可不加测试,但要在完成报告里点名说明。
|
|
55
|
+
|
|
56
|
+
### 5.1 黄金测试是"完成"的硬门槛
|
|
57
|
+
|
|
58
|
+
任何被宣称完成的功能必须:
|
|
59
|
+
|
|
60
|
+
- 新增 / 改动的生成器或模板代码**有自动化测试**(unit 或 integration)。
|
|
61
|
+
- **黄金路径绿**:用示例 / preset spec 生成项目 → `uv sync && pytest` 全绿 → mock LLM 跑通一次 function-calling(含一次工具调用)。
|
|
62
|
+
- 断言生成的 `pyproject.toml` **不含 `langchain`/`langgraph`/`adk`**。
|
|
63
|
+
- **可运行性自检绿**:生成后冒烟(`uv sync` + import + mock 跑一步 + `pytest -q`)。
|
|
64
|
+
- `ReadLints`(IDE 诊断)无新增 error / warning。
|
|
65
|
+
|
|
66
|
+
### 5.2 "大改动"额外跑回归
|
|
67
|
+
|
|
68
|
+
判定:动了 `HarnessSpec`、模板核心,或跨 ≥ 3 个文件。额外跑:
|
|
69
|
+
|
|
70
|
+
- 全量黄金快照(示例 spec + 每个 preset 各生成并 `pytest`)。
|
|
71
|
+
- **Docker 冒烟**:生成产物 `docker build` 成功 + `docker run` 跑通 mock 一步。
|
|
72
|
+
- `uvx harnessmith new` 冒烟。
|
|
73
|
+
|
|
74
|
+
任一项失败 → 该功能**未完成**,先修,不要急着开下一项。
|
|
75
|
+
|
|
76
|
+
### 5.3 可自主决定(不必请示)
|
|
77
|
+
|
|
78
|
+
命名;实现细节 / 内部数据结构 / stdlib 选择;已在文档区间内的参数(默认 context 策略、预算默认值等);fixture / mock 风格 / 测试组织;在 `pyproject.toml` 已列家族内加可选依赖(如 `pytest-*`)。执行后在完成报告里一句话提一下。
|
|
79
|
+
|
|
80
|
+
## 6. 停下来问人 / 上报的触发条件(命中任一即停)
|
|
81
|
+
|
|
82
|
+
1. 改 `HarnessSpec` schema 字段或其语义(影响生成器与所有模板)。
|
|
83
|
+
2. 给**默认产物模板**新增运行期依赖,或把 L2/L3 依赖混进默认核心依赖(违反"薄")。
|
|
84
|
+
3. 在生成产物里引入任何 **agent 编排框架**(LangChain/LangGraph/ADK…)——**定位红线,绝不允许**。
|
|
85
|
+
4. 改 LLM API 面(Chat Completions ↔ Responses)或底座 SDK 选型。
|
|
86
|
+
5. 任何可能让**密钥进入 git / spec 快照 / trace / 日志**的路径。
|
|
87
|
+
6. 跨 slice 的范围调整:把 L3 提前进 MVP,或把 L1 推迟。
|
|
88
|
+
7. 需要真实 key / 联网 / 花钱:真实 LLM 跑批、发布到 PyPI、对外网开放端口。
|
|
89
|
+
8. 生成产物核心代码体积明显超"薄"目标(核心循环远超 300 行)。
|
|
90
|
+
9. 同一问题连续两次尝试都失败——别进"再试一遍"循环,停下说清卡点。
|
|
91
|
+
10. 多种合理实现且权衡不清,影响 ≥ 2 个模块 / 后续 slice。
|
|
92
|
+
|
|
93
|
+
上报格式:**[需要人介入]** + 触发第几条 / 在做什么 / 卡在哪 / 已试过什么(尝试 1·2 及结果)/ 可选项 A·B 及代价 / 我的倾向。等回复再继续。
|
|
94
|
+
|
|
95
|
+
## 7. Agent 标准工作循环:plan → implement → self-verify → handoff
|
|
96
|
+
|
|
97
|
+
- **plan**:读相关 slice 文档顶部"交付物";复述理解 + 实现计划(≤ 10 行);列会改的文件、新增测试、所需 `.env` key 名;命中 §6 就在 plan 阶段先停。
|
|
98
|
+
- **implement**:先用 Read/Grep/SemanticSearch 看现有代码沿用风格;小步走能 commit 就 commit;不碰无关代码。
|
|
99
|
+
- **self-verify**:跑 §5.1(大改动加 §5.2);自查 §1–§4 没违反;fail 先修不要假装看不见。
|
|
100
|
+
- **handoff**:**先按 §9 把受影响文档更新到与实现一致**(门禁勾选、字段 / 行为描述、决策点结论);再按 `docs/02-development/00-overview.md §完成报告模板` 输出报告;**改动构成一个完整功能且 §5.1 门禁全绿后,直接执行 commit(默认行为,无需先问"要不要提交")**,按 Conventional Commits。
|
|
101
|
+
|
|
102
|
+
## 8. 提交与分支
|
|
103
|
+
|
|
104
|
+
- **Conventional Commits**:`feat: / fix: / refactor: / docs: / chore: / test: / perf: / build: / ci:`。
|
|
105
|
+
- 一个 commit 一件事,不捎带无关改动。
|
|
106
|
+
- **完整功能 + 测试完毕 = 直接 commit(默认行为,不必先问人)**:当本次改动构成一个完整功能(`feat` / `fix` 等)且 §5.1 门禁全绿后,**直接执行 commit**,不要停下来征求"要不要提交"。多个独立功能各自单独 commit(承接"一个 commit 一件事")。纯文档 / 注释 / 重命名等小改动同理可直接提交。
|
|
107
|
+
- `main` **不再受保护**:门禁全绿后,Agent **可直接 commit 并 push `main`**,无需 feature branch / PR。仍 **不 `--force` push、不跳 pre-commit hook**(除非人明确允许);测试未绿不得推 `main`。需要走 PR 时人会显式要求。
|
|
108
|
+
|
|
109
|
+
## 9. 文档维护
|
|
110
|
+
|
|
111
|
+
- **任务完成即更新文档(硬规则)**:每个任务 / slice 一旦实现完成,必须把受影响的文档更新到**与实现一致**——slice 子文档勾选退出门禁、修正字段枚举 / 行为描述、回填人审决策点结论,并同步 `docs/02-development/00-overview.md` 的相关条目。**文档落后于实现 = 任务未完成**。
|
|
112
|
+
- 改 `HarnessSpec` 字段:同步 `docs/02-development/00-overview.md §5` + 相关 slice 文档。
|
|
113
|
+
- 改全局决策(罕见,走 §6 人审):必须改 `docs/02-development/00-overview.md` 的决策总表。
|
|
114
|
+
- 改 `.env.example` / 默认依赖:同步产物 README / AGENTS.md 与相关 slice 文档。
|
|
115
|
+
- 文档与代码相互引用的地方,**改一处必检另一处**。Agent 是这套文档体系的唯一维护者。
|
|
116
|
+
|
|
117
|
+
## 10. 语言与文风
|
|
118
|
+
|
|
119
|
+
- 人机对话默认中文;代码注释 / commit / 文档正文沿用各文件原有语言,勿强行翻译。
|
|
120
|
+
- 不写"// 自增计数器"这类废话注释;不在文档里写"我 / 我们"或 LLM 客套话。
|
|
121
|
+
|
|
122
|
+
## 11. 入场顺序(Agent 第一次进入本项目时)
|
|
123
|
+
|
|
124
|
+
1. 读 `README.md` 速览定位。
|
|
125
|
+
2. 读本文件(`CLAUDE.md`)—— 你正在读。
|
|
126
|
+
3. 读 `docs/02-development/00-overview.md`(定位 / 范围 / 决策 / 切片门禁)。
|
|
127
|
+
4. 按当前任务**只读**相关 slice 子文档;不要把全部文档塞进上下文。
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM One-click launcher for the HarnessSmith setup wizard.
|
|
3
|
+
REM Lets you pick how to configure your harness: the Web form (opens in a browser)
|
|
4
|
+
REM or an interactive CLI wizard (right here in the terminal). Prefers uv (auto-syncs
|
|
5
|
+
REM the [wizard] extra for the web form); offers to install uv if it's missing.
|
|
6
|
+
echo [HarnessSmith] Setup launcher
|
|
7
|
+
cd /d "%~dp0"
|
|
8
|
+
echo [HarnessSmith] Folder: %CD%
|
|
9
|
+
echo.
|
|
10
|
+
|
|
11
|
+
REM --- Reuse the system proxy (corporate networks) so the curl probe below, the
|
|
12
|
+
REM official uv install, and uv's own sync all reach the net the same way. Only when
|
|
13
|
+
REM none is already set; curl/uv don't read the Windows WinINET proxy on their own,
|
|
14
|
+
REM whereas the wizard's urllib probe DOES - so without this the curl probe could
|
|
15
|
+
REM wrongly report PyPI unreachable and pin a mirror the proxy can't even reach.
|
|
16
|
+
if defined HTTP_PROXY goto :proxy_done
|
|
17
|
+
if defined HTTPS_PROXY goto :proxy_done
|
|
18
|
+
for /f "usebackq delims=" %%p in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$s=Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction SilentlyContinue; if ($s.ProxyEnable -eq 1 -and $s.ProxyServer) { $p=[string]$s.ProxyServer; if ($p -match 'https?=([^;]+)') { $p=$matches[1] }; if ($p -notmatch '://') { $p='http://'+$p }; $p }"`) do set "HTTP_PROXY=%%p"
|
|
19
|
+
if not defined HTTP_PROXY goto :proxy_done
|
|
20
|
+
set "HTTPS_PROXY=%HTTP_PROXY%"
|
|
21
|
+
echo [HarnessSmith] Using system proxy: %HTTP_PROXY%
|
|
22
|
+
echo.
|
|
23
|
+
:proxy_done
|
|
24
|
+
|
|
25
|
+
REM Web form (browser) vs interactive CLI wizard (this terminal). Web is the
|
|
26
|
+
REM default on Windows (a browser is normally present). Set HARNESSMITH_MODE to
|
|
27
|
+
REM web or cli to skip the prompt (e.g. for automation). Flat goto flow only.
|
|
28
|
+
set "MODE=web"
|
|
29
|
+
if defined HARNESSMITH_MODE goto :mode_env
|
|
30
|
+
echo [HarnessSmith] How do you want to set up your harness?
|
|
31
|
+
echo [1] Web wizard - fill a form in your browser
|
|
32
|
+
echo [2] CLI wizard - interactive setup in this terminal
|
|
33
|
+
set /p "MODE= Choose [1/2] (Enter = web): "
|
|
34
|
+
if "%MODE%"=="2" set "MODE=cli"
|
|
35
|
+
if not "%MODE%"=="cli" set "MODE=web"
|
|
36
|
+
goto :mode_done
|
|
37
|
+
:mode_env
|
|
38
|
+
set "MODE=%HARNESSMITH_MODE%"
|
|
39
|
+
:mode_done
|
|
40
|
+
echo.
|
|
41
|
+
|
|
42
|
+
echo [HarnessSmith] Step 1/4: looking for uv on PATH ...
|
|
43
|
+
where uv >nul 2>nul
|
|
44
|
+
if not errorlevel 1 goto :run
|
|
45
|
+
|
|
46
|
+
echo [HarnessSmith] Step 2/4: checking known uv install locations ...
|
|
47
|
+
if exist "%USERPROFILE%\.local\bin\uv.exe" set "PATH=%USERPROFILE%\.local\bin;%PATH%"
|
|
48
|
+
if exist "%LOCALAPPDATA%\Microsoft\WinGet\Links\uv.exe" set "PATH=%LOCALAPPDATA%\Microsoft\WinGet\Links;%PATH%"
|
|
49
|
+
where uv >nul 2>nul
|
|
50
|
+
if not errorlevel 1 goto :run
|
|
51
|
+
|
|
52
|
+
REM Look for the installed console script by its .exe name on purpose: a bare
|
|
53
|
+
REM `harnessmith` would resolve to THIS file (HarnessSmith.bat) first, since
|
|
54
|
+
REM Windows searches the current dir and is case-insensitive -> infinite relaunch.
|
|
55
|
+
echo [HarnessSmith] Step 3/4: looking for an installed harnessmith command ...
|
|
56
|
+
where harnessmith.exe >nul 2>nul
|
|
57
|
+
if not errorlevel 1 goto :run_cmd
|
|
58
|
+
|
|
59
|
+
echo [HarnessSmith] Step 4/4: uv is not installed yet.
|
|
60
|
+
echo.
|
|
61
|
+
echo The wizard needs uv - a small tool that manages Python and dependencies for
|
|
62
|
+
echo you (user-level install, no admin required). How would you like to install it?
|
|
63
|
+
echo [1] Standard - official installer (downloads uv from GitHub)
|
|
64
|
+
echo [2] China mirror - pip + Tsinghua mirror (needs Python already installed)
|
|
65
|
+
echo [n] Don't install
|
|
66
|
+
set "CHOICE=1"
|
|
67
|
+
set /p "CHOICE= Choose [1/2/n]: "
|
|
68
|
+
if /i "%CHOICE%"=="n" goto :manual
|
|
69
|
+
if "%CHOICE%"=="2" goto :install_cn
|
|
70
|
+
|
|
71
|
+
REM Use the official installer directly (NOT winget): winget first updates its
|
|
72
|
+
REM own source CDN, which hangs for a long time and fails on some networks (e.g.
|
|
73
|
+
REM behind the GFW) even when the GitHub download itself works fine.
|
|
74
|
+
echo [HarnessSmith] Installing uv via the official installer ...
|
|
75
|
+
powershell -ExecutionPolicy Bypass -NoProfile -Command "irm https://astral.sh/uv/install.ps1 | iex"
|
|
76
|
+
if exist "%USERPROFILE%\.local\bin\uv.exe" set "PATH=%USERPROFILE%\.local\bin;%PATH%"
|
|
77
|
+
where uv >nul 2>nul
|
|
78
|
+
if not errorlevel 1 goto :run
|
|
79
|
+
goto :manual
|
|
80
|
+
|
|
81
|
+
:install_cn
|
|
82
|
+
echo [HarnessSmith] China mirror: installing uv from the Tsinghua PyPI mirror ...
|
|
83
|
+
set "PY="
|
|
84
|
+
where py >nul 2>nul && set "PY=py -3"
|
|
85
|
+
if not defined PY (
|
|
86
|
+
where python >nul 2>nul && set "PY=python"
|
|
87
|
+
)
|
|
88
|
+
if not defined PY (
|
|
89
|
+
echo [HarnessSmith] No Python found - the China-mirror path needs Python first.
|
|
90
|
+
goto :manual_cn
|
|
91
|
+
)
|
|
92
|
+
%PY% -m pip install --user uv -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|
93
|
+
%PY% -m uv --version >nul 2>nul
|
|
94
|
+
if errorlevel 1 (
|
|
95
|
+
echo [HarnessSmith] pip did not produce a runnable uv.
|
|
96
|
+
goto :manual_cn
|
|
97
|
+
)
|
|
98
|
+
echo [HarnessSmith] uv installed. Using the Tsinghua mirror + your system Python.
|
|
99
|
+
set "UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
100
|
+
set "UV_PYTHON_PREFERENCE=only-system"
|
|
101
|
+
goto :run_py
|
|
102
|
+
|
|
103
|
+
:manual
|
|
104
|
+
echo.
|
|
105
|
+
echo [HarnessSmith] Could not find or install uv. Install it manually (user-level, no admin):
|
|
106
|
+
echo winget install astral-sh.uv
|
|
107
|
+
echo - or - powershell -c "irm https://astral.sh/uv/install.ps1 ^| iex"
|
|
108
|
+
echo then double-click this again.
|
|
109
|
+
echo.
|
|
110
|
+
pause
|
|
111
|
+
goto :eof
|
|
112
|
+
|
|
113
|
+
:manual_cn
|
|
114
|
+
echo.
|
|
115
|
+
echo [HarnessSmith] The China-mirror path needs Python. Options:
|
|
116
|
+
echo 1) Install Python (https://www.python.org/downloads/ , reachable in China),
|
|
117
|
+
echo then run this again and choose [2].
|
|
118
|
+
echo 2) Or with Python present: pip install uv -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|
119
|
+
echo 3) Or use a VPN/proxy and choose [1].
|
|
120
|
+
echo.
|
|
121
|
+
pause
|
|
122
|
+
goto :eof
|
|
123
|
+
|
|
124
|
+
:run
|
|
125
|
+
REM Auto-pick the package index: when no index is pinned and the official PyPI is
|
|
126
|
+
REM unreachable (e.g. behind the GFW), use the Tsinghua mirror for this run so the
|
|
127
|
+
REM first `uv sync` can still fetch deps. An explicit UV_DEFAULT_INDEX always wins;
|
|
128
|
+
REM we only probe when curl is present (ships with Windows 10 1803+).
|
|
129
|
+
if defined UV_DEFAULT_INDEX goto :run_go
|
|
130
|
+
where curl >nul 2>nul
|
|
131
|
+
if errorlevel 1 goto :run_go
|
|
132
|
+
echo [HarnessSmith] Checking whether PyPI is reachable ...
|
|
133
|
+
curl -sS -m 3 -I https://pypi.org/simple/ >nul 2>nul
|
|
134
|
+
if not errorlevel 1 goto :run_go
|
|
135
|
+
echo [HarnessSmith] PyPI looks unreachable - using the Tsinghua mirror this run.
|
|
136
|
+
set "UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
137
|
+
:run_go
|
|
138
|
+
if "%MODE%"=="cli" goto :run_go_cli
|
|
139
|
+
echo [HarnessSmith] Launching: uv run --extra wizard harnessmith wizard --open
|
|
140
|
+
echo.
|
|
141
|
+
uv run --extra wizard harnessmith wizard --open
|
|
142
|
+
goto :after_run
|
|
143
|
+
:run_go_cli
|
|
144
|
+
echo [HarnessSmith] Launching: uv run harnessmith new
|
|
145
|
+
echo.
|
|
146
|
+
uv run harnessmith new
|
|
147
|
+
goto :after_run
|
|
148
|
+
|
|
149
|
+
:run_cmd
|
|
150
|
+
if "%MODE%"=="cli" goto :run_cmd_cli
|
|
151
|
+
echo [HarnessSmith] Launching: harnessmith.exe wizard --open
|
|
152
|
+
echo.
|
|
153
|
+
harnessmith.exe wizard --open
|
|
154
|
+
goto :after_run
|
|
155
|
+
:run_cmd_cli
|
|
156
|
+
echo [HarnessSmith] Launching: harnessmith.exe new
|
|
157
|
+
echo.
|
|
158
|
+
harnessmith.exe new
|
|
159
|
+
goto :after_run
|
|
160
|
+
|
|
161
|
+
:run_py
|
|
162
|
+
if "%MODE%"=="cli" goto :run_py_cli
|
|
163
|
+
echo [HarnessSmith] Launching: %PY% -m uv run --extra wizard harnessmith wizard --open
|
|
164
|
+
echo.
|
|
165
|
+
%PY% -m uv run --extra wizard harnessmith wizard --open
|
|
166
|
+
goto :after_run
|
|
167
|
+
:run_py_cli
|
|
168
|
+
echo [HarnessSmith] Launching: %PY% -m uv run harnessmith new
|
|
169
|
+
echo.
|
|
170
|
+
%PY% -m uv run harnessmith new
|
|
171
|
+
goto :after_run
|
|
172
|
+
|
|
173
|
+
:after_run
|
|
174
|
+
echo.
|
|
175
|
+
echo [HarnessSmith] Process exited (code %errorlevel%). Press a key to close.
|
|
176
|
+
pause >nul
|
|
177
|
+
goto :eof
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One-click launcher for the HarnessSmith setup wizard.
|
|
3
|
+
#
|
|
4
|
+
# Lets you pick how to configure your harness: the Web form (opens in a browser)
|
|
5
|
+
# or an interactive CLI wizard (right here in the terminal). Prefers `uv` (which
|
|
6
|
+
# auto-syncs the optional [wizard] extra for the web form); if uv is missing it
|
|
7
|
+
# offers to install it (user-level, no root).
|
|
8
|
+
set -e
|
|
9
|
+
echo "[HarnessSmith] Setup launcher"
|
|
10
|
+
cd "$(dirname "$0")"
|
|
11
|
+
echo "[HarnessSmith] Folder: $PWD"
|
|
12
|
+
|
|
13
|
+
find_uv() {
|
|
14
|
+
if command -v uv >/dev/null 2>&1; then echo uv; return 0; fi
|
|
15
|
+
[ -x "$HOME/.local/bin/uv" ] && { echo "$HOME/.local/bin/uv"; return 0; }
|
|
16
|
+
return 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Web form vs interactive CLI wizard. On a headless Linux box (no display) the
|
|
20
|
+
# terminal wizard is the sensible default — the web form would only be reachable
|
|
21
|
+
# via SSH port-forward; elsewhere the browser form is the default. Echoes "web"
|
|
22
|
+
# or "cli" on stdout (prompts go to stderr). Set HARNESSMITH_MODE=web|cli to
|
|
23
|
+
# skip the prompt (e.g. for automation).
|
|
24
|
+
choose_mode() {
|
|
25
|
+
if [ -n "${HARNESSMITH_MODE:-}" ]; then echo "$HARNESSMITH_MODE"; return 0; fi
|
|
26
|
+
default=web
|
|
27
|
+
if [ "$(uname)" = Linux ] && [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
|
|
28
|
+
default=cli
|
|
29
|
+
fi
|
|
30
|
+
{
|
|
31
|
+
echo "[HarnessSmith] How do you want to set up your harness?"
|
|
32
|
+
echo " [1] Web wizard - fill a form in your browser"
|
|
33
|
+
echo " [2] CLI wizard - interactive setup here in the terminal"
|
|
34
|
+
printf " Choose [1/2] (Enter = %s): " "$default"
|
|
35
|
+
} >&2
|
|
36
|
+
read -r reply </dev/tty 2>/dev/null || reply=""
|
|
37
|
+
case "$reply" in
|
|
38
|
+
1) echo web ;;
|
|
39
|
+
2) echo cli ;;
|
|
40
|
+
*) echo "$default" ;;
|
|
41
|
+
esac
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Auto-pick the package index: when no index is pinned and the official PyPI is
|
|
45
|
+
# unreachable (e.g. behind the GFW), use the Tsinghua mirror for this run so the
|
|
46
|
+
# first `uv sync` can still fetch deps. An explicit UV_DEFAULT_INDEX always wins;
|
|
47
|
+
# we only probe when curl is available.
|
|
48
|
+
pick_index() {
|
|
49
|
+
[ -n "${UV_DEFAULT_INDEX:-}" ] && return 0
|
|
50
|
+
command -v curl >/dev/null 2>&1 || return 0
|
|
51
|
+
echo "[HarnessSmith] Checking whether PyPI is reachable ..."
|
|
52
|
+
if curl -sS -m 3 -I https://pypi.org/simple/ >/dev/null 2>&1; then return 0; fi
|
|
53
|
+
echo "[HarnessSmith] PyPI looks unreachable - using the Tsinghua mirror this run."
|
|
54
|
+
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Reuse the system proxy so the curl probe above, the uv install, and uv's sync all
|
|
58
|
+
# reach the net the same way. Only when none is already set. macOS's GUI proxy isn't
|
|
59
|
+
# read by curl/uv on their own, so on a Mac we read it via scutil and export it
|
|
60
|
+
# (Linux users set *_PROXY as usual -> this is a no-op there). Best-effort: any parse
|
|
61
|
+
# miss just leaves the env untouched. Mirrors the wizard's proxy-aware urllib probe.
|
|
62
|
+
pick_proxy() {
|
|
63
|
+
[ -n "${HTTP_PROXY:-}${HTTPS_PROXY:-}${http_proxy:-}${https_proxy:-}" ] && return 0
|
|
64
|
+
[ "$(uname)" = Darwin ] || return 0
|
|
65
|
+
command -v scutil >/dev/null 2>&1 || return 0
|
|
66
|
+
local enabled host port px
|
|
67
|
+
enabled="$(scutil --proxy 2>/dev/null | awk '/^[[:space:]]*HTTPSEnable/ {print $3}')"
|
|
68
|
+
[ "$enabled" = 1 ] || return 0
|
|
69
|
+
host="$(scutil --proxy 2>/dev/null | awk '/^[[:space:]]*HTTPSProxy/ {print $3}')"
|
|
70
|
+
[ -n "$host" ] || return 0
|
|
71
|
+
port="$(scutil --proxy 2>/dev/null | awk '/^[[:space:]]*HTTPSPort/ {print $3}')"
|
|
72
|
+
px="http://$host"
|
|
73
|
+
[ -n "$port" ] && px="$px:$port"
|
|
74
|
+
export HTTP_PROXY="$px" HTTPS_PROXY="$px"
|
|
75
|
+
echo "[HarnessSmith] Using system proxy: $px"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pick_proxy
|
|
79
|
+
|
|
80
|
+
echo "[HarnessSmith] Looking for uv ..."
|
|
81
|
+
uv_bin="$(find_uv || true)"
|
|
82
|
+
if [ -z "$uv_bin" ]; then
|
|
83
|
+
# No uv yet: an already-installed harnessmith command works too.
|
|
84
|
+
if command -v harnessmith >/dev/null 2>&1; then
|
|
85
|
+
echo "[HarnessSmith] Found the harnessmith command."
|
|
86
|
+
if [ "$(choose_mode)" = cli ]; then
|
|
87
|
+
echo "[HarnessSmith] Launching: harnessmith new"
|
|
88
|
+
exec harnessmith new
|
|
89
|
+
fi
|
|
90
|
+
echo "[HarnessSmith] Launching: harnessmith wizard --open"
|
|
91
|
+
exec harnessmith wizard --open
|
|
92
|
+
fi
|
|
93
|
+
echo "[HarnessSmith] uv is not installed yet. How would you like to install it?"
|
|
94
|
+
echo " [1] Standard - official installer (downloads from GitHub)"
|
|
95
|
+
echo " [2] China mirror - pip + Tsinghua mirror (needs python3 already installed)"
|
|
96
|
+
echo " [n] Don't install"
|
|
97
|
+
printf " Choose [1/2/n]: "
|
|
98
|
+
read -r choice </dev/tty 2>/dev/null || choice=1
|
|
99
|
+
case "$choice" in
|
|
100
|
+
[Nn]*) ;;
|
|
101
|
+
2)
|
|
102
|
+
if command -v python3 >/dev/null 2>&1; then py=python3
|
|
103
|
+
elif command -v python >/dev/null 2>&1; then py=python
|
|
104
|
+
else py=; fi
|
|
105
|
+
if [ -n "$py" ]; then
|
|
106
|
+
echo "[HarnessSmith] Installing uv via pip + Tsinghua mirror ..."
|
|
107
|
+
"$py" -m pip install --user uv -i https://pypi.tuna.tsinghua.edu.cn/simple || true
|
|
108
|
+
if "$py" -m uv --version >/dev/null 2>&1; then
|
|
109
|
+
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
|
|
110
|
+
export UV_PYTHON_PREFERENCE=only-system
|
|
111
|
+
echo "[HarnessSmith] Launching: $py -m uv run --extra wizard harnessmith wizard --open"
|
|
112
|
+
exec "$py" -m uv run --extra wizard harnessmith wizard --open
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
echo "[HarnessSmith] China path needs python3 first (then: pip install uv -i <Tsinghua>)." >&2
|
|
116
|
+
;;
|
|
117
|
+
*)
|
|
118
|
+
echo "[HarnessSmith] Installing uv via the official installer ..."
|
|
119
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh \
|
|
120
|
+
|| echo "[HarnessSmith] uv install failed; see https://docs.astral.sh/uv/" >&2 ;;
|
|
121
|
+
esac
|
|
122
|
+
uv_bin="$(find_uv || true)"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -n "$uv_bin" ]; then
|
|
126
|
+
pick_index
|
|
127
|
+
if [ "$(choose_mode)" = cli ]; then
|
|
128
|
+
echo "[HarnessSmith] Launching: $uv_bin run harnessmith new"
|
|
129
|
+
exec "$uv_bin" run harnessmith new
|
|
130
|
+
fi
|
|
131
|
+
echo "[HarnessSmith] Launching: $uv_bin run --extra wizard harnessmith wizard --open"
|
|
132
|
+
exec "$uv_bin" run --extra wizard harnessmith wizard --open
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
echo "[HarnessSmith] Could not find or install uv. Install it (user-level, no root):" >&2
|
|
136
|
+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh" >&2
|
|
137
|
+
echo " - or (China) - pip install uv -i https://pypi.tuna.tsinghua.edu.cn/simple" >&2
|
|
138
|
+
echo " then re-run this script." >&2
|
|
139
|
+
exit 1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EpisodeYu
|
|
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.
|