helloloop 0.1.2 → 0.2.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-plugin/plugin.json +14 -0
- package/.codex-plugin/plugin.json +4 -4
- package/README.md +310 -124
- package/hosts/claude/marketplace/.claude-plugin/marketplace.json +14 -0
- package/hosts/claude/marketplace/plugins/helloloop/.claude-plugin/plugin.json +9 -0
- package/hosts/claude/marketplace/plugins/helloloop/commands/helloloop.md +28 -0
- package/hosts/claude/marketplace/plugins/helloloop/skills/helloloop/SKILL.md +38 -0
- package/hosts/gemini/extension/GEMINI.md +29 -0
- package/hosts/gemini/extension/commands/helloloop.toml +24 -0
- package/hosts/gemini/extension/gemini-extension.json +13 -0
- package/package.json +5 -3
- package/skills/helloloop/SKILL.md +34 -10
- package/src/analyze_confirmation.mjs +128 -0
- package/src/analyzer.mjs +2 -2
- package/src/cli.mjs +79 -86
- package/src/cli_support.mjs +307 -0
- package/src/install.mjs +141 -21
- package/src/shell_invocation.mjs +21 -7
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: helloloop
|
|
3
|
+
description: 当用户希望 Claude Code 按开发文档先分析当前进度,再展示确认单,并在确认后持续接续开发时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# HelloLoop for Claude Code
|
|
7
|
+
|
|
8
|
+
这是 `HelloLoop` 的 Claude Code 原生技能入口。
|
|
9
|
+
|
|
10
|
+
## 默认流程
|
|
11
|
+
|
|
12
|
+
1. 自动识别目标仓库与开发文档
|
|
13
|
+
2. 分析当前代码与文档目标之间的真实进度和偏差
|
|
14
|
+
3. 在目标仓库根目录创建或刷新 `.helloloop/`
|
|
15
|
+
4. 输出中文执行确认单
|
|
16
|
+
5. 用户确认后,继续用 Claude Code 原生工具接续开发、测试和验收
|
|
17
|
+
|
|
18
|
+
## 关键规则
|
|
19
|
+
|
|
20
|
+
- 如果缺少目标仓库或开发文档,必须停下来询问用户。
|
|
21
|
+
- 不允许跳过执行确认单直接开始正式开发。
|
|
22
|
+
- 不允许把开发文档整体压成一个大任务;需要输出足够细的 backlog。
|
|
23
|
+
- 代码是事实源,开发文档是目标源。
|
|
24
|
+
- 遇到失败、阻塞、依赖未满足或风险超出自动阈值时,必须明确说明当前状态。
|
|
25
|
+
|
|
26
|
+
## `.helloloop/` 最低要求
|
|
27
|
+
|
|
28
|
+
- `backlog.json`
|
|
29
|
+
- `project.json`
|
|
30
|
+
- `status.json`
|
|
31
|
+
- `STATE.md`
|
|
32
|
+
- `runs/`
|
|
33
|
+
|
|
34
|
+
## 安全要求
|
|
35
|
+
|
|
36
|
+
- Windows 上避免使用危险的嵌套命令、拼接删除命令和 `cmd` 风格破坏性流程。
|
|
37
|
+
- 不允许静默失败、静默回退、吞掉错误。
|
|
38
|
+
- 真正执行前先确认,真正结束前先验证。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# HelloLoop for Gemini CLI
|
|
2
|
+
|
|
3
|
+
你正在使用 `HelloLoop` 的 Gemini CLI 原生扩展。
|
|
4
|
+
|
|
5
|
+
## 工作目标
|
|
6
|
+
|
|
7
|
+
基于开发文档持续推进仓库开发,先分析真实进度,再在确认后自动接续开发、测试和验收。
|
|
8
|
+
|
|
9
|
+
## 默认流程
|
|
10
|
+
|
|
11
|
+
1. 自动识别项目仓库与开发文档
|
|
12
|
+
2. 分析当前代码与开发文档的差距
|
|
13
|
+
3. 在目标仓库根目录创建或刷新 `.helloloop/`
|
|
14
|
+
4. 输出中文执行确认单
|
|
15
|
+
5. 用户确认后,再继续执行后续开发任务
|
|
16
|
+
|
|
17
|
+
## 强制规则
|
|
18
|
+
|
|
19
|
+
- 代码是事实源,开发文档是目标源。
|
|
20
|
+
- 开发前必须先输出执行确认单。
|
|
21
|
+
- 如果无法识别目标仓库或开发文档,必须停下来询问用户。
|
|
22
|
+
- 如果识别到偏差修正任务,优先收口偏差,再推进后续 backlog。
|
|
23
|
+
- 真正执行前确认,真正结束前验证。
|
|
24
|
+
|
|
25
|
+
## Windows 安全
|
|
26
|
+
|
|
27
|
+
- 谨慎使用 shell;优先使用原生文件工具完成读写与目录操作。
|
|
28
|
+
- 禁止危险删除、格式化、强推、硬重置等命令。
|
|
29
|
+
- 不允许通过路径拼接生成破坏性命令。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
description = "根据开发文档分析当前进度、生成确认单,并在确认后继续接续开发"
|
|
2
|
+
prompt = """
|
|
3
|
+
你现在正在执行 HelloLoop 的 Gemini CLI 原生工作流。
|
|
4
|
+
|
|
5
|
+
执行要求:
|
|
6
|
+
|
|
7
|
+
1. 自动识别目标仓库与开发文档;如果用户在命令后提供了参数,只把它解释为一个路径。
|
|
8
|
+
2. 分析当前代码与开发文档之间的真实进度和偏差。
|
|
9
|
+
3. 在目标仓库根目录创建或刷新 `.helloloop/`,至少维护 `backlog.json`、`project.json`、`status.json`、`STATE.md` 与 `runs/`。
|
|
10
|
+
4. 在真正开发前,必须输出中文执行确认单,至少包含:
|
|
11
|
+
- 目标仓库
|
|
12
|
+
- 开发文档
|
|
13
|
+
- 当前进度
|
|
14
|
+
- 已实现事项
|
|
15
|
+
- 待完成事项
|
|
16
|
+
- 任务统计
|
|
17
|
+
- 首个待执行任务
|
|
18
|
+
- 验证命令预览
|
|
19
|
+
- 自动执行停止条件
|
|
20
|
+
5. 用户未确认前,不要开始正式修改代码。
|
|
21
|
+
6. 用户确认后,继续用 Gemini CLI 原生工具推进 backlog,直到完成、遇到硬阻塞,或需要用户补充关键信息。
|
|
22
|
+
7. 如果用户给了开发文档但找不到项目仓库,或给了项目路径但找不到开发文档,必须停下来询问。
|
|
23
|
+
8. Windows 上优先用安全的文件与 shell 操作;禁止危险删除、硬重置、强推和其他破坏性命令。
|
|
24
|
+
"""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "helloloop",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "HelloLoop 的 Gemini CLI 原生扩展,用于按开发文档接续推进项目开发。",
|
|
5
|
+
"contextFileName": "GEMINI.md",
|
|
6
|
+
"excludeTools": [
|
|
7
|
+
"run_shell_command(rm -rf)",
|
|
8
|
+
"run_shell_command(git reset --hard)",
|
|
9
|
+
"run_shell_command(git push --force)",
|
|
10
|
+
"run_shell_command(FLUSHALL)",
|
|
11
|
+
"run_shell_command(FLUSHDB)"
|
|
12
|
+
]
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helloloop",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "面向 Codex
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "面向 Codex CLI、Claude Code、Gemini CLI 的多宿主开发工作流插件",
|
|
5
|
+
"author": "HelloLoop",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"homepage": "https://github.com/hellowind777/helloloop",
|
|
8
8
|
"type": "module",
|
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
"helloloop": "bin/helloloop.js"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
+
".claude-plugin",
|
|
20
21
|
".codex-plugin",
|
|
21
22
|
"LICENSE",
|
|
22
23
|
"README.md",
|
|
23
24
|
"bin",
|
|
25
|
+
"hosts",
|
|
24
26
|
"package.json",
|
|
25
27
|
"scripts",
|
|
26
28
|
"skills",
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: helloloop
|
|
3
|
-
description: 当用户希望 Codex
|
|
3
|
+
description: 当用户希望 Codex 先分析仓库当前进度、生成确认单,再自动按 backlog 持续接续开发时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# HelloLoop
|
|
7
7
|
|
|
8
8
|
当任务目标不是单轮对话里改一点代码,而是要基于开发文档持续推进整个仓库时,使用这个插件。
|
|
9
9
|
|
|
10
|
+
## 强制入口规则
|
|
11
|
+
|
|
12
|
+
- 用户显式调用 `$helloloop` / `helloloop:helloloop` 时,默认必须优先执行 `npx helloloop` 或 `npx helloloop <PATH>`。
|
|
13
|
+
- 不允许在对话里手工模拟 `HelloLoop` 的分析、确认单、backlog 编排和自动续跑流程来代替 CLI。
|
|
14
|
+
- 只有在以下情况,才允许先停下来问用户而不是直接执行 CLI:
|
|
15
|
+
1. 用户既没有给路径,当前目录也无法判断项目仓库或开发文档
|
|
16
|
+
2. 用户给了开发文档,但无法定位目标项目仓库
|
|
17
|
+
3. 用户给了项目路径,但无法找到开发文档
|
|
18
|
+
4. 用户明确要求先只讲解、不执行
|
|
19
|
+
|
|
20
|
+
## `$helloloop` 的默认执行映射
|
|
21
|
+
|
|
22
|
+
- 当前目录已经是目标项目仓库或开发文档目录 → 先执行 `npx helloloop`
|
|
23
|
+
- 用户给了单一路径 → 先执行 `npx helloloop <PATH>`
|
|
24
|
+
- 用户明确只想先看分析和确认单 → 执行 `npx helloloop --dry-run`
|
|
25
|
+
- 用户明确要求跳过确认直接开始 → 执行 `npx helloloop -y`
|
|
26
|
+
|
|
10
27
|
## 插件边界
|
|
11
28
|
|
|
12
29
|
- 当前 bundle 根目录就是 `HelloLoop` 的官方插件目录。
|
|
@@ -17,30 +34,37 @@ description: 当用户希望 Codex 先分析仓库当前进度,再生成 backl
|
|
|
17
34
|
|
|
18
35
|
1. 先通过 `npx helloloop install --codex-home <CODEX_HOME>` 或 `scripts/install-home-plugin.ps1` 安装插件。
|
|
19
36
|
2. 打开目标项目仓库目录,或者打开开发文档所在目录。
|
|
20
|
-
3. 运行 `npx helloloop` 或 `npx helloloop <
|
|
21
|
-
4.
|
|
37
|
+
3. 运行 `npx helloloop` 或 `npx helloloop <PATH>`。
|
|
38
|
+
4. 命中 `$helloloop` 后,优先按上面的默认执行映射直接调用 CLI。
|
|
39
|
+
5. `HelloLoop` 会先自动分析,再输出执行确认单。
|
|
40
|
+
6. 用户确认后,`HelloLoop` 才开始正式自动执行。
|
|
41
|
+
|
|
42
|
+
如果无法自动判断仓库路径或开发文档路径,就停下来提示用户补充;`--repo` 和 `--docs` 只作为显式覆盖选项使用。
|
|
22
43
|
|
|
23
44
|
## 工作模式
|
|
24
45
|
|
|
25
46
|
- 代码是事实源,开发文档是目标源。
|
|
26
47
|
- `HelloLoop` 会先分析当前真实进度,再生成或刷新 `.helloloop/backlog.json`。
|
|
27
|
-
-
|
|
48
|
+
- 分析后会展示执行确认单,明确告知当前进度、待办任务、验证命令和执行边界。
|
|
49
|
+
- 用户确认后,默认会持续执行到 backlog 清空或遇到硬阻塞。
|
|
28
50
|
- 真正的代码分析与实现仍由本机 `codex` CLI 完成。
|
|
51
|
+
- `$helloloop` 的职责是把用户请求路由到主 CLI 流程,而不是在对话里手工复刻一套平行流程。
|
|
29
52
|
|
|
30
53
|
## 核心命令
|
|
31
54
|
|
|
32
55
|
- `npx helloloop`
|
|
33
|
-
- `npx helloloop <
|
|
34
|
-
- `npx helloloop
|
|
35
|
-
- `npx helloloop
|
|
36
|
-
- `npx helloloop run-loop --max-tasks <n>`
|
|
56
|
+
- `npx helloloop <PATH>`
|
|
57
|
+
- `npx helloloop --dry-run`
|
|
58
|
+
- `npx helloloop -y`
|
|
37
59
|
|
|
38
|
-
##
|
|
60
|
+
## 手动控制命令
|
|
39
61
|
|
|
40
62
|
- `npx helloloop status`
|
|
63
|
+
- `npx helloloop next`
|
|
64
|
+
- `npx helloloop run-once`
|
|
65
|
+
- `npx helloloop run-loop --max-tasks <n>`
|
|
41
66
|
- `npx helloloop doctor`
|
|
42
67
|
- `npx helloloop init`
|
|
43
|
-
- `npx helloloop --repo <repo-root> --docs <docs-path>`
|
|
44
68
|
|
|
45
69
|
## 调用方式
|
|
46
70
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { analyzeExecution, summarizeBacklog } from "./backlog.mjs";
|
|
4
|
+
import { loadPolicy, loadVerifyCommands } from "./config.mjs";
|
|
5
|
+
|
|
6
|
+
function toDisplayPath(repoRoot, targetPath) {
|
|
7
|
+
const absolutePath = path.resolve(targetPath);
|
|
8
|
+
const relativePath = path.relative(repoRoot, absolutePath);
|
|
9
|
+
if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
10
|
+
return relativePath.replaceAll("\\", "/");
|
|
11
|
+
}
|
|
12
|
+
return absolutePath.replaceAll("\\", "/");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatList(items, fallback = "无") {
|
|
16
|
+
if (!Array.isArray(items) || !items.length) {
|
|
17
|
+
return [`- ${fallback}`];
|
|
18
|
+
}
|
|
19
|
+
return items.map((item) => `- ${item}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatTaskPreview(tasks) {
|
|
23
|
+
const preview = tasks
|
|
24
|
+
.filter((task) => ["pending", "in_progress"].includes(String(task.status || "pending")))
|
|
25
|
+
.slice(0, 5);
|
|
26
|
+
|
|
27
|
+
if (!preview.length) {
|
|
28
|
+
return ["- 当前没有待执行任务"];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return preview.map((task) => {
|
|
32
|
+
const parts = [
|
|
33
|
+
task.title,
|
|
34
|
+
`#${task.id}`,
|
|
35
|
+
task.priority || "P2",
|
|
36
|
+
`risk:${task.risk || "low"}`,
|
|
37
|
+
];
|
|
38
|
+
return `- ${parts.join(" | ")}`;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatExecutionState(execution) {
|
|
43
|
+
const stateMap = {
|
|
44
|
+
ready: "可自动执行",
|
|
45
|
+
blocked_in_progress: "存在未收束任务",
|
|
46
|
+
blocked_failed: "存在失败或阻塞任务",
|
|
47
|
+
blocked_risk: "风险超过自动阈值",
|
|
48
|
+
blocked_dependencies: "依赖未满足",
|
|
49
|
+
done: "backlog 已完成",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return stateMap[execution.state] || execution.state;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolvePreviewVerifyCommands(context, execution) {
|
|
56
|
+
const taskCommands = Array.isArray(execution.task?.verify) && execution.task.verify.length
|
|
57
|
+
? execution.task.verify
|
|
58
|
+
: [];
|
|
59
|
+
if (taskCommands.length) {
|
|
60
|
+
return taskCommands;
|
|
61
|
+
}
|
|
62
|
+
return loadVerifyCommands(context);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolveAutoRunMaxTasks(backlog, options = {}) {
|
|
66
|
+
const explicitMaxTasks = Number(options.maxTasks);
|
|
67
|
+
if (Number.isFinite(explicitMaxTasks) && explicitMaxTasks > 0) {
|
|
68
|
+
return explicitMaxTasks;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const summary = summarizeBacklog(backlog);
|
|
72
|
+
const pendingTotal = summary.pending + summary.inProgress;
|
|
73
|
+
return Math.max(1, pendingTotal);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function renderAnalyzeConfirmation(context, analysis, backlog, options = {}) {
|
|
77
|
+
const summary = summarizeBacklog(backlog);
|
|
78
|
+
const execution = analyzeExecution(backlog, options);
|
|
79
|
+
const policy = loadPolicy(context);
|
|
80
|
+
const verifyCommands = resolvePreviewVerifyCommands(context, execution);
|
|
81
|
+
const autoRunMaxTasks = resolveAutoRunMaxTasks(backlog, options);
|
|
82
|
+
const docsDisplay = analysis.requiredDocs.map((entry) => (
|
|
83
|
+
toDisplayPath(context.repoRoot, path.resolve(context.repoRoot, entry))
|
|
84
|
+
));
|
|
85
|
+
|
|
86
|
+
return [
|
|
87
|
+
"执行确认单",
|
|
88
|
+
"============",
|
|
89
|
+
`目标仓库:${context.repoRoot.replaceAll("\\", "/")}`,
|
|
90
|
+
`开发文档:${docsDisplay.length ? docsDisplay.join(",") : "未识别"}`,
|
|
91
|
+
"",
|
|
92
|
+
"当前进度:",
|
|
93
|
+
`- ${analysis.summary.currentState}`,
|
|
94
|
+
"",
|
|
95
|
+
"已实现:",
|
|
96
|
+
...formatList(analysis.summary.implemented, "暂无已实现摘要"),
|
|
97
|
+
"",
|
|
98
|
+
"待完成:",
|
|
99
|
+
...formatList(analysis.summary.remaining, "暂无待完成摘要"),
|
|
100
|
+
"",
|
|
101
|
+
"任务统计:",
|
|
102
|
+
`- 总任务:${summary.total}`,
|
|
103
|
+
`- 已完成:${summary.done}`,
|
|
104
|
+
`- 待处理:${summary.pending}`,
|
|
105
|
+
`- 进行中:${summary.inProgress}`,
|
|
106
|
+
`- 阻塞:${summary.blocked}`,
|
|
107
|
+
`- 失败:${summary.failed}`,
|
|
108
|
+
"",
|
|
109
|
+
"执行判断:",
|
|
110
|
+
`- 当前状态:${formatExecutionState(execution)}`,
|
|
111
|
+
`- 优先动作:${analysis.summary.nextAction}`,
|
|
112
|
+
execution.task
|
|
113
|
+
? `- 首个任务:${execution.task.title}`
|
|
114
|
+
: `- 首个任务:${execution.blockedTask?.title || "暂无"}`,
|
|
115
|
+
execution.blockedReason
|
|
116
|
+
? `- 当前阻塞:${execution.blockedReason}`
|
|
117
|
+
: "- 当前阻塞:无",
|
|
118
|
+
"- 偏差修正:按 backlog 优先级执行;如果分析识别出偏差修正任务,会先收口再继续后续开发",
|
|
119
|
+
`- 自动推进:最多 ${autoRunMaxTasks} 个任务,直到 backlog 清空或遇到硬阻塞`,
|
|
120
|
+
`- 单任务重试:每种策略最多 ${policy.maxTaskAttempts} 次,共 ${policy.maxTaskStrategies} 轮策略`,
|
|
121
|
+
"",
|
|
122
|
+
"待执行任务预览:",
|
|
123
|
+
...formatTaskPreview(backlog.tasks),
|
|
124
|
+
"",
|
|
125
|
+
"验证命令预览:",
|
|
126
|
+
...formatList(verifyCommands, "未配置 verify.yaml,执行阶段将仅依赖任务自带验证"),
|
|
127
|
+
].join("\n");
|
|
128
|
+
}
|
package/src/analyzer.mjs
CHANGED
|
@@ -87,8 +87,8 @@ function buildAnalysisSummaryText(context, analysis, backlog) {
|
|
|
87
87
|
nextTask ? `下一任务:${nextTask.title}` : "下一任务:暂无可执行任务",
|
|
88
88
|
"",
|
|
89
89
|
"下一步建议:",
|
|
90
|
-
`- npx helloloop
|
|
91
|
-
`- npx helloloop run
|
|
90
|
+
`- npx helloloop`,
|
|
91
|
+
`- npx helloloop --dry-run`,
|
|
92
92
|
].join("\n");
|
|
93
93
|
}
|
|
94
94
|
|
package/src/cli.mjs
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
2
|
|
|
3
|
+
import { analyzeExecution } from "./backlog.mjs";
|
|
4
|
+
import { renderAnalyzeConfirmation, resolveAutoRunMaxTasks } from "./analyze_confirmation.mjs";
|
|
5
|
+
import {
|
|
6
|
+
confirmAutoExecution,
|
|
7
|
+
renderAnalyzeStopMessage,
|
|
8
|
+
renderAutoRunSummary,
|
|
9
|
+
runDoctor,
|
|
10
|
+
} from "./cli_support.mjs";
|
|
4
11
|
import { createContext } from "./context.mjs";
|
|
5
|
-
import { fileExists } from "./common.mjs";
|
|
6
12
|
import { analyzeWorkspace } from "./analyzer.mjs";
|
|
7
|
-
import { scaffoldIfMissing } from "./config.mjs";
|
|
13
|
+
import { loadBacklog, scaffoldIfMissing } from "./config.mjs";
|
|
8
14
|
import { resolveRepoRoot } from "./discovery.mjs";
|
|
9
15
|
import { installPluginBundle } from "./install.mjs";
|
|
10
16
|
import { runLoop, runOnce, renderStatusText } from "./runner.mjs";
|
|
@@ -41,6 +47,7 @@ function parseArgs(argv) {
|
|
|
41
47
|
for (let index = 0; index < rest.length; index += 1) {
|
|
42
48
|
const arg = rest[index];
|
|
43
49
|
if (arg === "--dry-run") options.dryRun = true;
|
|
50
|
+
else if (arg === "--yes" || arg === "-y") options.yes = true;
|
|
44
51
|
else if (arg === "--allow-high-risk") options.allowHighRisk = true;
|
|
45
52
|
else if (arg === "--force") options.force = true;
|
|
46
53
|
else if (arg === "--task-id") { options.taskId = rest[index + 1]; index += 1; }
|
|
@@ -49,7 +56,10 @@ function parseArgs(argv) {
|
|
|
49
56
|
else if (arg === "--max-strategies") { options.maxStrategies = Number(rest[index + 1]); index += 1; }
|
|
50
57
|
else if (arg === "--repo") { options.repoRoot = rest[index + 1]; index += 1; }
|
|
51
58
|
else if (arg === "--docs") { options.docsPath = rest[index + 1]; index += 1; }
|
|
59
|
+
else if (arg === "--host") { options.host = rest[index + 1]; index += 1; }
|
|
52
60
|
else if (arg === "--codex-home") { options.codexHome = rest[index + 1]; index += 1; }
|
|
61
|
+
else if (arg === "--claude-home") { options.claudeHome = rest[index + 1]; index += 1; }
|
|
62
|
+
else if (arg === "--gemini-home") { options.geminiHome = rest[index + 1]; index += 1; }
|
|
53
63
|
else if (arg === "--config-dir") { options.configDirName = rest[index + 1]; index += 1; }
|
|
54
64
|
else if (arg === "--required-doc") { options.requiredDocs.push(rest[index + 1]); index += 1; }
|
|
55
65
|
else if (arg === "--constraint") { options.constraints.push(rest[index + 1]); index += 1; }
|
|
@@ -67,7 +77,7 @@ function helpText() {
|
|
|
67
77
|
"用法:helloloop [command] [path] [options]",
|
|
68
78
|
"",
|
|
69
79
|
"命令:",
|
|
70
|
-
" analyze
|
|
80
|
+
" analyze 自动分析并生成执行确认单;确认后继续自动接续开发(默认)",
|
|
71
81
|
" install 安装插件到 Codex Home(适合 npx / npm bin 分发)",
|
|
72
82
|
" init 初始化 .helloloop 配置",
|
|
73
83
|
" status 查看 backlog 与下一任务",
|
|
@@ -77,11 +87,15 @@ function helpText() {
|
|
|
77
87
|
" doctor 检查 Codex、当前插件 bundle 与目标仓库 .helloloop 配置是否可用",
|
|
78
88
|
"",
|
|
79
89
|
"选项:",
|
|
90
|
+
" --host <name> 安装宿主:codex | claude | gemini | all(默认 codex)",
|
|
80
91
|
" --codex-home <dir> Codex Home,install 默认使用 ~/.codex",
|
|
92
|
+
" --claude-home <dir> Claude Home,install 默认使用 ~/.claude",
|
|
93
|
+
" --gemini-home <dir> Gemini Home,install 默认使用 ~/.gemini",
|
|
81
94
|
" --repo <dir> 高级选项:显式指定项目仓库根目录",
|
|
82
95
|
" --docs <dir|file> 高级选项:显式指定开发文档目录或文件",
|
|
83
96
|
" --config-dir <dir> 配置目录,默认 .helloloop",
|
|
84
|
-
" --
|
|
97
|
+
" -y, --yes 跳过交互确认,分析后直接开始自动执行",
|
|
98
|
+
" --dry-run 只分析并输出确认单,不真正开始自动执行",
|
|
85
99
|
" --task-id <id> 指定任务 id",
|
|
86
100
|
" --max-tasks <n> run-loop 最多执行 n 个任务",
|
|
87
101
|
" --max-attempts <n> 每种策略内最多重试 n 次",
|
|
@@ -100,90 +114,37 @@ function renderFollowupExamples() {
|
|
|
100
114
|
return [
|
|
101
115
|
"下一步示例:",
|
|
102
116
|
`npx helloloop`,
|
|
117
|
+
`npx helloloop <PATH>`,
|
|
118
|
+
`npx helloloop --dry-run`,
|
|
119
|
+
`npx helloloop install --host all`,
|
|
103
120
|
`npx helloloop next`,
|
|
104
121
|
`如需显式补充路径:npx helloloop --repo ${REPO_ROOT_PLACEHOLDER} --docs ${DOCS_PATH_PLACEHOLDER}`,
|
|
105
122
|
].join("\n");
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
encoding: "utf8",
|
|
112
|
-
shell: true,
|
|
113
|
-
})
|
|
114
|
-
: spawnSync("codex", ["--version"], {
|
|
115
|
-
encoding: "utf8",
|
|
116
|
-
shell: false,
|
|
117
|
-
});
|
|
118
|
-
const ok = codexVersion.status === 0;
|
|
119
|
-
return {
|
|
120
|
-
ok,
|
|
121
|
-
detail: ok
|
|
122
|
-
? String(codexVersion.stdout || "").trim()
|
|
123
|
-
: String(codexVersion.stderr || codexVersion.error || "无法执行 codex --version").trim(),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function collectDoctorChecks(context) {
|
|
128
|
-
const codexVersion = probeCodexVersion();
|
|
129
|
-
return [
|
|
130
|
-
{
|
|
131
|
-
name: "codex CLI",
|
|
132
|
-
ok: codexVersion.ok,
|
|
133
|
-
detail: codexVersion.detail,
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
name: "backlog.json",
|
|
137
|
-
ok: fileExists(context.backlogFile),
|
|
138
|
-
detail: context.backlogFile,
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: "policy.json",
|
|
142
|
-
ok: fileExists(context.policyFile),
|
|
143
|
-
detail: context.policyFile,
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: "verify.yaml",
|
|
147
|
-
ok: fileExists(context.repoVerifyFile),
|
|
148
|
-
detail: context.repoVerifyFile,
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: "project.json",
|
|
152
|
-
ok: fileExists(context.projectFile),
|
|
153
|
-
detail: context.projectFile,
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: "plugin manifest",
|
|
157
|
-
ok: fileExists(context.pluginManifestFile),
|
|
158
|
-
detail: context.pluginManifestFile,
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
name: "plugin skill",
|
|
162
|
-
ok: fileExists(context.skillFile),
|
|
163
|
-
detail: context.skillFile,
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: "install script",
|
|
167
|
-
ok: fileExists(context.installScriptFile),
|
|
168
|
-
detail: context.installScriptFile,
|
|
169
|
-
},
|
|
125
|
+
function renderInstallSummary(result) {
|
|
126
|
+
const lines = [
|
|
127
|
+
"HelloLoop 已安装到以下宿主:",
|
|
170
128
|
];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function runDoctor(context) {
|
|
174
|
-
const checks = collectDoctorChecks(context);
|
|
175
|
-
|
|
176
|
-
for (const item of checks) {
|
|
177
|
-
console.log(`${item.ok ? "OK" : "FAIL"} ${item.name} ${item.detail}`);
|
|
178
|
-
}
|
|
179
129
|
|
|
180
|
-
|
|
181
|
-
|
|
130
|
+
for (const item of result.installedHosts) {
|
|
131
|
+
lines.push(`- ${item.displayName}:${item.targetRoot}`);
|
|
132
|
+
if (item.marketplaceFile) {
|
|
133
|
+
lines.push(` marketplace:${item.marketplaceFile}`);
|
|
134
|
+
}
|
|
135
|
+
if (item.settingsFile) {
|
|
136
|
+
lines.push(` settings:${item.settingsFile}`);
|
|
137
|
+
}
|
|
182
138
|
}
|
|
183
139
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
140
|
+
lines.push("");
|
|
141
|
+
lines.push("使用入口:");
|
|
142
|
+
lines.push("- Codex:`$helloloop` / `npx helloloop`");
|
|
143
|
+
lines.push("- Claude:`/helloloop`");
|
|
144
|
+
lines.push("- Gemini:`/helloloop`");
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push(renderFollowupExamples());
|
|
147
|
+
return lines.join("\n");
|
|
187
148
|
}
|
|
188
149
|
|
|
189
150
|
function resolveContextFromOptions(options) {
|
|
@@ -218,13 +179,13 @@ export async function runCli(argv) {
|
|
|
218
179
|
});
|
|
219
180
|
const result = installPluginBundle({
|
|
220
181
|
bundleRoot: context.bundleRoot,
|
|
182
|
+
host: options.host,
|
|
221
183
|
codexHome: options.codexHome,
|
|
184
|
+
claudeHome: options.claudeHome,
|
|
185
|
+
geminiHome: options.geminiHome,
|
|
222
186
|
force: options.force,
|
|
223
187
|
});
|
|
224
|
-
console.log(
|
|
225
|
-
console.log(`Marketplace 已更新:${result.marketplaceFile}`);
|
|
226
|
-
console.log("");
|
|
227
|
-
console.log(renderFollowupExamples());
|
|
188
|
+
console.log(renderInstallSummary(result));
|
|
228
189
|
return;
|
|
229
190
|
}
|
|
230
191
|
|
|
@@ -243,7 +204,39 @@ export async function runCli(argv) {
|
|
|
243
204
|
return;
|
|
244
205
|
}
|
|
245
206
|
|
|
246
|
-
|
|
207
|
+
const confirmationText = renderAnalyzeConfirmation(result.context, result.analysis, result.backlog, options);
|
|
208
|
+
const execution = analyzeExecution(result.backlog, options);
|
|
209
|
+
|
|
210
|
+
console.log(confirmationText);
|
|
211
|
+
console.log("");
|
|
212
|
+
|
|
213
|
+
if (options.dryRun) {
|
|
214
|
+
console.log("已按 --dry-run 跳过自动执行。");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (execution.state !== "ready") {
|
|
219
|
+
console.log(renderAnalyzeStopMessage(execution.blockedReason || "当前 backlog 已无可自动执行任务。"));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const approved = options.yes ? true : await confirmAutoExecution();
|
|
224
|
+
if (!approved) {
|
|
225
|
+
console.log("已取消自动执行;分析结果与 backlog 已保留在 .helloloop/。");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log("");
|
|
230
|
+
console.log("开始自动接续执行...");
|
|
231
|
+
const results = await runLoop(result.context, {
|
|
232
|
+
...options,
|
|
233
|
+
maxTasks: resolveAutoRunMaxTasks(result.backlog, options),
|
|
234
|
+
});
|
|
235
|
+
const refreshedBacklog = loadBacklog(result.context);
|
|
236
|
+
console.log(renderAutoRunSummary(result.context, refreshedBacklog, results, options));
|
|
237
|
+
if (results.some((item) => !item.ok)) {
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
}
|
|
247
240
|
return;
|
|
248
241
|
}
|
|
249
242
|
|
|
@@ -263,7 +256,7 @@ export async function runCli(argv) {
|
|
|
263
256
|
}
|
|
264
257
|
|
|
265
258
|
if (command === "doctor") {
|
|
266
|
-
await runDoctor(context);
|
|
259
|
+
await runDoctor(context, options);
|
|
267
260
|
return;
|
|
268
261
|
}
|
|
269
262
|
|