helloagents 3.0.7 → 3.0.9-beta.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.
Files changed (48) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +6 -6
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +65 -58
  5. package/README_CN.md +60 -53
  6. package/bootstrap-lite.md +46 -28
  7. package/bootstrap.md +51 -34
  8. package/gemini-extension.json +1 -1
  9. package/package.json +12 -2
  10. package/scripts/capability-registry.mjs +9 -9
  11. package/scripts/cli-codex-config.mjs +49 -55
  12. package/scripts/cli-codex.mjs +69 -77
  13. package/scripts/cli-doctor.mjs +26 -18
  14. package/scripts/cli-host-detect.mjs +18 -2
  15. package/scripts/cli-messages.mjs +1 -1
  16. package/scripts/cli-toml.mjs +30 -0
  17. package/scripts/delivery-gate.mjs +5 -4
  18. package/scripts/guard-rules.mjs +26 -1
  19. package/scripts/guard.mjs +43 -14
  20. package/scripts/notify-context.mjs +30 -33
  21. package/scripts/notify-route.mjs +5 -2
  22. package/scripts/notify-source.mjs +3 -60
  23. package/scripts/notify.mjs +43 -11
  24. package/scripts/project-storage.mjs +107 -15
  25. package/scripts/session-token.mjs +73 -0
  26. package/scripts/turn-state.mjs +173 -0
  27. package/scripts/workflow-core.mjs +19 -11
  28. package/scripts/workflow-plan-files.mjs +17 -6
  29. package/scripts/workflow-recommendation.mjs +14 -14
  30. package/scripts/workflow-state.mjs +14 -14
  31. package/skills/_meta/SKILL.md +1 -1
  32. package/skills/commands/auto/SKILL.md +24 -9
  33. package/skills/commands/build/SKILL.md +4 -4
  34. package/skills/commands/clean/SKILL.md +3 -3
  35. package/skills/commands/commit/SKILL.md +1 -1
  36. package/skills/commands/help/SKILL.md +3 -3
  37. package/skills/commands/idea/SKILL.md +2 -2
  38. package/skills/commands/init/SKILL.md +13 -8
  39. package/skills/commands/loop/SKILL.md +4 -4
  40. package/skills/commands/plan/SKILL.md +13 -11
  41. package/skills/commands/prd/SKILL.md +10 -8
  42. package/skills/commands/verify/SKILL.md +5 -5
  43. package/skills/commands/wiki/SKILL.md +9 -11
  44. package/skills/hello-review/SKILL.md +1 -1
  45. package/skills/hello-subagent/SKILL.md +3 -2
  46. package/skills/hello-ui/SKILL.md +13 -13
  47. package/skills/hello-verify/SKILL.md +6 -5
  48. package/skills/helloagents/SKILL.md +17 -12
@@ -7,21 +7,19 @@ import {
7
7
  } from './cli-utils.mjs';
8
8
  import { ensureTimestampedBackup, readCodexBackup, removeCodexBackup } from './cli-codex-backup.mjs';
9
9
  import {
10
- CODEX_DEVELOPER_INSTRUCTIONS,
11
10
  CODEX_MANAGED_TOML_COMMENT,
12
11
  CODEX_PLUGIN_CONFIG_HEADER,
13
- installCodexDeveloperInstructions,
12
+ isManagedCodexHooks,
13
+ installCodexManagedTopLevelConfig,
14
14
  isManagedCodexBackupInstruction,
15
15
  isManagedCodexModelInstruction,
16
16
  isManagedCodexNotify,
17
17
  removeCodexPluginConfig,
18
- uninstallCodexDeveloperInstructions,
18
+ restoreCodexTopLevelConfig,
19
19
  upsertCodexPluginConfig,
20
20
  } from './cli-codex-config.mjs';
21
21
  import {
22
- upsertTopLevelTomlKey,
23
22
  readTopLevelTomlLine,
24
- ensureTopLevelTomlLine,
25
23
  readTomlKeyInSection,
26
24
  removeTomlKeyInSection,
27
25
  ensureTomlKeyInSection,
@@ -31,7 +29,7 @@ import {
31
29
  export const CODEX_MARKETPLACE_NAME = 'local-plugins';
32
30
  export const CODEX_PLUGIN_NAME = 'helloagents';
33
31
  export const CODEX_PLUGIN_KEY = `${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`;
34
- export { CODEX_DEVELOPER_INSTRUCTIONS, CODEX_MANAGED_TOML_COMMENT, CODEX_PLUGIN_CONFIG_HEADER };
32
+ export { CODEX_MANAGED_TOML_COMMENT, CODEX_PLUGIN_CONFIG_HEADER };
35
33
  export const CODEX_RUNTIME_CARRIER = 'AGENTS.md';
36
34
  const CODEX_CONFIG_BASENAME = 'config.toml';
37
35
  export const CODEX_RUNTIME_ENTRIES = [
@@ -108,10 +106,6 @@ function removeCodexMarketplaceEntry(marketplaceFile) {
108
106
  return true;
109
107
  }
110
108
 
111
- function normalizePath(path) {
112
- return path.replace(/\\/g, '/');
113
- }
114
-
115
109
  function buildCodexRuntimeCarrier(bootstrapContent) {
116
110
  const normalized = String(bootstrapContent || '').trim();
117
111
  return normalized ? `${normalized}\n` : '';
@@ -131,11 +125,55 @@ function writeCodexRuntimeCarrier(filePath, bootstrapPath) {
131
125
  return true;
132
126
  }
133
127
 
134
- function stripCodexModelInstructions(toml = '') {
135
- return removeTopLevelTomlLines(
128
+ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } = {}) {
129
+ const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
130
+ let toml = safeRead(configPath) || '';
131
+
132
+ const currentModelInstructions = readTopLevelTomlLine(toml, 'model_instructions_file');
133
+ const currentNotify = readTopLevelTomlLine(toml, 'notify');
134
+ const currentCodexHooks = readTomlKeyInSection(toml, '[features]', 'codex_hooks');
135
+
136
+ const shouldRestoreModelInstructions = isManagedCodexModelInstruction(currentModelInstructions);
137
+ const shouldRestoreNotify = isManagedCodexNotify(currentNotify);
138
+ const shouldRestoreCodexHooks = isManagedCodexHooks(currentCodexHooks);
139
+
140
+ if (removePluginConfig) {
141
+ toml = removeCodexPluginConfig(toml);
142
+ }
143
+ if (shouldRestoreModelInstructions) {
144
+ toml = removeTopLevelTomlLines(toml, (line) =>
145
+ line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)).text;
146
+ }
147
+ if (shouldRestoreNotify) {
148
+ toml = removeTopLevelTomlLines(toml, (line) =>
149
+ line.startsWith('notify =') && isManagedCodexNotify(line)).text;
150
+ }
151
+ if (shouldRestoreCodexHooks) {
152
+ toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
153
+ }
154
+
155
+ const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
156
+ const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
157
+ const backupCodexHooks = readTomlKeyInSection(backupToml, '[features]', 'codex_hooks');
158
+
159
+ toml = restoreCodexTopLevelConfig(toml, {
160
+ modelInstructionsLine: shouldRestoreModelInstructions && !isManagedCodexBackupInstruction(backupModelInstructions)
161
+ ? backupModelInstructions
162
+ : '',
163
+ notifyLine: shouldRestoreNotify && !isManagedCodexNotify(backupNotify)
164
+ ? backupNotify
165
+ : '',
166
+ });
167
+ toml = ensureTomlKeyInSection(
136
168
  toml,
137
- (line) => line.startsWith('model_instructions_file ='),
138
- ).text;
169
+ '[features]',
170
+ 'codex_hooks',
171
+ shouldRestoreCodexHooks && !isManagedCodexHooks(backupCodexHooks)
172
+ ? backupCodexHooks
173
+ : '',
174
+ );
175
+
176
+ return toml;
139
177
  }
140
178
 
141
179
  export function installCodexStandby(home, pkgRoot) {
@@ -150,9 +188,10 @@ export function installCodexStandby(home, pkgRoot) {
150
188
  let toml = safeRead(configPath) || '';
151
189
  ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME);
152
190
 
153
- toml = stripCodexModelInstructions(toml);
154
- toml = upsertTopLevelTomlKey(toml, 'notify', `["node", "${normalizePath(join(pkgRoot, 'scripts', 'notify.mjs'))}", "codex-notify"]`);
155
- toml = installCodexDeveloperInstructions(configPath, toml);
191
+ toml = installCodexManagedTopLevelConfig(toml, {
192
+ modelInstructionsPath: codexAgentsPath,
193
+ notifyScriptPath: join(pkgRoot, 'scripts', 'notify.mjs'),
194
+ });
156
195
  safeWrite(configPath, toml);
157
196
 
158
197
  createLink(pkgRoot, join(codexDir, 'helloagents'));
@@ -167,34 +206,10 @@ export function uninstallCodexStandby(home) {
167
206
  removeMarkedContent(join(codexDir, 'AGENTS.md'));
168
207
 
169
208
  const configPath = join(codexDir, 'config.toml');
170
- const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
171
- let toml = safeRead(configPath) || '';
172
- if (toml.includes('helloagents') || toml.includes('HelloAGENTS')) {
173
- toml = removeTopLevelTomlLines(toml, (line) => {
174
- if (!line) return false;
175
- if (line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)) return true;
176
- if (line.startsWith('notify =') && line.includes('codex-notify')) return true;
177
- return false;
178
- }).text;
179
- toml = uninstallCodexDeveloperInstructions(configPath, toml);
180
- toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
181
- const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
182
- const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
183
- toml = ensureTopLevelTomlLine(
184
- toml,
185
- 'model_instructions_file',
186
- isManagedCodexBackupInstruction(backupModelInstructions) ? '' : backupModelInstructions,
187
- );
188
- toml = ensureTopLevelTomlLine(
189
- toml,
190
- 'notify',
191
- isManagedCodexNotify(backupNotify) ? '' : backupNotify,
192
- );
193
- toml = ensureTomlKeyInSection(toml, '[features]', 'codex_hooks', readTomlKeyInSection(backupToml, '[features]', 'codex_hooks'));
194
- if (toml.trim()) safeWrite(configPath, toml);
195
- else removeIfExists(configPath);
196
- changed = true;
197
- }
209
+ const toml = cleanupCodexManagedConfig(configPath);
210
+ if (toml.trim()) safeWrite(configPath, toml);
211
+ else removeIfExists(configPath);
212
+ changed = true;
198
213
  removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
199
214
  removeIfExists(join(codexDir, 'hooks.json'));
200
215
  removeLink(join(codexDir, 'helloagents'));
@@ -233,6 +248,7 @@ export function installCodexGlobal(home, pkgRoot) {
233
248
 
234
249
  copyEntries(pkgRoot, pluginRoot, CODEX_RUNTIME_ENTRIES);
235
250
  copyEntries(pkgRoot, installedPluginRoot, CODEX_RUNTIME_ENTRIES);
251
+ createLink(pluginRoot, join(codexDir, 'helloagents'));
236
252
  writeCodexRuntimeCarrier(
237
253
  join(pluginRoot, CODEX_RUNTIME_CARRIER),
238
254
  join(pluginRoot, 'bootstrap.md'),
@@ -241,21 +257,19 @@ export function installCodexGlobal(home, pkgRoot) {
241
257
  join(installedPluginRoot, CODEX_RUNTIME_CARRIER),
242
258
  join(installedPluginRoot, 'bootstrap.md'),
243
259
  );
244
- injectCodexRuntimeCarrier(join(codexDir, CODEX_RUNTIME_CARRIER), join(pkgRoot, 'bootstrap.md'));
260
+ const homeCarrierPath = join(codexDir, CODEX_RUNTIME_CARRIER);
261
+ injectCodexRuntimeCarrier(homeCarrierPath, join(pkgRoot, 'bootstrap.md'));
245
262
 
246
263
  ensureDir(join(home, '.agents', 'plugins'));
247
264
  updateCodexMarketplace(marketplaceFile);
248
265
 
249
266
  let toml = safeRead(configPath) || '';
250
267
  ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME);
251
- toml = stripCodexModelInstructions(toml);
252
- toml = upsertTopLevelTomlKey(
253
- toml,
254
- 'notify',
255
- `["node", "${normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))}", "codex-notify"]`,
256
- );
268
+ toml = installCodexManagedTopLevelConfig(toml, {
269
+ modelInstructionsPath: homeCarrierPath,
270
+ notifyScriptPath: join(pluginRoot, 'scripts', 'notify.mjs'),
271
+ });
257
272
  toml = upsertCodexPluginConfig(toml);
258
- toml = installCodexDeveloperInstructions(configPath, toml);
259
273
  safeWrite(configPath, toml);
260
274
 
261
275
  return true;
@@ -273,31 +287,9 @@ export function uninstallCodexGlobal(home) {
273
287
  removeIfExists(pluginCacheRoot);
274
288
  removeCodexMarketplaceEntry(marketplaceFile);
275
289
  removeMarkedContent(join(codexDir, 'AGENTS.md'));
290
+ removeLink(join(codexDir, 'helloagents'));
276
291
 
277
- const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
278
- let toml = safeRead(configPath) || '';
279
- toml = removeCodexPluginConfig(toml);
280
- toml = uninstallCodexDeveloperInstructions(configPath, toml);
281
- toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
282
- toml = removeTopLevelTomlLines(toml, (line) =>
283
- line.startsWith('model_instructions_file =')
284
- && isManagedCodexModelInstruction(line)).text;
285
- toml = removeTopLevelTomlLines(toml, (line) =>
286
- line.startsWith('notify =')
287
- && line.includes('/plugins/helloagents/scripts/notify.mjs')).text;
288
- const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
289
- const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
290
- toml = ensureTopLevelTomlLine(
291
- toml,
292
- 'model_instructions_file',
293
- isManagedCodexBackupInstruction(backupModelInstructions) ? '' : backupModelInstructions,
294
- );
295
- toml = ensureTopLevelTomlLine(
296
- toml,
297
- 'notify',
298
- isManagedCodexNotify(backupNotify) ? '' : backupNotify,
299
- );
300
- toml = ensureTomlKeyInSection(toml, '[features]', 'codex_hooks', readTomlKeyInSection(backupToml, '[features]', 'codex_hooks'));
292
+ const toml = cleanupCodexManagedConfig(configPath, { removePluginConfig: true });
301
293
  if (toml.trim()) safeWrite(configPath, toml);
302
294
  else removeIfExists(configPath);
303
295
  removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
@@ -140,8 +140,8 @@ function inspectClaudeDoctor(settings) {
140
140
  issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
141
141
  }
142
142
  if (detectedMode === 'standby') {
143
- if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 载体缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
144
- if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 载体内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
143
+ if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
144
+ if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
145
145
  if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
146
146
  if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
147
147
  if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
@@ -160,7 +160,7 @@ function inspectClaudeDoctor(settings) {
160
160
  issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
161
161
  }
162
162
  if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
163
- issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应受管痕迹', 'Config says this CLI is installed, but no managed artifacts were detected'))
163
+ issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
164
164
  }
165
165
 
166
166
  const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
@@ -188,8 +188,8 @@ function inspectGeminiDoctor(settings) {
188
188
  issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
189
189
  }
190
190
  if (detectedMode === 'standby') {
191
- if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 载体缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
192
- if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 载体内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
191
+ if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
192
+ if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
193
193
  if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
194
194
  if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
195
195
  if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
@@ -207,7 +207,7 @@ function inspectGeminiDoctor(settings) {
207
207
  issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
208
208
  }
209
209
  if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
210
- issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应受管痕迹', 'Config says this CLI is installed, but no managed artifacts were detected'))
210
+ issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
211
211
  }
212
212
 
213
213
  const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
@@ -215,31 +215,32 @@ function inspectGeminiDoctor(settings) {
215
215
  }
216
216
 
217
217
  function appendCodexStandbyIssues(issues, checks) {
218
- if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 载体缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
219
- if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 载体内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
218
+ if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
219
+ if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
220
220
  if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
221
- if (checks.modelInstructionsFile) issues.push(buildDoctorIssue('standby-model-instructions-shadow', 'standby config 中仍存在 model_instructions_file,可能覆盖 HelloAGENTS 载体', 'Standby config still contains model_instructions_file, which can shadow the HelloAGENTS carrier'))
221
+ if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('standby-model-instructions-missing', 'standby config 缺少受管 model_instructions_file', 'Standby config is missing the managed model_instructions_file'))
222
+ if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue('standby-model-instructions-drift', 'standby model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Standby model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
222
223
  if (!checks.codexNotify) issues.push(buildDoctorIssue('standby-notify-missing', 'standby notify 配置缺失', 'Standby notify configuration is missing'))
223
224
  if (checks.codexNotify && !checks.notifyPathMatch) issues.push(buildDoctorIssue('standby-notify-drift', 'standby notify 路径未指向当前包根目录', 'Standby notify path does not point to the current package root'))
224
- if (!checks.developerInstructions) issues.push(buildDoctorIssue('standby-developer-instructions-missing', 'standby developer_instructions 缺失', 'Standby developer_instructions block is missing'))
225
225
  if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled || checks.globalNotifyPath) {
226
- issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global 插件链路', 'Global plugin artifacts still remain while Codex is in standby mode'))
226
+ issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global 插件文件或配置', 'Global plugin artifacts still remain while Codex is in standby mode'))
227
227
  }
228
228
  }
229
229
 
230
230
  function appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion) {
231
- if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS 载体', 'Global `~/.codex/AGENTS.md` is missing the HelloAGENTS carrier'))
231
+ if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS 规则内容', 'Global `~/.codex/AGENTS.md` is missing the HelloAGENTS carrier'))
232
232
  if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('global-home-carrier-drift', 'global `~/.codex/AGENTS.md` 与当前 bootstrap.md 不一致', 'Global `~/.codex/AGENTS.md` differs from the current bootstrap.md'))
233
+ if (!checks.globalHomeLink) issues.push(buildDoctorIssue('global-read-root-link-missing', 'global `~/.codex/helloagents` 链接缺失或未指向当前插件根目录', 'Global `~/.codex/helloagents` link is missing or does not point to the current plugin root'))
233
234
  if (!checks.pluginRoot) issues.push(buildDoctorIssue('global-plugin-root-missing', 'global 插件根目录缺失', 'Global plugin root is missing'))
234
235
  if (!checks.pluginCache) issues.push(buildDoctorIssue('global-plugin-cache-missing', 'global 插件缓存目录缺失', 'Global plugin cache directory is missing'))
235
236
  if (checks.pluginRoot && !checks.pluginCarrierMatch) issues.push(buildDoctorIssue('global-plugin-carrier-drift', 'global 插件根目录中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin AGENTS.md differs from the current bootstrap.md'))
236
237
  if (checks.pluginCache && !checks.pluginCacheCarrierMatch) issues.push(buildDoctorIssue('global-plugin-cache-carrier-drift', 'global 插件缓存中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin cache AGENTS.md differs from the current bootstrap.md'))
237
238
  if (!checks.marketplaceEntry) issues.push(buildDoctorIssue('global-marketplace-missing', 'global marketplace 条目缺失', 'Global marketplace entry is missing'))
238
239
  if (!checks.pluginEnabled) issues.push(buildDoctorIssue('global-plugin-disabled', 'global config 中缺少插件启用段', 'Global plugin enablement block is missing from config'))
239
- if (checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-shadow', 'global config 中仍存在 model_instructions_file,可能覆盖 HelloAGENTS 载体', 'Global config still contains model_instructions_file, which can shadow the HelloAGENTS carrier'))
240
+ if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-missing', 'global config 缺少受管 model_instructions_file', 'Global config is missing the managed model_instructions_file'))
241
+ if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue('global-model-instructions-drift', 'global model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Global model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
240
242
  if (!checks.globalNotifyPath) issues.push(buildDoctorIssue('global-notify-missing', 'global notify 路径缺失', 'Global notify path is missing'))
241
243
  if (checks.globalNotifyPath && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue('global-notify-drift', 'global notify 路径未指向当前插件根目录', 'Global notify path does not point to the current plugin root'))
242
- if (!checks.developerInstructions) issues.push(buildDoctorIssue('global-developer-instructions-missing', 'global developer_instructions 缺失', 'Global developer_instructions block is missing'))
243
244
  if (pluginVersion && !checks.pluginVersionMatch) issues.push(buildDoctorIssue('global-plugin-version-drift', 'global 插件根目录版本与当前包版本不一致', 'Global plugin root version does not match the current package version'))
244
245
  if (cacheVersion && !checks.pluginCacheVersionMatch) issues.push(buildDoctorIssue('global-plugin-cache-version-drift', 'global 插件缓存版本与当前包版本不一致', 'Global plugin cache version does not match the current package version'))
245
246
  if (checks.homeLink) {
@@ -258,19 +259,26 @@ function inspectCodexDoctor(settings) {
258
259
  const marketplace = safeJson(join(runtime.home, '.agents', 'plugins', 'marketplace.json')) || {}
259
260
  const pluginVersion = safeJson(join(pluginRoot, 'package.json'))?.version || ''
260
261
  const cacheVersion = safeJson(join(pluginCacheRoot, 'package.json'))?.version || ''
262
+ const homeLinkTarget = safeRealTarget(join(codexDir, 'helloagents'))
263
+ const pkgRootTarget = safeRealTarget(runtime.pkgRoot) || normalizePath(runtime.pkgRoot)
264
+ const pluginRootTarget = safeRealTarget(pluginRoot) || normalizePath(pluginRoot)
261
265
  const standbyNotifyPath = normalizePath(join(runtime.pkgRoot, 'scripts', 'notify.mjs'))
262
266
  const globalNotifyPath = normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))
267
+ const managedHomeCarrierPath = normalizePath(join(codexDir, 'AGENTS.md'))
268
+ const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
263
269
  const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
264
270
  ? 'bootstrap.md'
265
271
  : 'bootstrap-lite.md'
266
272
  const checks = {
267
273
  carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
268
274
  carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md')) === readBootstrapContent(expectedHomeCarrier),
269
- homeLink: safeRealTarget(join(codexDir, 'helloagents')) === runtime.pkgRoot,
270
- modelInstructionsFile: !!readTopLevelTomlLine(codexConfig, 'model_instructions_file'),
275
+ homeLink: homeLinkTarget === pkgRootTarget,
276
+ globalHomeLink: homeLinkTarget === pluginRootTarget,
277
+ modelInstructionsFile: !!modelInstructionsLine,
278
+ modelInstructionsPathMatch: !!modelInstructionsLine
279
+ && normalizePath(modelInstructionsLine).includes(`"${managedHomeCarrierPath}"`),
271
280
  codexNotify: codexConfig.includes('codex-notify'),
272
281
  notifyPathMatch: codexConfig.includes(standbyNotifyPath),
273
- developerInstructions: codexConfig.includes('HelloAGENTS'),
274
282
  pluginRoot: existsSync(pluginRoot),
275
283
  pluginCache: existsSync(pluginCacheRoot),
276
284
  pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readBootstrapContent('bootstrap.md'),
@@ -298,7 +306,7 @@ function inspectCodexDoctor(settings) {
298
306
  issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
299
307
  }
300
308
  if (trackedMode !== 'none' && detectedMode === 'none') {
301
- issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应受管痕迹', 'Config says this CLI is installed, but no managed artifacts were detected'))
309
+ issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
302
310
  }
303
311
  if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') {
304
312
  notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
@@ -1,4 +1,4 @@
1
- import { existsSync } from 'node:fs'
1
+ import { existsSync, realpathSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
 
4
4
  import {
@@ -27,6 +27,18 @@ function hasHelloagentsSettings(filePath) {
27
27
  return JSON.stringify(safeJson(filePath) || {}).includes('helloagents')
28
28
  }
29
29
 
30
+ function normalizePath(value = '') {
31
+ return String(value || '').replace(/\\/g, '/').toLowerCase()
32
+ }
33
+
34
+ function safeRealTarget(linkPath) {
35
+ try {
36
+ return normalizePath(realpathSync(linkPath))
37
+ } catch {
38
+ return ''
39
+ }
40
+ }
41
+
30
42
  function detectClaudeMode(home) {
31
43
  const claudeDir = join(home, '.claude')
32
44
  if (
@@ -53,19 +65,23 @@ function detectGeminiMode(home) {
53
65
 
54
66
  function detectCodexMode(home) {
55
67
  const codexDir = join(home, '.codex')
68
+ const codexHomeLink = join(codexDir, 'helloagents')
56
69
  const codexConfig = safeRead(join(codexDir, 'config.toml')) || ''
57
70
  const marketplace = safeRead(join(home, '.agents', 'plugins', 'marketplace.json')) || ''
71
+ const globalPluginRoot = normalizePath(join(home, 'plugins', CODEX_PLUGIN_NAME))
72
+ const codexHomeLinkTarget = safeRealTarget(codexHomeLink)
58
73
  if (
59
74
  existsSync(join(home, 'plugins', CODEX_PLUGIN_NAME))
60
75
  || existsSync(join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME))
61
76
  || marketplace.includes(`"name": "${CODEX_PLUGIN_NAME}"`)
62
77
  || codexConfig.includes(CODEX_PLUGIN_KEY)
63
78
  || codexConfig.includes(`/plugins/${CODEX_PLUGIN_NAME}/scripts/notify.mjs`)
79
+ || codexHomeLinkTarget === globalPluginRoot
64
80
  ) {
65
81
  return 'global'
66
82
  }
67
83
  if (
68
- existsSync(join(codexDir, 'helloagents'))
84
+ (existsSync(codexHomeLink) && codexHomeLinkTarget !== globalPluginRoot)
69
85
  || hasHelloagentsMarker(join(codexDir, 'AGENTS.md'))
70
86
  || codexConfig.includes('codex-notify')
71
87
  || codexConfig.includes('HelloAGENTS')
@@ -92,7 +92,7 @@ ${msg('单 CLI 管理', 'Scoped CLI management')}:
92
92
  ${msg('诊断', 'Diagnostics')}:
93
93
  helloagents doctor
94
94
  helloagents doctor codex --json
95
- ${msg('检查 carrier、链接、hooks、配置注入、Codex 插件链路、model_instructions_file 遮蔽风险与版本漂移', 'Checks carriers, links, hooks, config injections, the Codex plugin chain, model_instructions_file shadowing risks, and version drift')}
95
+ ${msg('检查 carrier、链接、hooks、配置注入、Codex 插件链路、受管 model_instructions_file 指向与版本漂移', 'Checks carriers, links, hooks, config injections, the Codex plugin chain, managed model_instructions_file targeting, and version drift')}
96
96
 
97
97
  ${msg('卸载', 'Uninstall')}:
98
98
  helloagents cleanup ${msg('(推荐先执行,显式清理所有 CLI 注入/链接)', '(recommended first, explicitly cleans CLI injections/links)')}
@@ -33,6 +33,15 @@ function findFirstTomlSectionIndex(text) {
33
33
  return normalized.length;
34
34
  }
35
35
 
36
+ function splitTopLevelToml(text) {
37
+ const normalized = String(text || '').replace(/\r\n/g, '\n');
38
+ const topLevelEnd = findFirstTomlSectionIndex(normalized);
39
+ return {
40
+ topLevel: normalized.slice(0, topLevelEnd),
41
+ sections: normalized.slice(topLevelEnd),
42
+ };
43
+ }
44
+
36
45
  function findTopLevelTomlBlock(text, key) {
37
46
  const normalized = String(text || '').replace(/\r\n/g, '\n');
38
47
  const topLevelEnd = findFirstTomlSectionIndex(normalized);
@@ -93,6 +102,27 @@ export function removeTopLevelTomlBlock(text, key) {
93
102
  return normalizeToml(`${normalized.slice(0, existing.start)}${normalized.slice(existing.end)}`);
94
103
  }
95
104
 
105
+ export function prependTopLevelTomlBlocks(text, blocks) {
106
+ const normalizedBlocks = blocks
107
+ .map((block) => String(block || '').trim())
108
+ .filter(Boolean);
109
+
110
+ const { topLevel, sections } = splitTopLevelToml(text);
111
+ const normalizedTopLevel = topLevel.replace(/^\n+/, '').trimEnd();
112
+ const normalizedSections = sections.replace(/^\n+/, '').trimEnd();
113
+ const remainder = normalizedTopLevel && normalizedSections
114
+ ? `${normalizedTopLevel}\n\n${normalizedSections}`
115
+ : normalizedTopLevel || normalizedSections;
116
+ if (!normalizedBlocks.length) return normalizeToml(remainder);
117
+ const managedPrelude = normalizedBlocks.join('\n');
118
+
119
+ return normalizeToml(
120
+ remainder
121
+ ? `${managedPrelude}\n\n${remainder}`
122
+ : managedPrelude,
123
+ );
124
+ }
125
+
96
126
  export function upsertTopLevelTomlKey(text, key, value) {
97
127
  const re = new RegExp(`^${key}\\s*=.*$`, 'm');
98
128
  const next = re.test(text)
@@ -205,10 +205,11 @@ function main() {
205
205
  data = JSON.parse(readFileSync(0, 'utf-8'))
206
206
  } catch {}
207
207
  const cwd = data.cwd || process.cwd()
208
- const snapshot = getWorkflowSnapshot(cwd)
209
- const recommendation = getWorkflowRecommendation(cwd)
208
+ const workflowOptions = { payload: data }
209
+ const snapshot = getWorkflowSnapshot(cwd, workflowOptions)
210
+ const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
210
211
  const verificationStatus = getVerifyEvidenceStatus(cwd)
211
- const deliveryAction = getDeliveryAction(cwd)
212
+ const deliveryAction = getDeliveryAction(cwd, workflowOptions)
212
213
  const gatePlans = selectGatePlans(snapshot)
213
214
  const reviewStatus = getReviewEvidenceStatus(cwd, {
214
215
  required: deliveryAction?.phase === 'verify' && deliveryAction?.mode === 'review-first',
@@ -248,7 +249,7 @@ function main() {
248
249
 
249
250
  process.stdout.write(JSON.stringify({
250
251
  decision: 'block',
251
- reason: buildBlockReason(issues, recommendation, buildDeliveryGateHint(cwd)),
252
+ reason: buildBlockReason(issues, recommendation, buildDeliveryGateHint(cwd, workflowOptions)),
252
253
  suppressOutput: true,
253
254
  }))
254
255
  }
@@ -6,10 +6,12 @@ export const DANGEROUS_PATTERNS = [
6
6
  { pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?(-[a-zA-Z]*f[a-zA-Z]*\s+)?(\/|~|\*)/, reason: 'Recursive delete of critical path' },
7
7
  { pattern: /(sudo\s+)?rm\s+--recursive/, reason: 'Recursive delete (long option)' },
8
8
  { pattern: /(sudo\s+)?rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\.\.?(\s|$)/, reason: 'Recursive delete of current/parent directory' },
9
+ { pattern: /\bcmd(?:\.exe)?\s*\/c\b/i, reason: 'Nested cmd invocation bypasses PowerShell safety rules' },
10
+ { pattern: /\bStart-Process\s+cmd(?:\.exe)?\b/i, reason: 'Nested cmd invocation bypasses PowerShell safety rules' },
9
11
  { pattern: /git\s+push\s+(-f|--force)/, reason: 'Force push (specify branch explicitly)' },
10
12
  { pattern: /git\s+reset\s+--hard/, reason: 'Hard reset (destructive operation)' },
11
13
  { pattern: /DROP\s+(DATABASE|TABLE|SCHEMA)/i, reason: 'Database destruction command' },
12
- { pattern: /TRUNCATE\s+TABLE/i, reason: 'Table truncation' },
14
+ { pattern: /\bTRUNCATE(?:\s+TABLE)?\b/i, reason: 'Table truncation' },
13
15
  { pattern: /chmod\s+777/, reason: 'World-writable permissions' },
14
16
  { pattern: /mkfs\b/, reason: 'Filesystem format command' },
15
17
  { pattern: /dd\s+.*of=\/dev\//, reason: 'Direct device write' },
@@ -68,6 +70,29 @@ export function scanHighRiskCommands(command) {
68
70
  return warnings
69
71
  }
70
72
 
73
+ export function scanShellSafetyWarnings(command = '') {
74
+ const warnings = []
75
+ const normalized = String(command || '')
76
+
77
+ if (/\bpowershell(?:\.exe)?\b/i.test(normalized) && /\s-Command\b/i.test(normalized)) {
78
+ const inlineScript = normalized.split(/\s-Command\b/i).slice(1).join(' ').trim()
79
+ const logicalLines = inlineScript
80
+ .split(/[;\r\n]+/)
81
+ .map((entry) => entry.trim())
82
+ .filter(Boolean)
83
+ if (logicalLines.length > 3) {
84
+ warnings.push('PowerShell inline script exceeds 3 logical lines; prefer a temporary .ps1 file')
85
+ }
86
+ }
87
+
88
+ const fileOps = normalized.match(/\b(remove-item|move-item|copy-item|new-item|set-content|add-content|out-file|mkdir|md|touch|cp|copy|mv|move|ren|rename|del|erase|rm|rmdir)\b/ig) || []
89
+ if (fileOps.length > 1 && /[;\r\n]/.test(normalized)) {
90
+ warnings.push('Multiple file operations are chained in one shell command; split them into separate commands')
91
+ }
92
+
93
+ return warnings
94
+ }
95
+
71
96
  export function scanUnrequestedFiles(filePath, toolName) {
72
97
  if (!filePath || toolName?.toLowerCase() !== 'write') return []
73
98
  const basename = filePath.split(/[/\\]/).pop() || ''
package/scripts/guard.mjs CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  scanEnvCoverage,
19
19
  scanForSecrets,
20
20
  scanHighRiskCommands,
21
+ scanShellSafetyWarnings,
21
22
  scanUnrequestedFiles,
22
23
  } from './guard-rules.mjs'
23
24
 
@@ -61,15 +62,16 @@ function emitGuardEvent(cwd, event, source, reason, details = {}) {
61
62
  })
62
63
  }
63
64
 
64
- function buildHighRiskGate(matches, cwd) {
65
- const stateSyncHint = buildStateSyncHint(cwd)
65
+ function buildHighRiskGate(matches, cwd, payload = {}) {
66
+ const workflowOptions = { payload }
67
+ const stateSyncHint = buildStateSyncHint(cwd, workflowOptions)
66
68
  if (stateSyncHint) {
67
69
  return {
68
70
  reason: `[HelloAGENTS Guard] Blocked T3 command until project recovery state is synced.\n${stateSyncHint}`,
69
71
  }
70
72
  }
71
73
 
72
- const recommendation = getWorkflowRecommendation(cwd)
74
+ const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
73
75
  if (!recommendation) return null
74
76
  if (matches.some((match) => match.gate === 'post-verify')) {
75
77
  return {
@@ -122,7 +124,7 @@ function buildPostWriteWarnings(data) {
122
124
  const filePath = data.tool_input?.file_path || ''
123
125
  return [
124
126
  ...(detectIdeaBoundaryContext(data)?.zeroSideEffect
125
- ? ['~idea 当前轮要求只读探索;检测到写入工具落地,请回退到探索输出或升级到 ~plan / ~build / ~prd / ~auto 后再修改文件']
127
+ ? ['~idea 本轮要求只读探索;检测到写入工具落地,请回退到探索输出或升级到 ~plan / ~build / ~prd / ~auto 后再修改文件']
126
128
  : []),
127
129
  ...scanUnrequestedFiles(filePath, data.tool_name),
128
130
  ...(content ? [...scanForSecrets(content), ...scanDangerousPackages(content, filePath)] : []),
@@ -168,10 +170,10 @@ function handleDangerousCommand(data, command) {
168
170
 
169
171
  function handleHighRiskCommand(data, command) {
170
172
  const warnings = scanHighRiskCommands(command)
171
- if (warnings.length === 0) return
173
+ if (warnings.length === 0) return []
172
174
 
173
175
  const cwd = data.cwd || process.cwd()
174
- const gate = buildHighRiskGate(warnings, cwd)
176
+ const gate = buildHighRiskGate(warnings, cwd, data)
175
177
  if (gate) {
176
178
  emitHookPayload({
177
179
  hookSpecificOutput: {
@@ -185,20 +187,43 @@ function handleHighRiskCommand(data, command) {
185
187
  guardType: 'high-risk-gate',
186
188
  matches: warnings.map((warning) => warning.reason),
187
189
  })
188
- return
190
+ return null
191
+ }
192
+ return warnings.map((warning) => warning.reason)
193
+ }
194
+
195
+ function emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings) {
196
+ const sections = []
197
+ if (highRiskWarnings.length > 0) {
198
+ sections.push(`⚠️ [HelloAGENTS 高风险链路提醒] 检测到高风险命令:\n${highRiskWarnings.map((warning) => ` - ${warning}`).join('\n')}\n请确认已完成相应规划/审查并获得必要授权。`)
199
+ }
200
+ if (shellSafetyWarnings.length > 0) {
201
+ sections.push(`⚠️ [HelloAGENTS Shell 安全提醒] 检测到建议调整的命令写法:\n${shellSafetyWarnings.map((warning) => ` - ${warning}`).join('\n')}\n当前仅提示,不中断执行。`)
189
202
  }
203
+ if (sections.length === 0) return
190
204
 
191
205
  emitHookPayload({
192
206
  hookSpecificOutput: {
193
207
  hookEventName: HOOK_EVENT,
194
- additionalContext: `⚠️ [HelloAGENTS 高风险链路提醒] 检测到高风险命令:\n${warnings.map((warning) => ` - ${warning.reason}`).join('\n')}\n请确认已完成相应规划/审查并获得必要授权。`,
208
+ additionalContext: sections.join('\n\n'),
195
209
  },
196
210
  })
197
- emitGuardEvent(cwd, 'guard_warning', 'command', '', {
198
- guardType: 'high-risk-warning',
199
- command: command.slice(0, 200),
200
- warnings: warnings.map((warning) => warning.reason),
201
- })
211
+
212
+ const cwd = data.cwd || process.cwd()
213
+ if (highRiskWarnings.length > 0) {
214
+ emitGuardEvent(cwd, 'guard_warning', 'command', '', {
215
+ guardType: 'high-risk-warning',
216
+ command: command.slice(0, 200),
217
+ warnings: highRiskWarnings,
218
+ })
219
+ }
220
+ if (shellSafetyWarnings.length > 0) {
221
+ emitGuardEvent(cwd, 'guard_warning', 'command', '', {
222
+ guardType: 'shell-safety-warning',
223
+ command: command.slice(0, 200),
224
+ warnings: shellSafetyWarnings,
225
+ })
226
+ }
202
227
  }
203
228
 
204
229
  function handleShellCommand(data) {
@@ -217,7 +242,11 @@ function handleShellCommand(data) {
217
242
  }
218
243
 
219
244
  if (handleDangerousCommand(data, command)) return
220
- handleHighRiskCommand(data, command)
245
+ const highRiskWarnings = handleHighRiskCommand(data, command)
246
+ if (highRiskWarnings === null) return
247
+
248
+ const shellSafetyWarnings = scanShellSafetyWarnings(command)
249
+ emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings)
221
250
  }
222
251
 
223
252
  async function main() {