@angli/unit-test-tool 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/.claude/settings.local.json +14 -0
- package/README.md +232 -0
- package/dist/src/cli/commands/analyze.js +20 -0
- package/dist/src/cli/commands/guard.js +26 -0
- package/dist/src/cli/commands/init.js +39 -0
- package/dist/src/cli/commands/run.js +72 -0
- package/dist/src/cli/commands/schedule.js +29 -0
- package/dist/src/cli/commands/start.js +102 -0
- package/dist/src/cli/commands/status.js +27 -0
- package/dist/src/cli/commands/verify.js +15 -0
- package/dist/src/cli/context/create-context.js +101 -0
- package/dist/src/cli/index.js +23 -0
- package/dist/src/cli/utils/scan-dir.js +6 -0
- package/dist/src/core/analyzers/coverage-analyzer.js +8 -0
- package/dist/src/core/analyzers/dependency-complexity-analyzer.js +19 -0
- package/dist/src/core/analyzers/existing-test-analyzer.js +66 -0
- package/dist/src/core/analyzers/failure-history-analyzer.js +9 -0
- package/dist/src/core/analyzers/file-classifier-analyzer.js +24 -0
- package/dist/src/core/analyzers/index.js +37 -0
- package/dist/src/core/analyzers/llm-semantic-analyzer.js +3 -0
- package/dist/src/core/analyzers/path-priority-analyzer.js +34 -0
- package/dist/src/core/coverage/read-coverage-summary.js +183 -0
- package/dist/src/core/executor/claude-cli-executor.js +91 -0
- package/dist/src/core/middleware/loop-detection.js +6 -0
- package/dist/src/core/middleware/pre-completion-checklist.js +27 -0
- package/dist/src/core/middleware/silent-success-post-check.js +13 -0
- package/dist/src/core/planner/rank-candidates.js +3 -0
- package/dist/src/core/planner/rule-planner.js +41 -0
- package/dist/src/core/planner/score-candidate.js +49 -0
- package/dist/src/core/prompts/case-library.js +35 -0
- package/dist/src/core/prompts/edit-boundary-prompt.js +24 -0
- package/dist/src/core/prompts/retry-prompt.js +18 -0
- package/dist/src/core/prompts/system-prompt.js +27 -0
- package/dist/src/core/prompts/task-prompt.js +22 -0
- package/dist/src/core/reporter/index.js +48 -0
- package/dist/src/core/state-machine/index.js +12 -0
- package/dist/src/core/storage/defaults.js +16 -0
- package/dist/src/core/storage/event-store.js +16 -0
- package/dist/src/core/storage/lifecycle-store.js +18 -0
- package/dist/src/core/storage/report-store.js +16 -0
- package/dist/src/core/storage/state-store.js +11 -0
- package/dist/src/core/strategies/classify-failure.js +14 -0
- package/dist/src/core/strategies/switch-mock-strategy.js +9 -0
- package/dist/src/core/tools/analyze-baseline.js +54 -0
- package/dist/src/core/tools/guard.js +68 -0
- package/dist/src/core/tools/run-loop.js +108 -0
- package/dist/src/core/tools/run-with-claude-cli.js +645 -0
- package/dist/src/core/tools/verify-all.js +75 -0
- package/dist/src/core/worktrees/is-git-repo.js +10 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/types/logger.js +1 -0
- package/dist/src/utils/clock.js +10 -0
- package/dist/src/utils/command-runner.js +18 -0
- package/dist/src/utils/commands.js +28 -0
- package/dist/src/utils/duration.js +22 -0
- package/dist/src/utils/fs.js +53 -0
- package/dist/src/utils/logger.js +10 -0
- package/dist/src/utils/paths.js +21 -0
- package/dist/src/utils/process-lifecycle.js +74 -0
- package/dist/src/utils/prompts.js +20 -0
- package/dist/tests/core/create-context.test.js +41 -0
- package/dist/tests/core/default-state.test.js +10 -0
- package/dist/tests/core/failure-classification.test.js +7 -0
- package/dist/tests/core/loop-detection.test.js +7 -0
- package/dist/tests/core/paths.test.js +11 -0
- package/dist/tests/core/prompt-builders.test.js +33 -0
- package/dist/tests/core/score-candidate.test.js +28 -0
- package/dist/tests/core/state-machine.test.js +12 -0
- package/dist/tests/integration/status-report.test.js +21 -0
- package/docs/architecture.md +20 -0
- package/docs/demo.sh +266 -0
- package/docs/skill-integration.md +15 -0
- package/docs/state-machine.md +15 -0
- package/package.json +31 -0
- package/src/cli/commands/analyze.ts +22 -0
- package/src/cli/commands/guard.ts +28 -0
- package/src/cli/commands/init.ts +41 -0
- package/src/cli/commands/run.ts +79 -0
- package/src/cli/commands/schedule.ts +32 -0
- package/src/cli/commands/start.ts +111 -0
- package/src/cli/commands/status.ts +30 -0
- package/src/cli/commands/verify.ts +17 -0
- package/src/cli/context/create-context.ts +142 -0
- package/src/cli/index.ts +27 -0
- package/src/cli/utils/scan-dir.ts +5 -0
- package/src/core/analyzers/coverage-analyzer.ts +10 -0
- package/src/core/analyzers/dependency-complexity-analyzer.ts +25 -0
- package/src/core/analyzers/existing-test-analyzer.ts +76 -0
- package/src/core/analyzers/failure-history-analyzer.ts +12 -0
- package/src/core/analyzers/file-classifier-analyzer.ts +25 -0
- package/src/core/analyzers/index.ts +51 -0
- package/src/core/analyzers/llm-semantic-analyzer.ts +6 -0
- package/src/core/analyzers/path-priority-analyzer.ts +41 -0
- package/src/core/coverage/read-coverage-summary.ts +224 -0
- package/src/core/executor/claude-cli-executor.ts +94 -0
- package/src/core/middleware/loop-detection.ts +8 -0
- package/src/core/middleware/pre-completion-checklist.ts +32 -0
- package/src/core/middleware/silent-success-post-check.ts +16 -0
- package/src/core/planner/rank-candidates.ts +5 -0
- package/src/core/planner/rule-planner.ts +65 -0
- package/src/core/planner/score-candidate.ts +60 -0
- package/src/core/prompts/case-library.ts +36 -0
- package/src/core/prompts/edit-boundary-prompt.ts +26 -0
- package/src/core/prompts/retry-prompt.ts +22 -0
- package/src/core/prompts/system-prompt.ts +32 -0
- package/src/core/prompts/task-prompt.ts +26 -0
- package/src/core/reporter/index.ts +56 -0
- package/src/core/state-machine/index.ts +14 -0
- package/src/core/storage/defaults.ts +18 -0
- package/src/core/storage/event-store.ts +18 -0
- package/src/core/storage/lifecycle-store.ts +20 -0
- package/src/core/storage/report-store.ts +19 -0
- package/src/core/storage/state-store.ts +18 -0
- package/src/core/strategies/classify-failure.ts +9 -0
- package/src/core/strategies/switch-mock-strategy.ts +12 -0
- package/src/core/tools/analyze-baseline.ts +61 -0
- package/src/core/tools/guard.ts +89 -0
- package/src/core/tools/run-loop.ts +142 -0
- package/src/core/tools/run-with-claude-cli.ts +926 -0
- package/src/core/tools/verify-all.ts +83 -0
- package/src/core/worktrees/is-git-repo.ts +10 -0
- package/src/types/index.ts +291 -0
- package/src/types/logger.ts +6 -0
- package/src/utils/clock.ts +10 -0
- package/src/utils/command-runner.ts +24 -0
- package/src/utils/commands.ts +42 -0
- package/src/utils/duration.ts +20 -0
- package/src/utils/fs.ts +50 -0
- package/src/utils/logger.ts +12 -0
- package/src/utils/paths.ts +24 -0
- package/src/utils/process-lifecycle.ts +92 -0
- package/src/utils/prompts.ts +22 -0
- package/tests/core/create-context.test.ts +45 -0
- package/tests/core/default-state.test.ts +11 -0
- package/tests/core/failure-classification.test.ts +8 -0
- package/tests/core/loop-detection.test.ts +8 -0
- package/tests/core/paths.test.ts +13 -0
- package/tests/core/prompt-builders.test.ts +38 -0
- package/tests/core/score-candidate.test.ts +30 -0
- package/tests/core/state-machine.test.ts +14 -0
- package/tests/fixtures/simple-project/.openclaw-testbot/logs/events.jsonl +10 -0
- package/tests/fixtures/simple-project/.openclaw-testbot/plan.json +75 -0
- package/tests/fixtures/simple-project/.openclaw-testbot/reports/coverage-summary.json +9 -0
- package/tests/fixtures/simple-project/.openclaw-testbot/reports/final-report.json +14 -0
- package/tests/fixtures/simple-project/.openclaw-testbot/state.json +18 -0
- package/tests/fixtures/simple-project/coverage-summary.json +1 -0
- package/tests/fixtures/simple-project/package.json +8 -0
- package/tests/fixtures/simple-project/src/add.js +3 -0
- package/tests/fixtures/simple-project/test-runner.js +18 -0
- package/tests/integration/status-report.test.ts +24 -0
- package/tsconfig.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# TestBot(unit-test-tool)
|
|
2
|
+
|
|
3
|
+
面向项目的单元测试补全工具,基于 CLI 工作流完成基线分析、推荐测试目标,并调用 Claude CLI 进行补测。
|
|
4
|
+
|
|
5
|
+
## 适用场景
|
|
6
|
+
|
|
7
|
+
- 需要快速提升项目单测覆盖率
|
|
8
|
+
- 希望以可控参数(覆盖率目标、扫描目录、指定文件)进行补测
|
|
9
|
+
- 想将“分析 → 补测 → 验证 → 汇总”串成自动化流程
|
|
10
|
+
|
|
11
|
+
## 依赖与准备
|
|
12
|
+
|
|
13
|
+
- 需要本地可用的 Claude CLI(用于 `run` / `start` 补测流程)
|
|
14
|
+
- 在目标项目路径下可运行测试/校验命令(可通过参数覆盖)
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
### 全局安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @angli/unit-test-tool
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
安装后可直接使用:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
testbot --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 命令概览(保留关键命令)
|
|
31
|
+
|
|
32
|
+
- `testbot init`:初始化配置
|
|
33
|
+
- `testbot analyze`:基线分析
|
|
34
|
+
- `testbot run`:单次调用 Claude CLI 补测
|
|
35
|
+
- `testbot start`:初始化 + 基线分析 + 循环补测 + 状态汇总
|
|
36
|
+
- `testbot verify`:完整验证
|
|
37
|
+
- `testbot status`:输出状态
|
|
38
|
+
- `testbot report`:生成最终报告
|
|
39
|
+
- `testbot guard`:检测异常并尝试恢复
|
|
40
|
+
- `testbot schedule`:定时执行 guard
|
|
41
|
+
|
|
42
|
+
## 参数说明(表格)
|
|
43
|
+
|
|
44
|
+
> 说明:以下为各命令支持的参数集合,不同命令支持的参数略有差异。
|
|
45
|
+
|
|
46
|
+
| 参数 | 说明 | 默认值 | 适用命令 |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `--project <path>` | 项目路径 | `process.cwd()` 或必填 | `init`(必填)、`analyze`、`run`、`start`、`verify`、`status`、`report` |
|
|
49
|
+
| `--target <number>` | 覆盖率目标 | `80` | `init`、`analyze`、`run`、`start` |
|
|
50
|
+
| `--scan-dir <dir>` | 扫描目录 | `src` | `init`、`analyze`、`run`、`start` |
|
|
51
|
+
| `--file <path>` | 指定目标源文件 | 无 | `run`、`start` |
|
|
52
|
+
| `--topN <number>` | 推荐池大小 | `5` | `run`、`start` |
|
|
53
|
+
| `--permission-mode <mode>` | Claude CLI 权限模式 | 无 | `run`、`start` |
|
|
54
|
+
| `--allowed-tools <tools>` | Claude CLI 允许工具列表(逗号分隔) | 无 | `run`、`start` |
|
|
55
|
+
| `--timeout <ms>` | Claude CLI 超时(毫秒) | 无 | `run`、`start` |
|
|
56
|
+
| `--verify-cmd <cmd>` | 覆盖完整验证命令 | 无 | `init`、`run`、`start` |
|
|
57
|
+
| `--lint-cmd <cmd>` | 覆盖 lint 命令 | 无 | `init`、`run`、`start` |
|
|
58
|
+
| `--typecheck-cmd <cmd>` | 覆盖类型检查命令 | 无 | `init`、`run`、`start` |
|
|
59
|
+
| `--system-append <text>` | 追加项目级 system prompt 规则 | 无 | `init` |
|
|
60
|
+
| `--task-append <text>` | 追加项目级 task prompt 规则 | 无 | `init` |
|
|
61
|
+
| `--retry-append <text>` | 追加项目级 retry prompt 规则 | 无 | `init` |
|
|
62
|
+
| `--schedule` | 在 `start` 期间启用内置守护循环 | `false` | `start` |
|
|
63
|
+
| `--guard-interval <duration>` | 守护检查间隔 | `5m` | `start` |
|
|
64
|
+
| `--interval <duration>` | 定时 guard 间隔 | `5m` | `schedule` |
|
|
65
|
+
| `--stale-timeout <duration>` | heartbeat 超时阈值 | `2m` | `guard`、`schedule`、`start` |
|
|
66
|
+
| `--max-restart <count>` | 最大自动恢复次数 | `3` | `guard`、`schedule`、`start` |
|
|
67
|
+
| `--cooldown <duration>` | 两次恢复的冷却时间 | `60s` | `guard`、`schedule`、`start` |
|
|
68
|
+
|
|
69
|
+
## 日志与回放
|
|
70
|
+
|
|
71
|
+
运行时产物位于目标项目的 `.unit_test_tool_workspace/` 目录。
|
|
72
|
+
|
|
73
|
+
### 目录结构
|
|
74
|
+
|
|
75
|
+
- `logs/`:stdout/stderr 日志、`events.jsonl`
|
|
76
|
+
- `prompts/`:每个任务独立的 prompt 文件
|
|
77
|
+
- `prompt-runs/`:按一次 attempt 追加的整轮 prompt 索引/引用日志
|
|
78
|
+
- `reports/`:coverage summary / final report
|
|
79
|
+
|
|
80
|
+
### events.jsonl 怎么看
|
|
81
|
+
|
|
82
|
+
`events.jsonl` 是 JSONL 格式,一行一个事件。常见事件类型:
|
|
83
|
+
|
|
84
|
+
- `cli_run_started`:选中目标文件
|
|
85
|
+
- `cli_run_attempt`:开始调用 Claude CLI(含 prompt 路径)
|
|
86
|
+
- `cli_run_completed`:本轮成功
|
|
87
|
+
- `cli_run_failed`:本轮失败或 no-op
|
|
88
|
+
- `baseline_analyzed`:基线分析完成
|
|
89
|
+
|
|
90
|
+
建议优先关注 `data` 里的这些字段:
|
|
91
|
+
|
|
92
|
+
- `runId`:本次运行批次(例如 `r20260403T022232Z`)
|
|
93
|
+
- `taskId`:任务 ID(例如 `r20260403T022232Z-001`)
|
|
94
|
+
- `attemptLabel`:尝试标记(例如 `a1`)
|
|
95
|
+
- `targetBaseName`:目标文件 basename
|
|
96
|
+
- `promptPath / stdoutPath / stderrPath`:任务级产物路径
|
|
97
|
+
- `runPromptPath`:整轮 prompt 汇总文件路径
|
|
98
|
+
|
|
99
|
+
### 文件名组成规则
|
|
100
|
+
|
|
101
|
+
任务级产物文件名统一遵循:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
{taskId}-a{attempt}-{baseName}-{pathHash}-{timestamp}.{kind}.{ext}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
示例:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
r20260403T022232Z-001-a1-corpus-vue-4e2b1c9a-20260403T022232Z.prompt.txt
|
|
111
|
+
r20260403T022232Z-001-a1-corpus-vue-4e2b1c9a-20260403T022232Z.stdout.log
|
|
112
|
+
r20260403T022232Z-001-a1-corpus-vue-4e2b1c9a-20260403T022232Z.stderr.log
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
字段含义:
|
|
116
|
+
|
|
117
|
+
- `taskId`:`runId + 序号`
|
|
118
|
+
- `attempt`:第几次尝试
|
|
119
|
+
- `baseName`:目标文件 basename
|
|
120
|
+
- `pathHash`:目标文件路径 hash(短 hash)
|
|
121
|
+
- `timestamp`:本次 attempt 的时间戳
|
|
122
|
+
- `kind`:`prompt` / `stdout` / `stderr`
|
|
123
|
+
|
|
124
|
+
### run 级 prompt 汇总文件
|
|
125
|
+
|
|
126
|
+
整轮流程的 prompt 汇总写到单个文件(不再内嵌 prompt 正文,改为记录 prompt 文件路径):
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
run-{runId}.prompt.log
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
查看完整 prompt 内容,请到 `prompts/*.prompt.txt`。
|
|
133
|
+
|
|
134
|
+
推荐排查顺序:
|
|
135
|
+
|
|
136
|
+
1. `state.json` / `final-report.json`
|
|
137
|
+
2. `logs/events.jsonl`
|
|
138
|
+
3. `prompts/` 和 `logs/` 里的单任务产物
|
|
139
|
+
4. `prompt-runs/` 里的整轮回放
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### 1) 初始化配置
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
testbot init --project /path/to/project --target 85 --scan-dir src
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
如需给某个项目追加测试生成规则,可在初始化时一并写入:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
testbot init \
|
|
152
|
+
--project /path/to/project \
|
|
153
|
+
--target 85 \
|
|
154
|
+
--system-append "项目里大量使用 provide/inject,优先复用现有 context 装配方式" \
|
|
155
|
+
--task-append "涉及表格组件时,优先断言列配置和事件,不做整表 DOM 快照" \
|
|
156
|
+
--retry-append "如果 wrapper 测试连续失败,退回到 render 参数和事件透传断言"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 2) 仅做基线分析
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
testbot analyze --project /path/to/project --target 80
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 3) 单次补测(可指定文件)
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
testbot run --project /path/to/project --file src/foo.ts --topN 3 --permission-mode ask
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 4) 一站式流程(初始化 + 分析 + 循环补测 + 状态)
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
testbot start --project /path/to/project --target 80 --topN 5
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 5) 完整验证与状态
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
testbot verify --project /path/to/project
|
|
181
|
+
|
|
182
|
+
testbot status --project /path/to/project
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 7) 守护与定时恢复
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
testbot guard --project /path/to/project
|
|
189
|
+
|
|
190
|
+
testbot schedule --project /path/to/project --interval 5m
|
|
191
|
+
|
|
192
|
+
# 在 start 期间启用守护
|
|
193
|
+
|
|
194
|
+
testbot start --project /path/to/project --schedule --guard-interval 5m
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 8) Claude CLI 项目级配置
|
|
198
|
+
|
|
199
|
+
补测执行时会优先读取“待补测项目目录”下的 `.claude/settings.local.json`,并使用其中 `env` 的 `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` 覆盖全局环境变量。
|
|
200
|
+
|
|
201
|
+
示例:
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"env": {
|
|
206
|
+
"ANTHROPIC_AUTH_TOKEN": "...",
|
|
207
|
+
"ANTHROPIC_BASE_URL": "..."
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 9) config.json 中的 promptOverrides
|
|
213
|
+
|
|
214
|
+
`testbot init` 写入的 `.unit_test_tool_workspace/config.json` 支持项目级 prompt 覆盖。工具会先使用内置“通用单测策略 + 案例片段库”,再追加这里的覆盖内容。
|
|
215
|
+
|
|
216
|
+
示例:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"projectPath": "/path/to/project",
|
|
221
|
+
"coverageTarget": 85,
|
|
222
|
+
"include": ["src/**/*"],
|
|
223
|
+
"exclude": ["node_modules/**", "dist/**", "coverage/**"],
|
|
224
|
+
"mode": "serial",
|
|
225
|
+
"concurrency": 1,
|
|
226
|
+
"promptOverrides": {
|
|
227
|
+
"systemAppend": "项目里大量使用 provide/inject,生成测试前先识别上下文注入入口。",
|
|
228
|
+
"taskAppend": "表格/列表组件优先测列配置、事件和状态切换,不做大段 DOM 快照。",
|
|
229
|
+
"retryAppend": "render/HOC 测试连续失败时,回退到参数透传和派生 props 断言。"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { analyzeBaseline } from '../../core/tools/analyze-baseline.js';
|
|
3
|
+
import { buildIncludeFromScanDir } from '../utils/scan-dir.js';
|
|
4
|
+
export function registerAnalyzeCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('analyze')
|
|
7
|
+
.option('--project <path>', 'project path')
|
|
8
|
+
.option('--target <number>', 'coverage target', '80')
|
|
9
|
+
.option('--scan-dir <dir>', 'scan dir (default: src)')
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
const include = buildIncludeFromScanDir(options.scanDir ?? 'src');
|
|
12
|
+
const ctx = await createContext({
|
|
13
|
+
projectPath: options.project ?? process.cwd(),
|
|
14
|
+
coverageTarget: Number(options.target),
|
|
15
|
+
configOverrides: include ? { include } : undefined
|
|
16
|
+
});
|
|
17
|
+
const result = await analyzeBaseline(ctx);
|
|
18
|
+
console.log(result.summary);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { runGuard } from '../../core/tools/guard.js';
|
|
3
|
+
import { parseDuration } from '../../utils/duration.js';
|
|
4
|
+
export function registerGuardCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('guard')
|
|
7
|
+
.option('--project <path>', 'project path')
|
|
8
|
+
.option('--stale-timeout <duration>', 'heartbeat stale timeout (default: 2m)')
|
|
9
|
+
.option('--max-restart <count>', 'max restart attempts (default: 3)')
|
|
10
|
+
.option('--cooldown <duration>', 'restart cooldown (default: 60s)')
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
const ctx = await createContext({
|
|
13
|
+
projectPath: options.project ?? process.cwd(),
|
|
14
|
+
coverageTarget: 80
|
|
15
|
+
});
|
|
16
|
+
const staleTimeoutMs = options.staleTimeout ? parseDuration(options.staleTimeout) : undefined;
|
|
17
|
+
const cooldownMs = options.cooldown ? parseDuration(options.cooldown) : undefined;
|
|
18
|
+
const maxRestart = options.maxRestart ? Number(options.maxRestart) : undefined;
|
|
19
|
+
const result = await runGuard(ctx, {
|
|
20
|
+
staleTimeoutMs: staleTimeoutMs,
|
|
21
|
+
maxRestart,
|
|
22
|
+
cooldownMs
|
|
23
|
+
});
|
|
24
|
+
console.log(result.summary);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { buildIncludeFromScanDir } from '../utils/scan-dir.js';
|
|
3
|
+
export function registerInitCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('init')
|
|
6
|
+
.requiredOption('--project <path>', 'project path')
|
|
7
|
+
.option('--target <number>', 'coverage target', '80')
|
|
8
|
+
.option('--scan-dir <dir>', 'scan dir (default: src)')
|
|
9
|
+
.option('--verify-cmd <cmd>', 'override full verify command')
|
|
10
|
+
.option('--lint-cmd <cmd>', 'override lint command')
|
|
11
|
+
.option('--typecheck-cmd <cmd>', 'override typecheck command')
|
|
12
|
+
.option('--system-append <text>', 'append project-level system prompt guidance')
|
|
13
|
+
.option('--task-append <text>', 'append project-level task prompt guidance')
|
|
14
|
+
.option('--retry-append <text>', 'append project-level retry prompt guidance')
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
const include = buildIncludeFromScanDir(options.scanDir ?? 'src');
|
|
17
|
+
const commandOverrides = {
|
|
18
|
+
...(options.verifyCmd ? { verifyFull: options.verifyCmd } : {}),
|
|
19
|
+
...(options.lintCmd ? { lint: options.lintCmd } : {}),
|
|
20
|
+
...(options.typecheckCmd ? { typecheck: options.typecheckCmd } : {})
|
|
21
|
+
};
|
|
22
|
+
const promptOverrides = {
|
|
23
|
+
...(options.systemAppend ? { systemAppend: options.systemAppend } : {}),
|
|
24
|
+
...(options.taskAppend ? { taskAppend: options.taskAppend } : {}),
|
|
25
|
+
...(options.retryAppend ? { retryAppend: options.retryAppend } : {})
|
|
26
|
+
};
|
|
27
|
+
const ctx = await createContext({
|
|
28
|
+
projectPath: options.project,
|
|
29
|
+
coverageTarget: Number(options.target),
|
|
30
|
+
configOverrides: {
|
|
31
|
+
...(include ? { include } : {}),
|
|
32
|
+
...(Object.keys(commandOverrides).length > 0 ? { commandOverrides } : {}),
|
|
33
|
+
...(Object.keys(promptOverrides).length > 0 ? { promptOverrides } : {})
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
await ctx.fileSystem.writeJson(ctx.paths.configPath, ctx.config);
|
|
37
|
+
console.log('config saved');
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { buildIncludeFromScanDir } from '../utils/scan-dir.js';
|
|
3
|
+
import { runWithClaudeCli } from '../../core/tools/run-with-claude-cli.js';
|
|
4
|
+
import { startLifecycle } from '../../utils/process-lifecycle.js';
|
|
5
|
+
export function registerRunCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('run')
|
|
8
|
+
.option('--project <path>', 'project path')
|
|
9
|
+
.option('--target <number>', 'coverage target', '80')
|
|
10
|
+
.option('--scan-dir <dir>', 'scan dir (default: src)')
|
|
11
|
+
.option('--file <path>', 'explicit target source file')
|
|
12
|
+
.option('--topN <number>', 'recommendation pool size', '5')
|
|
13
|
+
.option('--permission-mode <mode>', 'Claude CLI permission mode')
|
|
14
|
+
.option('--allowed-tools <tools>', 'comma-separated Claude CLI allowed tools')
|
|
15
|
+
.option('--timeout <ms>', 'Claude CLI timeout in ms')
|
|
16
|
+
.option('--verify-cmd <cmd>', 'override full verify command')
|
|
17
|
+
.option('--lint-cmd <cmd>', 'override lint command')
|
|
18
|
+
.option('--typecheck-cmd <cmd>', 'override typecheck command')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const include = buildIncludeFromScanDir(options.scanDir ?? 'src');
|
|
21
|
+
const commandOverrides = {
|
|
22
|
+
...(options.verifyCmd ? { verifyFull: options.verifyCmd } : {}),
|
|
23
|
+
...(options.lintCmd ? { lint: options.lintCmd } : {}),
|
|
24
|
+
...(options.typecheckCmd ? { typecheck: options.typecheckCmd } : {})
|
|
25
|
+
};
|
|
26
|
+
const ctx = await createContext({
|
|
27
|
+
projectPath: options.project ?? process.cwd(),
|
|
28
|
+
coverageTarget: Number(options.target),
|
|
29
|
+
configOverrides: {
|
|
30
|
+
...(include ? { include } : {}),
|
|
31
|
+
...(Object.keys(commandOverrides).length > 0 ? { commandOverrides } : {})
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
await ctx.fileSystem.writeJson(ctx.paths.configPath, ctx.config);
|
|
35
|
+
const lifecycle = await startLifecycle(ctx, {
|
|
36
|
+
command: 'run',
|
|
37
|
+
argv: process.argv.slice(2)
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
const result = await runWithClaudeCli(ctx, {
|
|
41
|
+
targetFile: options.file,
|
|
42
|
+
topN: Number(options.topN),
|
|
43
|
+
permissionMode: options.permissionMode,
|
|
44
|
+
allowedTools: parseAllowedTools(options.allowedTools),
|
|
45
|
+
timeoutMs: options.timeout ? Number(options.timeout) : undefined
|
|
46
|
+
});
|
|
47
|
+
console.log(result.summary);
|
|
48
|
+
if (result.artifacts?.finalReportPath) {
|
|
49
|
+
console.log(`report: ${result.artifacts.finalReportPath}`);
|
|
50
|
+
}
|
|
51
|
+
await lifecycle.stop();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
await ctx.lifecycleStore.patch({
|
|
56
|
+
status: 'crashed',
|
|
57
|
+
lastError: message,
|
|
58
|
+
updatedAt: ctx.clock.nowIso()
|
|
59
|
+
});
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function parseAllowedTools(value) {
|
|
65
|
+
if (!value)
|
|
66
|
+
return undefined;
|
|
67
|
+
const tools = value
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((item) => item.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
return tools.length > 0 ? tools : undefined;
|
|
72
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { parseDuration } from '../../utils/duration.js';
|
|
3
|
+
import { runSchedule } from '../../core/tools/guard.js';
|
|
4
|
+
const DEFAULT_INTERVAL_MS = 5 * 60_000;
|
|
5
|
+
export function registerScheduleCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('schedule')
|
|
8
|
+
.option('--project <path>', 'project path')
|
|
9
|
+
.option('--interval <duration>', 'guard interval (default: 5m)')
|
|
10
|
+
.option('--stale-timeout <duration>', 'heartbeat stale timeout (default: 2m)')
|
|
11
|
+
.option('--max-restart <count>', 'max restart attempts (default: 3)')
|
|
12
|
+
.option('--cooldown <duration>', 'restart cooldown (default: 60s)')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
const ctx = await createContext({
|
|
15
|
+
projectPath: options.project ?? process.cwd(),
|
|
16
|
+
coverageTarget: 80
|
|
17
|
+
});
|
|
18
|
+
const intervalMs = options.interval ? parseDuration(options.interval) ?? DEFAULT_INTERVAL_MS : DEFAULT_INTERVAL_MS;
|
|
19
|
+
const staleTimeoutMs = options.staleTimeout ? parseDuration(options.staleTimeout) : undefined;
|
|
20
|
+
const cooldownMs = options.cooldown ? parseDuration(options.cooldown) : undefined;
|
|
21
|
+
const maxRestart = options.maxRestart ? Number(options.maxRestart) : undefined;
|
|
22
|
+
console.log(`[schedule] interval=${intervalMs}ms`);
|
|
23
|
+
await runSchedule(ctx, intervalMs, {
|
|
24
|
+
staleTimeoutMs,
|
|
25
|
+
maxRestart,
|
|
26
|
+
cooldownMs
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { analyzeBaseline } from '../../core/tools/analyze-baseline.js';
|
|
3
|
+
import { runLoop } from '../../core/tools/run-loop.js';
|
|
4
|
+
import { reportStatus } from '../../core/reporter/index.js';
|
|
5
|
+
import { buildIncludeFromScanDir } from '../utils/scan-dir.js';
|
|
6
|
+
import { startLifecycle } from '../../utils/process-lifecycle.js';
|
|
7
|
+
import { runSchedule } from '../../core/tools/guard.js';
|
|
8
|
+
import { parseDuration } from '../../utils/duration.js';
|
|
9
|
+
const DEFAULT_GUARD_INTERVAL_MS = 5 * 60_000;
|
|
10
|
+
export function registerStartCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('start')
|
|
13
|
+
.option('--project <path>', 'project path')
|
|
14
|
+
.option('--target <number>', 'coverage target', '80')
|
|
15
|
+
.option('--topN <number>', 'recommendation pool size', '5')
|
|
16
|
+
.option('--scan-dir <dir>', 'scan dir (default: src)')
|
|
17
|
+
.option('--file <path>', 'explicit target source file')
|
|
18
|
+
.option('--permission-mode <mode>', 'Claude CLI permission mode')
|
|
19
|
+
.option('--allowed-tools <tools>', 'comma-separated Claude CLI allowed tools')
|
|
20
|
+
.option('--timeout <ms>', 'Claude CLI timeout in ms')
|
|
21
|
+
.option('--verify-cmd <cmd>', 'override full verify command')
|
|
22
|
+
.option('--lint-cmd <cmd>', 'override lint command')
|
|
23
|
+
.option('--typecheck-cmd <cmd>', 'override typecheck command')
|
|
24
|
+
.option('--schedule', 'run built-in guard schedule')
|
|
25
|
+
.option('--guard-interval <duration>', 'guard interval (default: 5m)')
|
|
26
|
+
.option('--stale-timeout <duration>', 'heartbeat stale timeout (default: 2m)')
|
|
27
|
+
.option('--max-restart <count>', 'max restart attempts (default: 3)')
|
|
28
|
+
.option('--cooldown <duration>', 'restart cooldown (default: 60s)')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
const include = buildIncludeFromScanDir(options.scanDir ?? 'src');
|
|
31
|
+
const commandOverrides = {
|
|
32
|
+
...(options.verifyCmd ? { verifyFull: options.verifyCmd } : {}),
|
|
33
|
+
...(options.lintCmd ? { lint: options.lintCmd } : {}),
|
|
34
|
+
...(options.typecheckCmd ? { typecheck: options.typecheckCmd } : {})
|
|
35
|
+
};
|
|
36
|
+
console.log('[1/4] 初始化配置...');
|
|
37
|
+
const ctx = await createContext({
|
|
38
|
+
projectPath: options.project ?? process.cwd(),
|
|
39
|
+
coverageTarget: Number(options.target),
|
|
40
|
+
configOverrides: {
|
|
41
|
+
...(include ? { include } : {}),
|
|
42
|
+
...(Object.keys(commandOverrides).length > 0 ? { commandOverrides } : {})
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
await ctx.fileSystem.writeJson(ctx.paths.configPath, ctx.config);
|
|
46
|
+
console.log(`[1/4] 初始化完成:${ctx.paths.configPath}`);
|
|
47
|
+
const lifecycle = await startLifecycle(ctx, {
|
|
48
|
+
command: 'start',
|
|
49
|
+
argv: process.argv.slice(2)
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
if (options.schedule) {
|
|
53
|
+
const intervalMs = options.guardInterval ? parseDuration(options.guardInterval) ?? DEFAULT_GUARD_INTERVAL_MS : DEFAULT_GUARD_INTERVAL_MS;
|
|
54
|
+
const staleTimeoutMs = options.staleTimeout ? parseDuration(options.staleTimeout) : undefined;
|
|
55
|
+
const cooldownMs = options.cooldown ? parseDuration(options.cooldown) : undefined;
|
|
56
|
+
const maxRestart = options.maxRestart ? Number(options.maxRestart) : undefined;
|
|
57
|
+
await runSchedule(ctx, intervalMs, {
|
|
58
|
+
staleTimeoutMs,
|
|
59
|
+
maxRestart,
|
|
60
|
+
cooldownMs
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
console.log('[2/4] 开始基线分析...');
|
|
64
|
+
const baseline = await analyzeBaseline(ctx);
|
|
65
|
+
console.log(`[2/4] ${baseline.summary}`);
|
|
66
|
+
console.log('[3/4] 调用 Claude CLI 补测...');
|
|
67
|
+
const runResult = await runLoop(ctx, {
|
|
68
|
+
targetFile: options.file,
|
|
69
|
+
topN: Number(options.topN),
|
|
70
|
+
permissionMode: options.permissionMode,
|
|
71
|
+
allowedTools: parseAllowedTools(options.allowedTools),
|
|
72
|
+
timeoutMs: options.timeout ? Number(options.timeout) : undefined
|
|
73
|
+
});
|
|
74
|
+
console.log(`[3/4] ${runResult.summary}`);
|
|
75
|
+
console.log('[4/4] 输出状态...');
|
|
76
|
+
const status = await reportStatus(ctx);
|
|
77
|
+
console.log(`[4/4] ${status.summary}`);
|
|
78
|
+
if (runResult.artifacts?.finalReportPath) {
|
|
79
|
+
console.log(`[4/4] report generated: ${runResult.artifacts.finalReportPath}`);
|
|
80
|
+
}
|
|
81
|
+
await lifecycle.stop();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
await ctx.lifecycleStore.patch({
|
|
86
|
+
status: 'crashed',
|
|
87
|
+
lastError: message,
|
|
88
|
+
updatedAt: ctx.clock.nowIso()
|
|
89
|
+
});
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function parseAllowedTools(value) {
|
|
95
|
+
if (!value)
|
|
96
|
+
return undefined;
|
|
97
|
+
const tools = value
|
|
98
|
+
.split(',')
|
|
99
|
+
.map((item) => item.trim())
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
return tools.length > 0 ? tools : undefined;
|
|
102
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { reportStatus, buildFinalReport } from '../../core/reporter/index.js';
|
|
3
|
+
export function registerStatusCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('status')
|
|
6
|
+
.option('--project <path>', 'project path')
|
|
7
|
+
.action(async (options) => {
|
|
8
|
+
const ctx = await createContext({
|
|
9
|
+
projectPath: options.project ?? process.cwd(),
|
|
10
|
+
coverageTarget: 80
|
|
11
|
+
});
|
|
12
|
+
const result = await reportStatus(ctx);
|
|
13
|
+
console.log(result.summary);
|
|
14
|
+
});
|
|
15
|
+
program
|
|
16
|
+
.command('report')
|
|
17
|
+
.option('--project <path>', 'project path')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
const ctx = await createContext({
|
|
20
|
+
projectPath: options.project ?? process.cwd(),
|
|
21
|
+
coverageTarget: 80
|
|
22
|
+
});
|
|
23
|
+
const report = await buildFinalReport(ctx);
|
|
24
|
+
await ctx.reportStore.saveFinalReport(report);
|
|
25
|
+
console.log('report generated');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext } from '../context/create-context.js';
|
|
2
|
+
import { verifyAll } from '../../core/tools/verify-all.js';
|
|
3
|
+
export function registerVerifyCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('verify')
|
|
6
|
+
.option('--project <path>', 'project path')
|
|
7
|
+
.action(async (options) => {
|
|
8
|
+
const ctx = await createContext({
|
|
9
|
+
projectPath: options.project ?? process.cwd(),
|
|
10
|
+
coverageTarget: 80
|
|
11
|
+
});
|
|
12
|
+
const result = await verifyAll(ctx);
|
|
13
|
+
console.log(result.summary);
|
|
14
|
+
});
|
|
15
|
+
}
|