helloloop 0.2.1 → 0.3.1
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +228 -279
- package/hosts/claude/marketplace/plugins/helloloop/.claude-plugin/plugin.json +1 -1
- package/hosts/claude/marketplace/plugins/helloloop/commands/helloloop.md +16 -9
- package/hosts/claude/marketplace/plugins/helloloop/skills/helloloop/SKILL.md +4 -1
- package/hosts/gemini/extension/GEMINI.md +8 -4
- package/hosts/gemini/extension/commands/helloloop.toml +15 -8
- package/hosts/gemini/extension/gemini-extension.json +1 -1
- package/package.json +2 -2
- package/scripts/uninstall-home-plugin.ps1 +25 -0
- package/skills/helloloop/SKILL.md +28 -3
- package/src/analyze_confirmation.mjs +79 -3
- package/src/analyze_prompt.mjs +12 -0
- package/src/analyze_user_input.mjs +303 -0
- package/src/analyzer.mjs +38 -0
- package/src/cli.mjs +211 -25
- package/src/cli_support.mjs +90 -23
- package/src/discovery.mjs +243 -9
- package/src/discovery_inference.mjs +62 -18
- package/src/discovery_paths.mjs +143 -8
- package/src/discovery_prompt.mjs +298 -0
- package/src/install.mjs +153 -0
- package/src/rebuild.mjs +116 -0
- package/templates/analysis-output.schema.json +58 -0
package/src/cli.mjs
CHANGED
|
@@ -4,15 +4,25 @@ import { analyzeExecution } from "./backlog.mjs";
|
|
|
4
4
|
import { renderAnalyzeConfirmation, resolveAutoRunMaxTasks } from "./analyze_confirmation.mjs";
|
|
5
5
|
import {
|
|
6
6
|
confirmAutoExecution,
|
|
7
|
+
confirmRepoConflictResolution,
|
|
7
8
|
renderAnalyzeStopMessage,
|
|
8
9
|
renderAutoRunSummary,
|
|
10
|
+
renderRepoConflictStopMessage,
|
|
9
11
|
runDoctor,
|
|
12
|
+
shouldConfirmRepoRebuild,
|
|
10
13
|
} from "./cli_support.mjs";
|
|
11
14
|
import { createContext } from "./context.mjs";
|
|
12
15
|
import { analyzeWorkspace } from "./analyzer.mjs";
|
|
16
|
+
import {
|
|
17
|
+
hasBlockingInputIssues,
|
|
18
|
+
normalizeAnalyzeOptions,
|
|
19
|
+
renderBlockingInputIssueMessage,
|
|
20
|
+
} from "./analyze_user_input.mjs";
|
|
13
21
|
import { loadBacklog, scaffoldIfMissing } from "./config.mjs";
|
|
14
22
|
import { resolveRepoRoot } from "./discovery.mjs";
|
|
15
|
-
import {
|
|
23
|
+
import { createDiscoveryPromptSession, resolveDiscoveryFailureInteractively } from "./discovery_prompt.mjs";
|
|
24
|
+
import { installPluginBundle, uninstallPluginBundle } from "./install.mjs";
|
|
25
|
+
import { resetRepoForRebuild } from "./rebuild.mjs";
|
|
16
26
|
import { runLoop, runOnce, renderStatusText } from "./runner.mjs";
|
|
17
27
|
|
|
18
28
|
const REPO_ROOT_PLACEHOLDER = "<REPO_ROOT>";
|
|
@@ -20,6 +30,7 @@ const DOCS_PATH_PLACEHOLDER = "<DOCS_PATH>";
|
|
|
20
30
|
const KNOWN_COMMANDS = new Set([
|
|
21
31
|
"analyze",
|
|
22
32
|
"install",
|
|
33
|
+
"uninstall",
|
|
23
34
|
"init",
|
|
24
35
|
"status",
|
|
25
36
|
"next",
|
|
@@ -42,6 +53,7 @@ function parseArgs(argv) {
|
|
|
42
53
|
const options = {
|
|
43
54
|
requiredDocs: [],
|
|
44
55
|
constraints: [],
|
|
56
|
+
positionalArgs: [],
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
for (let index = 0; index < rest.length; index += 1) {
|
|
@@ -49,6 +61,7 @@ function parseArgs(argv) {
|
|
|
49
61
|
if (arg === "--dry-run") options.dryRun = true;
|
|
50
62
|
else if (arg === "--yes" || arg === "-y") options.yes = true;
|
|
51
63
|
else if (arg === "--allow-high-risk") options.allowHighRisk = true;
|
|
64
|
+
else if (arg === "--rebuild-existing") options.rebuildExisting = true;
|
|
52
65
|
else if (arg === "--force") options.force = true;
|
|
53
66
|
else if (arg === "--task-id") { options.taskId = rest[index + 1]; index += 1; }
|
|
54
67
|
else if (arg === "--max-tasks") { options.maxTasks = Number(rest[index + 1]); index += 1; }
|
|
@@ -63,10 +76,7 @@ function parseArgs(argv) {
|
|
|
63
76
|
else if (arg === "--config-dir") { options.configDirName = rest[index + 1]; index += 1; }
|
|
64
77
|
else if (arg === "--required-doc") { options.requiredDocs.push(rest[index + 1]); index += 1; }
|
|
65
78
|
else if (arg === "--constraint") { options.constraints.push(rest[index + 1]); index += 1; }
|
|
66
|
-
else
|
|
67
|
-
else {
|
|
68
|
-
throw new Error(`未知参数:${arg}`);
|
|
69
|
-
}
|
|
79
|
+
else { options.positionalArgs.push(arg); }
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
return { command, options };
|
|
@@ -74,11 +84,12 @@ function parseArgs(argv) {
|
|
|
74
84
|
|
|
75
85
|
function helpText() {
|
|
76
86
|
return [
|
|
77
|
-
"用法:helloloop [command] [path] [options]",
|
|
87
|
+
"用法:helloloop [command] [path|需求说明...] [options]",
|
|
78
88
|
"",
|
|
79
89
|
"命令:",
|
|
80
90
|
" analyze 自动分析并生成执行确认单;确认后继续自动接续开发(默认)",
|
|
81
91
|
" install 安装插件到 Codex Home(适合 npx / npm bin 分发)",
|
|
92
|
+
" uninstall 从所选宿主卸载插件并清理注册信息",
|
|
82
93
|
" init 初始化 .helloloop 配置",
|
|
83
94
|
" status 查看 backlog 与下一任务",
|
|
84
95
|
" next 生成下一任务干跑预览",
|
|
@@ -101,8 +112,13 @@ function helpText() {
|
|
|
101
112
|
" --max-attempts <n> 每种策略内最多重试 n 次",
|
|
102
113
|
" --max-strategies <n> 单任务最多切换 n 种策略继续重试",
|
|
103
114
|
" --allow-high-risk 允许执行 medium/high/critical 风险任务",
|
|
115
|
+
" --rebuild-existing 分析判断当前项目与文档冲突时,自动清理当前项目后按文档重建",
|
|
104
116
|
" --required-doc <p> 增加一个全局必读文档(AGENTS.md 会被自动忽略)",
|
|
105
117
|
" --constraint <text> 增加一个全局实现约束",
|
|
118
|
+
"",
|
|
119
|
+
"补充说明:",
|
|
120
|
+
" analyze 默认支持在命令后混合传入路径和自然语言要求。",
|
|
121
|
+
" 示例:npx helloloop <DOCS_PATH> <PROJECT_ROOT> 先分析偏差,不要执行",
|
|
106
122
|
].join("\n");
|
|
107
123
|
}
|
|
108
124
|
|
|
@@ -117,6 +133,7 @@ function renderFollowupExamples() {
|
|
|
117
133
|
`npx helloloop <PATH>`,
|
|
118
134
|
`npx helloloop --dry-run`,
|
|
119
135
|
`npx helloloop install --host all`,
|
|
136
|
+
`npx helloloop uninstall --host all`,
|
|
120
137
|
`npx helloloop next`,
|
|
121
138
|
`如需显式补充路径:npx helloloop --repo ${REPO_ROOT_PLACEHOLDER} --docs ${DOCS_PATH_PLACEHOLDER}`,
|
|
122
139
|
].join("\n");
|
|
@@ -147,6 +164,29 @@ function renderInstallSummary(result) {
|
|
|
147
164
|
return lines.join("\n");
|
|
148
165
|
}
|
|
149
166
|
|
|
167
|
+
function renderUninstallSummary(result) {
|
|
168
|
+
const lines = [
|
|
169
|
+
"HelloLoop 已从以下宿主卸载:",
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
for (const item of result.uninstalledHosts) {
|
|
173
|
+
lines.push(`- ${item.displayName}:${item.removed ? "已清理" : "未发现现有安装"}`);
|
|
174
|
+
lines.push(` 目标目录:${item.targetRoot}`);
|
|
175
|
+
if (item.marketplaceFile) {
|
|
176
|
+
lines.push(` marketplace:${item.marketplaceFile}`);
|
|
177
|
+
}
|
|
178
|
+
if (item.settingsFile) {
|
|
179
|
+
lines.push(` settings:${item.settingsFile}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push("如需重新安装:");
|
|
185
|
+
lines.push("- `npx helloloop install --host codex`");
|
|
186
|
+
lines.push("- `npx helloloop install --host all`");
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
150
190
|
function resolveContextFromOptions(options) {
|
|
151
191
|
const resolvedRepo = resolveRepoRoot({
|
|
152
192
|
cwd: process.cwd(),
|
|
@@ -164,8 +204,88 @@ function resolveContextFromOptions(options) {
|
|
|
164
204
|
});
|
|
165
205
|
}
|
|
166
206
|
|
|
207
|
+
async function analyzeWithResolvedDiscovery(options) {
|
|
208
|
+
let currentOptions = { ...options };
|
|
209
|
+
let lastResult = null;
|
|
210
|
+
let promptSession = null;
|
|
211
|
+
|
|
212
|
+
function getPromptSession() {
|
|
213
|
+
if (currentOptions.yes) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (!promptSession) {
|
|
217
|
+
promptSession = createDiscoveryPromptSession();
|
|
218
|
+
}
|
|
219
|
+
return promptSession;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
224
|
+
lastResult = await analyzeWorkspace({
|
|
225
|
+
cwd: process.cwd(),
|
|
226
|
+
inputPath: currentOptions.inputPath,
|
|
227
|
+
repoRoot: currentOptions.repoRoot,
|
|
228
|
+
docsPath: currentOptions.docsPath,
|
|
229
|
+
configDirName: currentOptions.configDirName,
|
|
230
|
+
allowNewRepoRoot: currentOptions.allowNewRepoRoot,
|
|
231
|
+
selectionSources: currentOptions.selectionSources,
|
|
232
|
+
userIntent: currentOptions.userIntent,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (lastResult.ok) {
|
|
236
|
+
return {
|
|
237
|
+
options: currentOptions,
|
|
238
|
+
result: lastResult,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const nextOptions = await resolveDiscoveryFailureInteractively(
|
|
243
|
+
lastResult,
|
|
244
|
+
currentOptions,
|
|
245
|
+
process.cwd(),
|
|
246
|
+
!currentOptions.yes,
|
|
247
|
+
getPromptSession(),
|
|
248
|
+
);
|
|
249
|
+
if (!nextOptions) {
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
currentOptions = nextOptions;
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
promptSession?.close();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
options: currentOptions,
|
|
260
|
+
result: lastResult,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renderRebuildSummary(resetSummary) {
|
|
265
|
+
return [
|
|
266
|
+
"已按确认结果清理当前项目,并准备按开发文档重新开始。",
|
|
267
|
+
`- 已清理顶层条目:${resetSummary.removedEntries.length ? resetSummary.removedEntries.join(",") : "无"}`,
|
|
268
|
+
`- 已保留开发文档:${resetSummary.preservedDocs.length ? resetSummary.preservedDocs.join(",") : "无"}`,
|
|
269
|
+
`- 重建记录:${resetSummary.manifestFile.replaceAll("\\", "/")}`,
|
|
270
|
+
].join("\n");
|
|
271
|
+
}
|
|
272
|
+
|
|
167
273
|
export async function runCli(argv) {
|
|
168
|
-
const
|
|
274
|
+
const parsed = parseArgs(argv);
|
|
275
|
+
const command = parsed.command;
|
|
276
|
+
const options = command === "analyze"
|
|
277
|
+
? normalizeAnalyzeOptions(parsed.options, process.cwd())
|
|
278
|
+
: (() => {
|
|
279
|
+
const nextOptions = { ...parsed.options };
|
|
280
|
+
const positionals = Array.isArray(nextOptions.positionalArgs) ? nextOptions.positionalArgs : [];
|
|
281
|
+
if (positionals.length > 1) {
|
|
282
|
+
throw new Error(`未知参数:${positionals.slice(1).join(" ")}`);
|
|
283
|
+
}
|
|
284
|
+
if (positionals.length === 1 && !nextOptions.inputPath) {
|
|
285
|
+
nextOptions.inputPath = positionals[0];
|
|
286
|
+
}
|
|
287
|
+
return nextOptions;
|
|
288
|
+
})();
|
|
169
289
|
|
|
170
290
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
171
291
|
printHelp();
|
|
@@ -189,28 +309,94 @@ export async function runCli(argv) {
|
|
|
189
309
|
return;
|
|
190
310
|
}
|
|
191
311
|
|
|
192
|
-
if (command === "
|
|
193
|
-
const result =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
configDirName: options.configDirName,
|
|
312
|
+
if (command === "uninstall") {
|
|
313
|
+
const result = uninstallPluginBundle({
|
|
314
|
+
host: options.host,
|
|
315
|
+
codexHome: options.codexHome,
|
|
316
|
+
claudeHome: options.claudeHome,
|
|
317
|
+
geminiHome: options.geminiHome,
|
|
199
318
|
});
|
|
319
|
+
console.log(renderUninstallSummary(result));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
200
322
|
|
|
201
|
-
|
|
202
|
-
|
|
323
|
+
if (command === "analyze") {
|
|
324
|
+
if (hasBlockingInputIssues(options.inputIssues)) {
|
|
325
|
+
console.error(renderBlockingInputIssueMessage(options.inputIssues));
|
|
203
326
|
process.exitCode = 1;
|
|
204
327
|
return;
|
|
205
328
|
}
|
|
206
329
|
|
|
207
|
-
|
|
208
|
-
|
|
330
|
+
let analyzed = await analyzeWithResolvedDiscovery(options);
|
|
331
|
+
let result = analyzed.result;
|
|
332
|
+
let activeOptions = analyzed.options;
|
|
209
333
|
|
|
210
|
-
|
|
211
|
-
|
|
334
|
+
while (true) {
|
|
335
|
+
if (!result.ok) {
|
|
336
|
+
console.error(result.summary);
|
|
337
|
+
process.exitCode = 1;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
212
340
|
|
|
213
|
-
|
|
341
|
+
const confirmationText = renderAnalyzeConfirmation(
|
|
342
|
+
result.context,
|
|
343
|
+
result.analysis,
|
|
344
|
+
result.backlog,
|
|
345
|
+
activeOptions,
|
|
346
|
+
result.discovery,
|
|
347
|
+
);
|
|
348
|
+
console.log(confirmationText);
|
|
349
|
+
console.log("");
|
|
350
|
+
|
|
351
|
+
if (!shouldConfirmRepoRebuild(result.analysis, result.discovery)) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (activeOptions.rebuildExisting) {
|
|
356
|
+
const resetSummary = resetRepoForRebuild(result.context, result.discovery);
|
|
357
|
+
console.log(renderRebuildSummary(resetSummary));
|
|
358
|
+
console.log("");
|
|
359
|
+
analyzed = await analyzeWithResolvedDiscovery({
|
|
360
|
+
...activeOptions,
|
|
361
|
+
repoRoot: result.context.repoRoot,
|
|
362
|
+
rebuildExisting: false,
|
|
363
|
+
});
|
|
364
|
+
result = analyzed.result;
|
|
365
|
+
activeOptions = analyzed.options;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (activeOptions.yes) {
|
|
370
|
+
console.log(renderRepoConflictStopMessage(result.analysis));
|
|
371
|
+
process.exitCode = 1;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const repoConflictDecision = await confirmRepoConflictResolution(result.analysis);
|
|
376
|
+
if (repoConflictDecision === "cancel") {
|
|
377
|
+
console.log("已取消自动执行;分析结果与 backlog 已保留在 .helloloop/。");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (repoConflictDecision === "continue") {
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const resetSummary = resetRepoForRebuild(result.context, result.discovery);
|
|
386
|
+
console.log(renderRebuildSummary(resetSummary));
|
|
387
|
+
console.log("");
|
|
388
|
+
analyzed = await analyzeWithResolvedDiscovery({
|
|
389
|
+
...activeOptions,
|
|
390
|
+
repoRoot: result.context.repoRoot,
|
|
391
|
+
rebuildExisting: false,
|
|
392
|
+
});
|
|
393
|
+
result = analyzed.result;
|
|
394
|
+
activeOptions = analyzed.options;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const execution = analyzeExecution(result.backlog, activeOptions);
|
|
398
|
+
|
|
399
|
+
if (activeOptions.dryRun) {
|
|
214
400
|
console.log("已按 --dry-run 跳过自动执行。");
|
|
215
401
|
return;
|
|
216
402
|
}
|
|
@@ -220,7 +406,7 @@ export async function runCli(argv) {
|
|
|
220
406
|
return;
|
|
221
407
|
}
|
|
222
408
|
|
|
223
|
-
const approved =
|
|
409
|
+
const approved = activeOptions.yes ? true : await confirmAutoExecution();
|
|
224
410
|
if (!approved) {
|
|
225
411
|
console.log("已取消自动执行;分析结果与 backlog 已保留在 .helloloop/。");
|
|
226
412
|
return;
|
|
@@ -229,11 +415,11 @@ export async function runCli(argv) {
|
|
|
229
415
|
console.log("");
|
|
230
416
|
console.log("开始自动接续执行...");
|
|
231
417
|
const results = await runLoop(result.context, {
|
|
232
|
-
...
|
|
233
|
-
maxTasks: resolveAutoRunMaxTasks(result.backlog,
|
|
418
|
+
...activeOptions,
|
|
419
|
+
maxTasks: resolveAutoRunMaxTasks(result.backlog, activeOptions),
|
|
234
420
|
});
|
|
235
421
|
const refreshedBacklog = loadBacklog(result.context);
|
|
236
|
-
console.log(renderAutoRunSummary(result.context, refreshedBacklog, results,
|
|
422
|
+
console.log(renderAutoRunSummary(result.context, refreshedBacklog, results, activeOptions));
|
|
237
423
|
if (results.some((item) => !item.ok)) {
|
|
238
424
|
process.exitCode = 1;
|
|
239
425
|
}
|
package/src/cli_support.mjs
CHANGED
|
@@ -28,34 +28,30 @@ function probeCodexVersion() {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
function shouldCheckProjectRuntime(context, options = {}) {
|
|
32
|
+
if (options.repoRoot || options.inputPath) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (context.repoRoot !== context.toolRoot) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
return [
|
|
41
|
+
context.backlogFile,
|
|
42
|
+
context.policyFile,
|
|
43
|
+
context.projectFile,
|
|
44
|
+
].some((filePath) => fileExists(filePath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function collectDoctorChecks(context, options = {}) {
|
|
48
|
+
const codexVersion = probeCodexVersion();
|
|
49
|
+
const checks = [
|
|
34
50
|
{
|
|
35
51
|
name: "codex CLI",
|
|
36
52
|
ok: codexVersion.ok,
|
|
37
53
|
detail: codexVersion.detail,
|
|
38
54
|
},
|
|
39
|
-
{
|
|
40
|
-
name: "backlog.json",
|
|
41
|
-
ok: fileExists(context.backlogFile),
|
|
42
|
-
detail: context.backlogFile,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "policy.json",
|
|
46
|
-
ok: fileExists(context.policyFile),
|
|
47
|
-
detail: context.policyFile,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: "verify.yaml",
|
|
51
|
-
ok: fileExists(context.repoVerifyFile),
|
|
52
|
-
detail: context.repoVerifyFile,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "project.json",
|
|
56
|
-
ok: fileExists(context.projectFile),
|
|
57
|
-
detail: context.projectFile,
|
|
58
|
-
},
|
|
59
55
|
{
|
|
60
56
|
name: "plugin manifest",
|
|
61
57
|
ok: fileExists(context.pluginManifestFile),
|
|
@@ -72,6 +68,33 @@ export function collectDoctorChecks(context) {
|
|
|
72
68
|
detail: context.installScriptFile,
|
|
73
69
|
},
|
|
74
70
|
];
|
|
71
|
+
|
|
72
|
+
if (shouldCheckProjectRuntime(context, options)) {
|
|
73
|
+
checks.splice(1, 0,
|
|
74
|
+
{
|
|
75
|
+
name: "backlog.json",
|
|
76
|
+
ok: fileExists(context.backlogFile),
|
|
77
|
+
detail: context.backlogFile,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "policy.json",
|
|
81
|
+
ok: fileExists(context.policyFile),
|
|
82
|
+
detail: context.policyFile,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "verify.yaml",
|
|
86
|
+
ok: fileExists(context.repoVerifyFile),
|
|
87
|
+
detail: context.repoVerifyFile,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "project.json",
|
|
91
|
+
ok: fileExists(context.projectFile),
|
|
92
|
+
detail: context.projectFile,
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return checks;
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
function probeNamedCliVersion(commandName, toolDisplayName) {
|
|
@@ -108,7 +131,7 @@ function normalizeDoctorHosts(hostOption) {
|
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
function collectCodexDoctorChecks(context, options = {}) {
|
|
111
|
-
const checks = collectDoctorChecks(context);
|
|
134
|
+
const checks = collectDoctorChecks(context, options);
|
|
112
135
|
if (options.codexHome) {
|
|
113
136
|
checks.push({
|
|
114
137
|
name: "codex installed plugin",
|
|
@@ -278,6 +301,50 @@ export async function confirmAutoExecution() {
|
|
|
278
301
|
}
|
|
279
302
|
}
|
|
280
303
|
|
|
304
|
+
export function shouldConfirmRepoRebuild(analysis, discovery) {
|
|
305
|
+
return analysis?.repoDecision?.action === "confirm_rebuild"
|
|
306
|
+
&& discovery?.resolution?.repo?.exists !== false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function confirmRepoConflictResolution(analysis) {
|
|
310
|
+
const decision = analysis?.repoDecision || {};
|
|
311
|
+
const readline = createInterface({
|
|
312
|
+
input: process.stdin,
|
|
313
|
+
output: process.stdout,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const promptText = [
|
|
317
|
+
"检测到当前项目与开发文档目标存在明显冲突:",
|
|
318
|
+
`- ${decision.reason || "分析结果认为当前项目更适合先确认处理方式。"}`,
|
|
319
|
+
"请选择后续动作:",
|
|
320
|
+
"1. 继续在当前项目上尝试接续",
|
|
321
|
+
"2. 清理当前项目内容后按文档目标重新开始(推荐)",
|
|
322
|
+
"3. 取消本次执行",
|
|
323
|
+
"请输入 1 / 2 / 3:",
|
|
324
|
+
].join("\n");
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const answer = String(await readline.question(promptText) || "").trim();
|
|
328
|
+
if (["2", "重建", "rebuild"].includes(answer.toLowerCase ? answer.toLowerCase() : answer)) {
|
|
329
|
+
return "rebuild";
|
|
330
|
+
}
|
|
331
|
+
if (["1", "继续", "continue"].includes(answer.toLowerCase ? answer.toLowerCase() : answer)) {
|
|
332
|
+
return "continue";
|
|
333
|
+
}
|
|
334
|
+
return "cancel";
|
|
335
|
+
} finally {
|
|
336
|
+
readline.close();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function renderRepoConflictStopMessage(analysis) {
|
|
341
|
+
return [
|
|
342
|
+
"当前项目与开发文档目标存在明显冲突,已暂停自动执行。",
|
|
343
|
+
analysis?.repoDecision?.reason ? `原因:${analysis.repoDecision.reason}` : "",
|
|
344
|
+
"请重新运行交互式 `npx helloloop` 进行选择,或显式追加 `--rebuild-existing` 后再执行。",
|
|
345
|
+
].filter(Boolean).join("\n");
|
|
346
|
+
}
|
|
347
|
+
|
|
281
348
|
export function renderAnalyzeStopMessage(reason) {
|
|
282
349
|
return reason || "当前没有可自动执行的任务。";
|
|
283
350
|
}
|