helloagents 3.0.33 → 3.0.37
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/marketplace.json +1 -4
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -4
- package/README.md +78 -74
- package/README_CN.md +78 -74
- package/bootstrap-lite.md +9 -11
- package/bootstrap.md +21 -23
- package/gemini-extension.json +1 -1
- package/install.ps1 +27 -4
- package/install.sh +27 -3
- package/package.json +2 -2
- package/scripts/capability-registry.mjs +5 -3
- package/scripts/cli-doctor-codex.mjs +153 -1
- package/scripts/cli-doctor-render.mjs +2 -1
- package/scripts/cli-doctor.mjs +3 -3
- package/scripts/cli-hosts.mjs +1 -1
- package/scripts/cli-lifecycle-hosts.mjs +124 -54
- package/scripts/cli-lifecycle.mjs +50 -15
- package/scripts/cli-messages.mjs +7 -7
- package/scripts/cli-runtime-root.mjs +9 -1
- package/scripts/delivery-gate-messages.mjs +5 -4
- package/scripts/delivery-gate.mjs +11 -22
- package/scripts/guard.mjs +1 -1
- package/scripts/notify-closeout.mjs +61 -22
- package/scripts/notify-context.mjs +5 -5
- package/scripts/notify-route.mjs +1 -1
- package/scripts/notify-sound.mjs +2 -1
- package/scripts/notify.mjs +2 -2
- package/scripts/plan-contract.mjs +10 -14
- package/scripts/project-session-cleanup.mjs +91 -31
- package/scripts/qa-review-state.mjs +313 -0
- package/scripts/ralph-loop.mjs +32 -13
- package/scripts/runtime-artifacts.mjs +2 -2
- package/scripts/runtime-scope.mjs +14 -13
- package/scripts/runtime-ttl.mjs +7 -4
- package/scripts/session-capsule.mjs +75 -13
- package/scripts/session-token.mjs +44 -9
- package/scripts/state-document.mjs +77 -0
- package/scripts/workflow-core.mjs +13 -19
- package/scripts/workflow-plan-files.mjs +1 -1
- package/scripts/workflow-recommendation.mjs +55 -67
- package/scripts/workflow-state.mjs +8 -8
- package/skills/commands/auto/SKILL.md +12 -12
- package/skills/commands/build/SKILL.md +9 -10
- package/skills/commands/commit/SKILL.md +1 -1
- package/skills/commands/help/SKILL.md +11 -13
- package/skills/commands/init/SKILL.md +18 -9
- package/skills/commands/loop/SKILL.md +70 -96
- package/skills/commands/plan/SKILL.md +7 -8
- package/skills/commands/prd/SKILL.md +3 -3
- package/skills/commands/qa/SKILL.md +49 -0
- package/skills/hello-ui/SKILL.md +3 -3
- package/skills/helloagents/SKILL.md +11 -14
- package/skills/qa-review/SKILL.md +92 -0
- package/templates/plans/contract.json +4 -7
- package/templates/plans/plan.md +1 -1
- package/templates/plans/tasks.md +1 -1
- package/templates/verify.yaml +1 -1
- package/scripts/review-state.mjs +0 -193
- package/scripts/verify-state.mjs +0 -175
- package/skills/commands/global/SKILL.md +0 -71
- package/skills/commands/verify/SKILL.md +0 -46
- package/skills/commands/wiki/SKILL.md +0 -57
- package/skills/hello-review/SKILL.md +0 -42
- package/skills/hello-verify/SKILL.md +0 -144
- /package/hooks/{hooks.json → hooks-gemini.json} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { platform } from 'node:os'
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
installClaudeStandby,
|
|
@@ -17,23 +18,50 @@ import { getHostLabel } from './cli-host-detect.mjs'
|
|
|
17
18
|
|
|
18
19
|
const CLAUDE_COMMAND = process.env.HELLOAGENTS_CLAUDE_CMD || 'claude'
|
|
19
20
|
const GEMINI_COMMAND = process.env.HELLOAGENTS_GEMINI_CMD || 'gemini'
|
|
20
|
-
const CLAUDE_MARKETPLACE = 'hellowind777/helloagents'
|
|
21
|
+
const CLAUDE_MARKETPLACE = 'https://github.com/hellowind777/helloagents.git'
|
|
21
22
|
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
function normalizeCommand(command = '') {
|
|
25
|
+
return String(command || '').trim()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function commandCandidates(command = '') {
|
|
29
|
+
const normalized = normalizeCommand(command)
|
|
30
|
+
if (!normalized) return []
|
|
31
|
+
|
|
32
|
+
if (platform() !== 'win32') return [normalized]
|
|
33
|
+
|
|
34
|
+
const candidates = new Set([normalized])
|
|
35
|
+
if (!/\.(cmd|bat|exe|ps1)$/i.test(normalized)) {
|
|
36
|
+
candidates.add(`${normalized}.cmd`)
|
|
37
|
+
candidates.add(`${normalized}.exe`)
|
|
38
|
+
}
|
|
39
|
+
return [...candidates]
|
|
40
|
+
}
|
|
23
41
|
|
|
24
42
|
function runHostCommand(command, args) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
const attempts = commandCandidates(command)
|
|
44
|
+
let lastResult = null
|
|
45
|
+
|
|
46
|
+
for (const candidate of attempts) {
|
|
47
|
+
const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(candidate)
|
|
48
|
+
const result = spawnSync(candidate, args, {
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
errors: 'replace',
|
|
51
|
+
shell: needsShell,
|
|
52
|
+
windowsHide: true,
|
|
53
|
+
})
|
|
54
|
+
lastResult = result
|
|
55
|
+
if (!result.error || !['ENOENT', 'EINVAL'].includes(result.error.code)) {
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const errorMessage = lastResult?.error?.message || ''
|
|
33
61
|
return {
|
|
34
|
-
ok:
|
|
35
|
-
missing:
|
|
36
|
-
output: `${
|
|
62
|
+
ok: lastResult?.status === 0,
|
|
63
|
+
missing: ['ENOENT', 'EINVAL'].includes(lastResult?.error?.code || ''),
|
|
64
|
+
output: `${lastResult?.stdout || ''}${lastResult?.stderr || ''}${errorMessage}`.trim(),
|
|
37
65
|
}
|
|
38
66
|
}
|
|
39
67
|
|
|
@@ -46,6 +74,13 @@ function buildNativeResult(result, successCN, successEN, manualCN, manualEN) {
|
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
|
|
77
|
+
function preserveTrackedModeOnFailure(result = {}, trackedMode = '') {
|
|
78
|
+
if (result.ok === false && trackedMode) {
|
|
79
|
+
return { ...result, trackedModeOnFailure: trackedMode }
|
|
80
|
+
}
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
83
|
+
|
|
49
84
|
function installClaudeGlobalPlugin() {
|
|
50
85
|
const add = runHostCommand(CLAUDE_COMMAND, ['plugin', 'marketplace', 'add', CLAUDE_MARKETPLACE])
|
|
51
86
|
if (!add.ok && add.missing) return { ok: false, output: '未找到 claude 命令' }
|
|
@@ -53,8 +88,8 @@ function installClaudeGlobalPlugin() {
|
|
|
53
88
|
return { ok: install.ok, output: install.output || add.output }
|
|
54
89
|
}
|
|
55
90
|
|
|
56
|
-
function installGeminiGlobalExtension() {
|
|
57
|
-
return runHostCommand(GEMINI_COMMAND, ['extensions', '
|
|
91
|
+
function installGeminiGlobalExtension(runtimeRoot) {
|
|
92
|
+
return runHostCommand(GEMINI_COMMAND, ['extensions', 'link', runtimeRoot])
|
|
58
93
|
}
|
|
59
94
|
|
|
60
95
|
function removeClaudeGlobalPlugin() {
|
|
@@ -70,8 +105,11 @@ function reportHostAction(runtime, action, host, mode, result = {}) {
|
|
|
70
105
|
const isCleanup = action === 'cleanup' || action === 'uninstall'
|
|
71
106
|
if (result.skipped) {
|
|
72
107
|
console.log(runtime.msg(` - ${label} 未检测到,跳过`, ` - ${label} not detected, skipped`))
|
|
73
|
-
} else if (result.ok === false
|
|
74
|
-
console.log(runtime.msg(
|
|
108
|
+
} else if (result.ok === false) {
|
|
109
|
+
console.log(runtime.msg(
|
|
110
|
+
isCleanup ? ` - ${label} 自动清理未完成` : ` - ${label} 自动配置未完成`,
|
|
111
|
+
isCleanup ? ` - ${label} automatic cleanup did not complete` : ` - ${label} automatic setup did not complete`,
|
|
112
|
+
))
|
|
75
113
|
} else if (isCleanup) {
|
|
76
114
|
runtime.ok(runtime.msg(`${label} 已清理(${mode} 模式)`, `${label} cleaned (${mode} mode)`))
|
|
77
115
|
} else if (mode === 'standby') {
|
|
@@ -87,14 +125,46 @@ function reportHostAction(runtime, action, host, mode, result = {}) {
|
|
|
87
125
|
}
|
|
88
126
|
}
|
|
89
127
|
|
|
90
|
-
function
|
|
128
|
+
function prepareClaudeStandby(previousMode) {
|
|
129
|
+
if (previousMode !== 'global') return {}
|
|
130
|
+
return preserveTrackedModeOnFailure(
|
|
131
|
+
buildNativeResult(
|
|
132
|
+
removeClaudeGlobalPlugin(),
|
|
133
|
+
'已自动移除 Claude Code 插件',
|
|
134
|
+
'Claude Code plugin removed automatically',
|
|
135
|
+
'切到 standby 前无法自动移除 Claude Code 插件,请先在 Claude Code 中执行: /plugin remove helloagents',
|
|
136
|
+
'Could not remove the Claude Code plugin before switching to standby. Run inside Claude Code: /plugin remove helloagents',
|
|
137
|
+
),
|
|
138
|
+
'global',
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function prepareGeminiStandby(previousMode) {
|
|
143
|
+
if (previousMode !== 'global') return {}
|
|
144
|
+
return preserveTrackedModeOnFailure(
|
|
145
|
+
buildNativeResult(
|
|
146
|
+
removeGeminiGlobalExtension(),
|
|
147
|
+
'已自动移除 Gemini CLI 扩展',
|
|
148
|
+
'Gemini CLI extension removed automatically',
|
|
149
|
+
'切到 standby 前无法自动移除 Gemini CLI 扩展,请先手动执行: gemini extensions uninstall helloagents',
|
|
150
|
+
'Could not remove the Gemini CLI extension before switching to standby. Run manually: gemini extensions uninstall helloagents',
|
|
151
|
+
),
|
|
152
|
+
'global',
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function installHostStandby(runtime, host, { previousMode = '' } = {}) {
|
|
91
157
|
if (host === 'claude') {
|
|
158
|
+
const cleanupResult = prepareClaudeStandby(previousMode)
|
|
159
|
+
if (cleanupResult.ok === false) return cleanupResult
|
|
92
160
|
installClaudeStandby(runtime.home, runtime.pkgRoot)
|
|
93
|
-
return
|
|
161
|
+
return cleanupResult
|
|
94
162
|
}
|
|
95
163
|
if (host === 'gemini') {
|
|
164
|
+
const cleanupResult = prepareGeminiStandby(previousMode)
|
|
165
|
+
if (cleanupResult.ok === false) return cleanupResult
|
|
96
166
|
installGeminiStandby(runtime.home, runtime.pkgRoot)
|
|
97
|
-
return
|
|
167
|
+
return cleanupResult
|
|
98
168
|
}
|
|
99
169
|
if (!installCodexStandby(runtime.home, runtime.pkgRoot)) return { skipped: true }
|
|
100
170
|
cleanupCodexGlobalResidueForStandby(runtime.home)
|
|
@@ -108,18 +178,18 @@ function installHostGlobal(runtime, host) {
|
|
|
108
178
|
installClaudeGlobalPlugin(),
|
|
109
179
|
'已自动安装 Claude Code 插件;重启 Claude Code 后生效',
|
|
110
180
|
'Claude Code plugin installed automatically; restart Claude Code to apply',
|
|
111
|
-
'Claude Code 插件自动安装失败,请在 Claude Code 中执行: /plugin marketplace add hellowind777/helloagents;/plugin install helloagents@helloagents',
|
|
112
|
-
'Claude Code plugin auto-install failed. Run inside Claude Code: /plugin marketplace add hellowind777/helloagents; /plugin install helloagents@helloagents',
|
|
181
|
+
'Claude Code 插件自动安装失败,请在 Claude Code 中执行: /plugin marketplace add https://github.com/hellowind777/helloagents.git;/plugin install helloagents@helloagents',
|
|
182
|
+
'Claude Code plugin auto-install failed. Run inside Claude Code: /plugin marketplace add https://github.com/hellowind777/helloagents.git; /plugin install helloagents@helloagents',
|
|
113
183
|
)
|
|
114
184
|
}
|
|
115
185
|
if (host === 'gemini') {
|
|
116
186
|
uninstallGeminiStandby(runtime.home)
|
|
117
187
|
return buildNativeResult(
|
|
118
|
-
installGeminiGlobalExtension(),
|
|
188
|
+
installGeminiGlobalExtension(runtime.pkgRoot),
|
|
119
189
|
'已自动安装 Gemini CLI 扩展;重启 Gemini CLI 后生效',
|
|
120
190
|
'Gemini CLI extension installed automatically; restart Gemini CLI to apply',
|
|
121
|
-
|
|
122
|
-
|
|
191
|
+
`Gemini CLI 扩展自动安装失败,请手动执行: gemini extensions link ${runtime.pkgRoot}`,
|
|
192
|
+
`Gemini CLI extension auto-install failed. Run manually: gemini extensions link ${runtime.pkgRoot}`,
|
|
123
193
|
)
|
|
124
194
|
}
|
|
125
195
|
uninstallCodexStandby(runtime.home)
|
|
@@ -137,41 +207,41 @@ function cleanupHostStandby(runtime, host) {
|
|
|
137
207
|
function cleanupHostGlobal(runtime, host) {
|
|
138
208
|
if (host === 'claude') {
|
|
139
209
|
uninstallClaudeStandby(runtime.home)
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
210
|
+
return preserveTrackedModeOnFailure(
|
|
211
|
+
buildNativeResult(
|
|
212
|
+
removeClaudeGlobalPlugin(),
|
|
213
|
+
'已自动移除 Claude Code 插件',
|
|
214
|
+
'Claude Code plugin removed automatically',
|
|
215
|
+
'Claude Code 插件自动移除失败,请手动执行: /plugin remove helloagents',
|
|
216
|
+
'Claude Code plugin auto-remove failed. Run manually: /plugin remove helloagents',
|
|
217
|
+
),
|
|
218
|
+
'global',
|
|
146
219
|
)
|
|
147
220
|
}
|
|
148
221
|
if (host === 'gemini') {
|
|
149
222
|
uninstallGeminiStandby(runtime.home)
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
223
|
+
return preserveTrackedModeOnFailure(
|
|
224
|
+
buildNativeResult(
|
|
225
|
+
removeGeminiGlobalExtension(),
|
|
226
|
+
'已自动移除 Gemini CLI 扩展',
|
|
227
|
+
'Gemini CLI extension removed automatically',
|
|
228
|
+
'Gemini CLI 扩展自动移除失败,请手动执行: gemini extensions uninstall helloagents',
|
|
229
|
+
'Gemini CLI extension auto-remove failed. Run manually: gemini extensions uninstall helloagents',
|
|
230
|
+
),
|
|
231
|
+
'global',
|
|
156
232
|
)
|
|
157
233
|
}
|
|
158
234
|
return { skipped: !uninstallCodexGlobal(runtime.home) }
|
|
159
235
|
}
|
|
160
236
|
|
|
161
|
-
function installStandby(runtime) {
|
|
237
|
+
function installStandby(runtime, previousModes = {}) {
|
|
162
238
|
const results = {}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
if (installGeminiStandby(runtime.home, runtime.pkgRoot)) {
|
|
170
|
-
runtime.ok(runtime.msg('Gemini CLI 已配置(standby 模式)', 'Gemini CLI configured (standby mode)'))
|
|
171
|
-
results.gemini = {}
|
|
172
|
-
} else {
|
|
173
|
-
results.gemini = { skipped: true }
|
|
174
|
-
}
|
|
239
|
+
const claudeResult = installHostStandby(runtime, 'claude', { previousMode: previousModes.claude || '' })
|
|
240
|
+
reportHostAction(runtime, 'install', 'claude', 'standby', claudeResult)
|
|
241
|
+
results.claude = claudeResult.skipped ? { skipped: true } : claudeResult
|
|
242
|
+
const geminiResult = installHostStandby(runtime, 'gemini', { previousMode: previousModes.gemini || '' })
|
|
243
|
+
reportHostAction(runtime, 'install', 'gemini', 'standby', geminiResult)
|
|
244
|
+
results.gemini = geminiResult.skipped ? { skipped: true } : geminiResult
|
|
175
245
|
if (installCodexStandby(runtime.home, runtime.pkgRoot)) {
|
|
176
246
|
cleanupCodexGlobalResidueForStandby(runtime.home)
|
|
177
247
|
runtime.ok(runtime.msg('Codex CLI 已配置(standby 模式)', 'Codex CLI configured (standby mode)'))
|
|
@@ -193,9 +263,9 @@ function installGlobal(runtime) {
|
|
|
193
263
|
return results
|
|
194
264
|
}
|
|
195
265
|
|
|
196
|
-
export function installAllHosts(runtime, mode) {
|
|
266
|
+
export function installAllHosts(runtime, mode, { previousModes = {} } = {}) {
|
|
197
267
|
if (mode === 'global') return installGlobal(runtime)
|
|
198
|
-
return installStandby(runtime)
|
|
268
|
+
return installStandby(runtime, previousModes)
|
|
199
269
|
}
|
|
200
270
|
|
|
201
271
|
export function uninstallAllHosts(runtime) {
|
|
@@ -205,10 +275,10 @@ export function uninstallAllHosts(runtime) {
|
|
|
205
275
|
uninstallCodexGlobal(runtime.home)
|
|
206
276
|
}
|
|
207
277
|
|
|
208
|
-
export function runHostLifecycle(runtime, action, host, mode) {
|
|
278
|
+
export function runHostLifecycle(runtime, action, host, mode, options = {}) {
|
|
209
279
|
const result = (action === 'cleanup' || action === 'uninstall')
|
|
210
280
|
? (mode === 'global' ? cleanupHostGlobal(runtime, host) : cleanupHostStandby(runtime, host))
|
|
211
|
-
: (mode === 'global' ? installHostGlobal(runtime, host) : installHostStandby(runtime, host))
|
|
281
|
+
: (mode === 'global' ? installHostGlobal(runtime, host) : installHostStandby(runtime, host, options))
|
|
212
282
|
|
|
213
283
|
reportHostAction(runtime, action, host, mode, result)
|
|
214
284
|
return result
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getHostLabel as resolveHostLabel,
|
|
8
8
|
normalizeHost as normalizeLifecycleHost,
|
|
9
9
|
} from './cli-host-detect.mjs'
|
|
10
|
-
import { installAllHosts, runHostLifecycle
|
|
10
|
+
import { installAllHosts, runHostLifecycle } from './cli-lifecycle-hosts.mjs'
|
|
11
11
|
import { ensureDir, safeJson, safeWrite } from './cli-utils.mjs'
|
|
12
12
|
|
|
13
13
|
export const HOSTS = ['claude', 'gemini', 'codex']
|
|
@@ -82,6 +82,18 @@ function syncTrackedHostMode(settings, host, result, mode) {
|
|
|
82
82
|
setTrackedHostMode(settings, host, mode)
|
|
83
83
|
return
|
|
84
84
|
}
|
|
85
|
+
if (result?.trackedModeOnFailure) {
|
|
86
|
+
setTrackedHostMode(settings, host, result.trackedModeOnFailure)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
clearTrackedHostMode(settings, host)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function syncCleanupTrackedHostMode(settings, host, result) {
|
|
93
|
+
if (result?.trackedModeOnFailure) {
|
|
94
|
+
setTrackedHostMode(settings, host, result.trackedModeOnFailure)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
85
97
|
clearTrackedHostMode(settings, host)
|
|
86
98
|
}
|
|
87
99
|
|
|
@@ -129,6 +141,14 @@ export function getHostLabel(host) {
|
|
|
129
141
|
return resolveHostLabel(host)
|
|
130
142
|
}
|
|
131
143
|
|
|
144
|
+
function resolvePreviousHostMode(settings, host) {
|
|
145
|
+
return detectHostMode(host) || getTrackedHostMode(settings, host) || ''
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildPreviousHostModes(settings) {
|
|
149
|
+
return Object.fromEntries(HOSTS.map((host) => [host, resolvePreviousHostMode(settings, host)]))
|
|
150
|
+
}
|
|
151
|
+
|
|
132
152
|
function resolveHostMode(host, explicitMode, settings) {
|
|
133
153
|
if (explicitMode) return explicitMode
|
|
134
154
|
return detectHostMode(host)
|
|
@@ -167,6 +187,7 @@ export function switchMode(newMode) {
|
|
|
167
187
|
const config = readSettings(true)
|
|
168
188
|
const oldMode = config.install_mode || DEFAULTS.install_mode
|
|
169
189
|
const isRefresh = oldMode === newMode
|
|
190
|
+
const previousModes = buildPreviousHostModes(config)
|
|
170
191
|
|
|
171
192
|
if (!isRefresh) {
|
|
172
193
|
config.install_mode = newMode
|
|
@@ -175,7 +196,7 @@ export function switchMode(newMode) {
|
|
|
175
196
|
runtime.ok(runtime.msg(`当前已是 ${newMode} 模式,正在刷新安装`, `Already in ${newMode} mode, refreshing installation`))
|
|
176
197
|
}
|
|
177
198
|
|
|
178
|
-
const results = installAllHosts(runtime, newMode)
|
|
199
|
+
const results = installAllHosts(runtime, newMode, { previousModes })
|
|
179
200
|
clearAllTrackedHostModes(config)
|
|
180
201
|
for (const host of HOSTS) {
|
|
181
202
|
syncTrackedHostMode(config, host, results?.[host], newMode)
|
|
@@ -187,13 +208,25 @@ export function switchMode(newMode) {
|
|
|
187
208
|
function runAllHostsLifecycle(action, explicitMode) {
|
|
188
209
|
if (action === 'cleanup' || action === 'uninstall') {
|
|
189
210
|
console.log(`\n HelloAGENTS — ${runtime.msg('正在清理', 'Cleaning up')}\n`)
|
|
190
|
-
|
|
211
|
+
const settings = existsSync(runtime.configFile) ? readSettings() : {}
|
|
212
|
+
const results = {}
|
|
213
|
+
for (const host of HOSTS) {
|
|
214
|
+
const mode = explicitMode || resolveHostMode(host, '', settings)
|
|
215
|
+
results[host] = runHostLifecycle(runtime, action, host, mode, {
|
|
216
|
+
previousMode: resolvePreviousHostMode(settings, host),
|
|
217
|
+
})
|
|
218
|
+
}
|
|
191
219
|
if (existsSync(runtime.configFile)) {
|
|
192
|
-
const
|
|
193
|
-
|
|
220
|
+
for (const host of HOSTS) {
|
|
221
|
+
syncCleanupTrackedHostMode(settings, host, results[host])
|
|
222
|
+
}
|
|
194
223
|
writeSettings(settings)
|
|
195
224
|
}
|
|
196
|
-
|
|
225
|
+
const hasFailures = Object.values(results).some((result) => result?.ok === false)
|
|
226
|
+
runtime.ok(runtime.msg(
|
|
227
|
+
hasFailures ? '部分 CLI 仍需手动清理' : '所有 CLI 配置已清理',
|
|
228
|
+
hasFailures ? 'Some CLI cleanup still needs manual action' : 'All CLI configurations cleaned',
|
|
229
|
+
))
|
|
197
230
|
console.log(runtime.msg(
|
|
198
231
|
' ℹ ~/.helloagents/ 已保留(如需彻底清理请手动删除)\n ℹ 已自动尝试移除 Claude/Gemini 插件或扩展;如宿主命令不可用,请手动执行对应移除命令',
|
|
199
232
|
' ℹ ~/.helloagents/ preserved (delete manually if desired)\n ℹ Claude/Gemini plugin or extension removal was attempted automatically; if host commands are unavailable, remove them manually',
|
|
@@ -203,12 +236,14 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
203
236
|
}
|
|
204
237
|
|
|
205
238
|
const settings = readSettings(true)
|
|
239
|
+
const previousModes = buildPreviousHostModes(settings)
|
|
206
240
|
if (!explicitMode) {
|
|
207
241
|
for (const host of HOSTS) {
|
|
208
242
|
const mode = resolveHostMode(host, '', settings)
|
|
209
|
-
const result = runHostLifecycle(runtime, action, host, mode
|
|
210
|
-
|
|
211
|
-
|
|
243
|
+
const result = runHostLifecycle(runtime, action, host, mode, {
|
|
244
|
+
previousMode: previousModes[host],
|
|
245
|
+
})
|
|
246
|
+
syncTrackedHostMode(settings, host, result, mode)
|
|
212
247
|
}
|
|
213
248
|
writeSettings(settings)
|
|
214
249
|
const modes = Object.values(settings.host_install_modes || {})
|
|
@@ -221,12 +256,10 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
221
256
|
|
|
222
257
|
const mode = resolveInstallMode(explicitMode, settings)
|
|
223
258
|
if (explicitMode) settings.install_mode = explicitMode
|
|
224
|
-
const results = installAllHosts(runtime, mode)
|
|
259
|
+
const results = installAllHosts(runtime, mode, { previousModes })
|
|
225
260
|
settings.host_install_modes = {}
|
|
226
261
|
for (const host of HOSTS) {
|
|
227
|
-
|
|
228
|
-
settings.host_install_modes[host] = mode
|
|
229
|
-
}
|
|
262
|
+
syncTrackedHostMode(settings, host, results?.[host], mode)
|
|
230
263
|
}
|
|
231
264
|
writeSettings(settings)
|
|
232
265
|
runtime.printInstallMsg(mode, action === 'update' ? 'refresh' : 'install')
|
|
@@ -242,11 +275,13 @@ export function runScopedLifecycle(action, rawArgs) {
|
|
|
242
275
|
const shouldEnsure = action === 'install' || action === 'update'
|
|
243
276
|
const settings = readSettings(shouldEnsure)
|
|
244
277
|
const mode = resolveHostMode(host, explicitMode, settings)
|
|
245
|
-
const result = runHostLifecycle(runtime, action, host, mode
|
|
278
|
+
const result = runHostLifecycle(runtime, action, host, mode, {
|
|
279
|
+
previousMode: resolvePreviousHostMode(settings, host),
|
|
280
|
+
})
|
|
246
281
|
|
|
247
282
|
if (action === 'cleanup' || action === 'uninstall') {
|
|
248
283
|
if (existsSync(runtime.configFile)) {
|
|
249
|
-
|
|
284
|
+
syncCleanupTrackedHostMode(settings, host, result)
|
|
250
285
|
writeSettings(settings)
|
|
251
286
|
}
|
|
252
287
|
} else if (!result.skipped) {
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -21,9 +21,9 @@ function codexGlobalStatus({ home, msg }) {
|
|
|
21
21
|
|
|
22
22
|
function pluginCommands() {
|
|
23
23
|
return [
|
|
24
|
-
' Claude Code: /plugin marketplace add hellowind777/helloagents',
|
|
24
|
+
' Claude Code: /plugin marketplace add https://github.com/hellowind777/helloagents.git',
|
|
25
25
|
' /plugin install helloagents@helloagents',
|
|
26
|
-
' Gemini CLI:
|
|
26
|
+
' Gemini CLI: helloagents install gemini --global',
|
|
27
27
|
].join('\n')
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -65,18 +65,18 @@ function renderInstallMessage(context, mode, state) {
|
|
|
65
65
|
|
|
66
66
|
if (install) {
|
|
67
67
|
return msg(
|
|
68
|
-
`\n ✅ HelloAGENTS 已安装(standby 模式)!\n\n Claude Code: 已自动配置(~/.claude/CLAUDE.md + hooks)\n Gemini CLI: 已自动配置(~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n standby 模式下,hello-* 技能不会自动触发。\n 在项目中使用 ~
|
|
69
|
-
`\n ✅ HelloAGENTS installed (standby mode)!\n\n Claude Code: Auto-configured (~/.claude/CLAUDE.md + hooks)\n Gemini CLI: Auto-configured (~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n In standby mode, hello-* skills won't auto-trigger.\n Use ~
|
|
68
|
+
`\n ✅ HelloAGENTS 已安装(standby 模式)!\n\n Claude Code: 已自动配置(~/.claude/CLAUDE.md + hooks)\n Gemini CLI: 已自动配置(~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n standby 模式下,hello-* 技能不会自动触发。\n 在项目中使用 ~init 初始化完整项目工作流;未初始化时也可继续用 ~command 按需调用。\n\n 切换模式:\n helloagents --global 宿主级全局部署(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件)`,
|
|
69
|
+
`\n ✅ HelloAGENTS installed (standby mode)!\n\n Claude Code: Auto-configured (~/.claude/CLAUDE.md + hooks)\n Gemini CLI: Auto-configured (~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n In standby mode, hello-* skills won't auto-trigger.\n Use ~init to initialize the full project workflow; uninitialized repos can still use ~command on demand.\n\n Switch modes:\n helloagents --global Host-wide global deployment (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex)`,
|
|
70
70
|
)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
return msg(
|
|
74
74
|
refresh
|
|
75
75
|
? ` standby 模式已刷新,CLI 注入与链接已同步最新文件。\n ${restartHint(msg)}\n ${removeHint(msg)}`
|
|
76
|
-
: ` 项目可通过 ~
|
|
76
|
+
: ` 项目可通过 ~init 初始化完整工作流;未初始化时仅注入轻量规则。\n ${restartHint(msg)}\n ${removeHint(msg)}`,
|
|
77
77
|
refresh
|
|
78
78
|
? ` Standby mode refreshed; injected files and links were synchronized.\n ${restartHint(msg)}\n ${removeHint(msg)}`
|
|
79
|
-
: ` Projects can use ~
|
|
79
|
+
: ` Projects can use ~init to initialize the full workflow; projects that are not initialized get lite rules only.\n ${restartHint(msg)}\n ${removeHint(msg)}`,
|
|
80
80
|
)
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -90,7 +90,7 @@ HelloAGENTS v${pkgVersion} — The orchestration kernel for AI CLIs
|
|
|
90
90
|
helloagents-js ${msg('(受管宿主配置的跨平台稳定入口)', '(cross-platform stable entrypoint for managed host configs)')}
|
|
91
91
|
|
|
92
92
|
${msg('模式切换', 'Mode switching')}:
|
|
93
|
-
helloagents --global ${msg('
|
|
93
|
+
helloagents --global ${msg('宿主级全局部署(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件)', 'Host-wide global deployment (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex)')}
|
|
94
94
|
helloagents --standby ${msg('标准模式(非插件安装,hello-* 不自动触发,默认)', "Standby mode (non-plugin install, hello-* won't auto-trigger, default)")}
|
|
95
95
|
|
|
96
96
|
${msg('单 CLI 管理', 'Scoped CLI management')}:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtempSync, realpathSync, renameSync } from 'node:fs'
|
|
1
|
+
import { copyFileSync, existsSync, mkdtempSync, realpathSync, renameSync } from 'node:fs'
|
|
2
2
|
import { dirname, join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { copyEntries, ensureDir, removeIfExists } from './cli-utils.mjs'
|
|
@@ -63,6 +63,13 @@ function retryTransientFs(operation) {
|
|
|
63
63
|
throw lastError
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function materializeGeminiHooks(root) {
|
|
67
|
+
const source = join(root, 'hooks', 'hooks-gemini.json')
|
|
68
|
+
const target = join(root, 'hooks', 'hooks.json')
|
|
69
|
+
if (!existsSync(source)) return
|
|
70
|
+
copyFileSync(source, target)
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
/** Sync package runtime files into the stable root without copying repo-only files. */
|
|
67
74
|
export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
68
75
|
const source = resolve(sourceRoot)
|
|
@@ -77,6 +84,7 @@ export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
|
77
84
|
|
|
78
85
|
try {
|
|
79
86
|
copyEntries(source, staging, RUNTIME_ROOT_ENTRIES)
|
|
87
|
+
materializeGeminiHooks(staging)
|
|
80
88
|
retryTransientFs(() => {
|
|
81
89
|
removeIfExists(target)
|
|
82
90
|
renameSync(staging, target)
|
|
@@ -24,10 +24,8 @@ function issueHeading(issue) {
|
|
|
24
24
|
return '任务缺少可交付元数据'
|
|
25
25
|
case 'missing-contract':
|
|
26
26
|
return '方案包缺少可信的结构化契约'
|
|
27
|
-
case 'missing-
|
|
28
|
-
return '
|
|
29
|
-
case 'missing-review-evidence':
|
|
30
|
-
return '当前工作流缺少最新审查证据'
|
|
27
|
+
case 'missing-qa-review-evidence':
|
|
28
|
+
return '当前工作流缺少最新 qa-review 证据'
|
|
31
29
|
case 'missing-advisor-evidence':
|
|
32
30
|
return '当前工作流缺少最新 advisor 证据'
|
|
33
31
|
case 'missing-visual-evidence':
|
|
@@ -62,6 +60,9 @@ export function buildDeliveryBlockReason(issues, recommendation, gateHint) {
|
|
|
62
60
|
if (issues.some((issue) => issue.type === 'missing-visual-evidence')) {
|
|
63
61
|
lines.push('视觉验收动作:先写入当前会话 `artifacts/visual.json`,记录 `tooling`、`screensChecked`、`statesChecked`、`status` 和 `summary`,再报告完成。')
|
|
64
62
|
}
|
|
63
|
+
if (issues.some((issue) => issue.type === 'missing-qa-review-evidence')) {
|
|
64
|
+
lines.push('质量闭环动作:先完成 `~qa` 或写入当前会话 `artifacts/qa-review.json`,记录结论、问题定位、验证命令与最新结果,再报告完成。')
|
|
65
|
+
}
|
|
65
66
|
if (gateHint) {
|
|
66
67
|
lines.push(gateHint)
|
|
67
68
|
}
|
|
@@ -9,10 +9,9 @@ import { fileURLToPath } from 'node:url'
|
|
|
9
9
|
import { getAdvisorEvidenceStatus } from './advisor-state.mjs'
|
|
10
10
|
import { getCloseoutEvidenceStatus } from './closeout-state.mjs'
|
|
11
11
|
import { getAdvisorRequirement, getVisualValidationRequirement } from './plan-contract.mjs'
|
|
12
|
+
import { getQaReviewEvidenceStatus } from './qa-review-state.mjs'
|
|
12
13
|
import { getVisualEvidenceStatus } from './visual-state.mjs'
|
|
13
14
|
import { buildDeliveryGateHint, getDeliveryAction, getWorkflowRecommendation, getWorkflowSnapshot } from './workflow-state.mjs'
|
|
14
|
-
import { getReviewEvidenceStatus } from './review-state.mjs'
|
|
15
|
-
import { getVerifyEvidenceStatus } from './verify-state.mjs'
|
|
16
15
|
import { buildDeliveryBlockReason, buildUnderSpecifiedDetails } from './delivery-gate-messages.mjs'
|
|
17
16
|
|
|
18
17
|
function selectGatePlans(snapshot) {
|
|
@@ -84,20 +83,12 @@ function collectPlanIssues(planEntries) {
|
|
|
84
83
|
return issues
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
function collectEvidenceIssues(issues,
|
|
88
|
-
if (
|
|
86
|
+
function collectEvidenceIssues(issues, qaStatus, advisorStatus, visualStatus, closeoutStatus) {
|
|
87
|
+
if (qaStatus?.required && qaStatus.status !== 'valid') {
|
|
89
88
|
issues.push({
|
|
90
|
-
type: 'missing-
|
|
89
|
+
type: 'missing-qa-review-evidence',
|
|
91
90
|
planName: 'delivery',
|
|
92
|
-
details:
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (reviewStatus?.required && reviewStatus.status !== 'valid') {
|
|
97
|
-
issues.push({
|
|
98
|
-
type: 'missing-review-evidence',
|
|
99
|
-
planName: 'delivery',
|
|
100
|
-
details: reviewStatus.details,
|
|
91
|
+
details: qaStatus.details,
|
|
101
92
|
})
|
|
102
93
|
}
|
|
103
94
|
if (advisorStatus?.required && advisorStatus.status !== 'valid') {
|
|
@@ -124,9 +115,9 @@ function collectEvidenceIssues(issues, verificationStatus, reviewStatus, advisor
|
|
|
124
115
|
}
|
|
125
116
|
}
|
|
126
117
|
|
|
127
|
-
function collectGateIssues(planEntries,
|
|
118
|
+
function collectGateIssues(planEntries, qaStatus, advisorStatus, visualStatus, closeoutStatus) {
|
|
128
119
|
const issues = collectPlanIssues(planEntries)
|
|
129
|
-
collectEvidenceIssues(issues,
|
|
120
|
+
collectEvidenceIssues(issues, qaStatus, advisorStatus, visualStatus, closeoutStatus)
|
|
130
121
|
return issues
|
|
131
122
|
}
|
|
132
123
|
|
|
@@ -143,11 +134,10 @@ export function evaluateDeliveryGate(data = {}) {
|
|
|
143
134
|
const workflowOptions = { payload: data }
|
|
144
135
|
const snapshot = getWorkflowSnapshot(cwd, workflowOptions)
|
|
145
136
|
const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
|
|
146
|
-
const verificationStatus = getVerifyEvidenceStatus(cwd, workflowOptions)
|
|
147
137
|
const deliveryAction = getDeliveryAction(cwd, workflowOptions)
|
|
148
138
|
const gatePlans = selectGatePlans(snapshot)
|
|
149
|
-
const
|
|
150
|
-
required: deliveryAction?.phase === '
|
|
139
|
+
const qaStatus = getQaReviewEvidenceStatus(cwd, {
|
|
140
|
+
required: deliveryAction?.phase === 'qa' || deliveryAction?.phase === 'consolidate',
|
|
151
141
|
...workflowOptions,
|
|
152
142
|
})
|
|
153
143
|
if (gatePlans.length === 0) {
|
|
@@ -169,8 +159,7 @@ export function evaluateDeliveryGate(data = {}) {
|
|
|
169
159
|
})
|
|
170
160
|
const closeoutRequired = (
|
|
171
161
|
gatePlans.every((entry) => entry.missingFiles.length === 0 && entry.templateIssues.length === 0 && entry.taskSummary.total > 0 && entry.taskSummary.open === 0 && entry.taskSummary.underSpecifiedCount === 0)
|
|
172
|
-
&& (!
|
|
173
|
-
&& (!reviewStatus.required || reviewStatus.status === 'valid')
|
|
162
|
+
&& (!qaStatus.required || qaStatus.status === 'valid')
|
|
174
163
|
&& (!advisorStatus.required || advisorStatus.status === 'valid')
|
|
175
164
|
&& (!visualStatus.required || visualStatus.status === 'valid')
|
|
176
165
|
)
|
|
@@ -179,7 +168,7 @@ export function evaluateDeliveryGate(data = {}) {
|
|
|
179
168
|
...workflowOptions,
|
|
180
169
|
})
|
|
181
170
|
|
|
182
|
-
const issues = collectGateIssues(gatePlans,
|
|
171
|
+
const issues = collectGateIssues(gatePlans, qaStatus, advisorStatus, visualStatus, closeoutStatus)
|
|
183
172
|
if (issues.length === 0) {
|
|
184
173
|
return { suppressOutput: true }
|
|
185
174
|
}
|
package/scripts/guard.mjs
CHANGED
|
@@ -76,7 +76,7 @@ function buildHighRiskGate(matches, cwd, payload = {}) {
|
|
|
76
76
|
if (!recommendation) return null
|
|
77
77
|
if (matches.some((match) => match.gate === 'post-verify')) {
|
|
78
78
|
return {
|
|
79
|
-
reason: `[HelloAGENTS Guard] 已阻止 T3 命令:当前工作流尚未进入
|
|
79
|
+
reason: `[HelloAGENTS Guard] 已阻止 T3 命令:当前工作流尚未进入 QA / CONSOLIDATE。\n当前工作流:${recommendation.summary}\n处理路径:${recommendation.nextPath}\n${recommendation.guidance}`,
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
if (matches.some((match) => match.gate === 'plan-first') && recommendation.nextCommand === 'plan') {
|