helloagents 3.0.28 → 3.0.30

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 (44) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +50 -37
  4. package/README_CN.md +50 -35
  5. package/bootstrap-lite.md +8 -8
  6. package/bootstrap.md +12 -10
  7. package/gemini-extension.json +1 -1
  8. package/install.ps1 +5 -5
  9. package/install.sh +5 -5
  10. package/package.json +1 -1
  11. package/scripts/advisor-state.mjs +1 -1
  12. package/scripts/cli-branch.mjs +7 -3
  13. package/scripts/cli-codex.mjs +6 -5
  14. package/scripts/cli-deepseek.mjs +131 -0
  15. package/scripts/cli-doctor-codex.mjs +10 -5
  16. package/scripts/cli-doctor-render.mjs +16 -0
  17. package/scripts/cli-doctor.mjs +78 -5
  18. package/scripts/cli-host-detect.mjs +29 -0
  19. package/scripts/cli-hosts.mjs +8 -0
  20. package/scripts/cli-lifecycle-hosts.mjs +28 -2
  21. package/scripts/cli-lifecycle.mjs +1 -1
  22. package/scripts/cli-messages.mjs +28 -15
  23. package/scripts/cli-runtime-carrier.mjs +3 -3
  24. package/scripts/cli-utils.mjs +9 -0
  25. package/scripts/notify-events.mjs +1 -0
  26. package/scripts/notify-route.mjs +3 -4
  27. package/scripts/notify-source.mjs +1 -0
  28. package/scripts/notify.mjs +6 -1
  29. package/scripts/plan-contract.mjs +1 -1
  30. package/scripts/project-storage.mjs +4 -4
  31. package/scripts/replay-state.mjs +22 -4
  32. package/scripts/runtime-context.mjs +14 -2
  33. package/scripts/runtime-scope.mjs +144 -2
  34. package/scripts/session-capsule.mjs +14 -0
  35. package/scripts/turn-state.mjs +7 -0
  36. package/skills/commands/build/SKILL.md +1 -1
  37. package/skills/commands/commit/SKILL.md +2 -2
  38. package/skills/commands/global/SKILL.md +71 -0
  39. package/skills/commands/help/SKILL.md +16 -15
  40. package/skills/commands/init/SKILL.md +14 -31
  41. package/skills/commands/wiki/SKILL.md +1 -1
  42. package/skills/hello-review/SKILL.md +1 -1
  43. package/skills/hello-ui/SKILL.md +5 -5
  44. package/skills/helloagents/SKILL.md +6 -5
package/bootstrap.md CHANGED
@@ -20,13 +20,13 @@
20
20
  ### 执行纪律
21
21
  - 一次做完:用户需求明确且已获得执行授权时,必须持续执行到完成;只有符合下文“阻塞判定”的情况,才可中途停下
22
22
  - 直接推进:用户已明确同意方案、修改方向或继续执行时,直接执行;不得把可执行动作改写为建议、可选项、等待确认,也不用“下一步建议”代替实际执行
23
- - 普通问答、解释、分析、改写、邮件回复和其他一次性交付,不进入完整实现/验证流程,但仍属于交付;请求已满足时直接结束,不追加无执行价值的延伸、第二版或邀约式收尾,除非用户明确要求
23
+ - 普通问答、解释、分析、改写、邮件回复和其他一次性交付,不进入完整实现/验证流程,但仍属于交付;默认只交付与当前请求直接对应的一版最终结果。请求已满足时直接结束,不主动追加无执行价值的延伸、派生版本、不同写法、第二版或邀约式收尾,除非用户明确要求
24
24
  - 回复末尾只保留结论、风险、限制、已完成状态、阻塞项或真实下一步动作;不得用条件式邀约、自我能力陈述或“如果需要 / 如需 / 我可以继续”这类表述替代交付
25
25
 
26
26
  ### 表达与语气
27
27
  - 所有用户可见文本,包括回复、生成文件、CLI 输出、运行时提示、模板内容、文档与说明,都必须同时遵守本节全部规则:
28
28
  - 说话像成熟同事,不像客服、销售或咨询顾问
29
- - 直接回答,少铺垫;需要先给结论时先给结论,再补必要细节
29
+ - 直接回答,少铺垫;需要先给结论时先给结论,再补必要细节。能用一版说清就只给一版,不主动提供多个备选、补充改写或派生版本,除非用户明确要求比较、多方案或不同风格版本
30
30
  - 用词用语和表述方式保持简洁、自然、清晰、准确、合理、统一,不赘述、不冗余、不过度精简
31
31
  - 优先使用普通、易懂、贴近用户的表达;必要术语先解释,再补原名
32
32
  - 准确优先于压缩:不得为了更短而省略必要的条件、边界、风险、状态、路径、验证结论或下一步动作
@@ -209,7 +209,7 @@
209
209
  - 审查 / 执行验证 → `~verify`
210
210
  - 不确定或希望端到端自动推进时使用 `~auto`
211
211
 
212
- 当前项目只要已建立 `.helloagents/`(例如执行过 `~wiki`、`~init`,或已进入项目级连续流程),就按项目级完整流程执行。
212
+ 当前项目只要已初始化(例如执行过 `~global`,或当前项目级规则文件已包含 `<!-- HELLOAGENTS_PROFILE: full -->`),就按项目级完整流程执行。
213
213
 
214
214
  #### 2. SPEC — 澄清目标与验收
215
215
  根据任务需要,按需读取项目上下文(知识库文件和项目文件),明确:
@@ -225,9 +225,9 @@
225
225
  先确定当前技能根目录:
226
226
  - 优先使用当前上下文中已注入的“本轮 HelloAGENTS 读取根目录”
227
227
  - 若当前上下文未注入,则使用稳定运行根目录 `~/.helloagents/helloagents`
228
- - 宿主固定链接(Codex `~/.codex/helloagents`、Claude `~/.claude/helloagents`、Gemini `~/.gemini/helloagents`)只作为兼容别名,不作为优先探测路径
228
+ - 宿主固定链接(Codex `~/.codex/helloagents`、Claude `~/.claude/helloagents`、Gemini `~/.gemini/helloagents`、DeepSeek `~/.deepseek/helloagents`)只作为兼容别名,不作为优先探测路径
229
229
  - 仍无法确定时,明确说明缺少 HelloAGENTS 读取根目录;不要递归扫描 `$HOME`、`Downloads`、项目目录或旧版本目录
230
- - 已激活项目或全局模式下,技能是否需要使用由当前已加载 AGENTS 规则决定;不要因此额外探测项目目录里的 HelloAGENTS skills 路径
230
+ - 全局模式或已初始化项目时,技能是否需要使用由当前已加载 AGENTS 规则决定;不要因此额外探测项目目录里的 HelloAGENTS skills 路径
231
231
  路径确定一次即可,不预读、不扫描整个目录,也不重复探测同一路径。
232
232
  hello-* 技能读取路径:`{HELLOAGENTS_READ_ROOT}/skills/{技能名}/SKILL.md`
233
233
  包内脚本优先使用稳定命令入口;涉及 turn-state 时按“收尾状态信号”执行。
@@ -265,8 +265,10 @@ hello-* 技能读取路径:`{HELLOAGENTS_READ_ROOT}/skills/{技能名}/SKILL.m
265
265
  - 有方案包且准备报告完成 → 优先调用 `scripts/closeout-state.mjs write` 写当前会话 `artifacts/closeout.json`,记录“需求覆盖”和“交付清单”;每项写明 `PASS` / `BLOCKED` 与简要摘要,再进入最终交付
266
266
  - 状态文件维护:按上文“流程状态”中的适用范围执行。属于“强制创建并持续更新”范围时,重写 `state_path` 指向的文件(“正在做什么”更新为已完成,清空关键上下文 / 下一步 / 阻塞项);属于“已有则更新”范围时,仅在文件已存在时重写;属于“不创建”范围时不生成此文件
267
267
  - 有方案包且任务已完成 → 将整个 `plans/{feature}/` 目录归档到 `.helloagents/archive/YYYY-MM/`,并更新 `archive/_index.md`。清理当前会话临时文件(`artifacts/loop-results.tsv`、`capsule.json`、`events.jsonl`、`artifacts/loop-breaker.json`、`artifacts/verify.json`、`artifacts/review.json`、`artifacts/closeout.json`)
268
- - 按 `kb_create_mode` 同步知识库(0=关闭 / 1=已激活项目或全局模式中编码自动 / 2=已激活项目或全局模式中始终):
269
- - `.helloagents/` 不存在则按 templates/ 创建知识库文件(`context.md`、`guidelines.md`、`verify.yaml`、`CHANGELOG.md`、`modules/`)
268
+ - 按 `kb_create_mode` 同步知识库(0=关闭 / 1=知识库已存在时自动同步,未创建则不自动补建 / 2=编码任务在知识库已存在或全局模式下自动创建或同步):
269
+ - `0` 跳过
270
+ - `1` → 仅在知识库已存在时按模板增量同步;未创建则不自动补建
271
+ - `2` → 仅在编码任务中生效;知识库已存在时按模板增量同步;若知识库不存在但当前项目已处于全局模式,则按 templates/ 创建或补全 `context.md`、`guidelines.md`、`verify.yaml`、`CHANGELOG.md`、`modules/`
270
272
  - 已存在但不完整(缺少上述核心文件)→ 按 templates/ 补全缺失文件,不覆盖已有文件
271
273
  - 已存在且完整则按模板格式更新 `CHANGELOG.md`、相关 `modules/*.md`、增量经验 delta 追加
272
274
  - 符合条件时触发 `hello-reflect`(详见 `hello-reflect` SKILL.md)
@@ -287,7 +289,7 @@ hello-* 技能读取路径:`{HELLOAGENTS_READ_ROOT}/skills/{技能名}/SKILL.m
287
289
  ### .helloagents/ 目录
288
290
  路径: {CWD}/.helloagents/
289
291
  所有文件的创建和更新必须按 templates/ 目录中对应模板的格式执行,不可自由发挥格式。
290
- - `.helloagents/` 表示项目级存储路径,也是标准模式的项目激活信号
292
+ - `.helloagents/` 表示项目本地存储路径,负责知识、方案、状态与运行态;它不再作为项目是否已初始化的判定信号
291
293
  - `state_path` 指向的状态文件、当前会话 `capsule.json`、`events.jsonl`、`artifacts/*.json`、`artifacts/loop-results.tsv` 等运行态文件始终保留在项目本地 `.helloagents/sessions/{workspace}/{session}/`
292
294
  - `state_path` 是状态文件的唯一位置。宿主提供会话标识时,写入 `.helloagents/sessions/{workspace}/{session}/STATE.md`;没有稳定会话标识时,写入 `.helloagents/sessions/{workspace}/default/STATE.md`
293
295
  - `{workspace}` 为当前 Git 分支、`detached-{sha}` 或非 Git 项目的 `workspace`;`.helloagents/sessions/active.json` 只记录当前活跃会话索引,避免同一会话被拆成多个目录
@@ -299,7 +301,7 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
299
301
  - 状态文件(`state_path`)— ≤70 行,用来记录“上次做到哪里”。判断当前任务时,当前用户消息、显式命令、活跃方案包 / PRD、代码与验证证据优先于状态文件
300
302
  内容:主线目标、正在做什么、关键上下文(决策/变更/假设)、下一步(具体可执行动作含文件路径)、阻塞项
301
303
  适用边界:
302
- - 强制创建并持续更新:`~wiki`、`~init`、`~plan`、`~build`、`~auto`、`~prd`、`~loop`,以及进入工作流阶段或已激活项目的连续任务
304
+ - 强制创建并持续更新:`~wiki`、`~init`、`~global`、`~plan`、`~build`、`~auto`、`~prd`、`~loop`,以及进入工作流阶段、已初始化项目的连续任务,或任何会创建/修改本地文件、会在当前工作区留下实际输出或操作记录的非只读任务
303
305
  - 强制更新,不要求首次创建:`~clean`,主代理汇总子代理结果后
304
306
  - 已有则更新:`~verify`、`~review`(兼容别名)、`~test`、`~commit`
305
307
  - 不创建:`~help`、`~idea`、普通问答、一次性只读任务、子代理自身执行过程、压缩/恢复钩子
@@ -320,7 +322,7 @@ templates/ 查找路径(按优先级;首次确定模板根目录后,本轮
320
322
  - archive/_index.md — 归档索引
321
323
 
322
324
  ### 知识记录(受 `kb_create_mode` 控制)
323
- - 0=关闭;1=已激活项目或全局模式中的编码任务自动同步;2=已激活项目或全局模式中始终同步
325
+ - 0=关闭;1=知识库已存在时自动同步;2=编码任务在知识库已存在或全局模式下自动创建或同步
324
326
  - context.md — 项目架构、技术栈、目录结构、模块索引
325
327
  - guidelines.md — 编码约定(仅含非显而易见的约定)
326
328
  - CHANGELOG.md — 变更历史
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.28",
3
+ "version": "3.0.30",
4
4
  "description": "Quality-driven orchestration kernel for AI CLIs",
5
5
  "contextFileName": "bootstrap.md",
6
6
  "author": "HelloWind",
package/install.ps1 CHANGED
@@ -1,12 +1,12 @@
1
1
  # HelloAGENTS one-shot installer.
2
2
  #
3
3
  # Environment:
4
- # HELLOAGENTS=all|claude|gemini|codex[:standby|global]
4
+ # HELLOAGENTS=all|claude|gemini|codex|deepseek[:standby|global]
5
5
  # HELLOAGENTS_ACTION=install|update|cleanup|uninstall|switch-branch|branch
6
- # HELLOAGENTS_TARGET=all|claude|gemini|codex
6
+ # HELLOAGENTS_TARGET=all|claude|gemini|codex|deepseek
7
7
  # HELLOAGENTS_MODE=standby|global
8
8
  # HELLOAGENTS_BRANCH=main|beta|...
9
- # HELLOAGENTS_PACKAGE=helloagents|github:owner/repo#ref|...
9
+ # HELLOAGENTS_PACKAGE=helloagents|https://github.com/owner/repo/archive/refs/heads/ref.tar.gz|...
10
10
 
11
11
  $ErrorActionPreference = "Stop"
12
12
 
@@ -29,7 +29,7 @@ if (-not $Target) { $Target = "all" }
29
29
  $Target = $Target.ToLowerInvariant()
30
30
  if ($Mode) { $Mode = $Mode.ToLowerInvariant() }
31
31
 
32
- if (@("all", "claude", "gemini", "codex") -notcontains $Target) {
32
+ if (@("all", "claude", "gemini", "codex", "deepseek") -notcontains $Target) {
33
33
  throw "Unsupported HELLOAGENTS target: $Target"
34
34
  }
35
35
 
@@ -39,7 +39,7 @@ if ($Mode -and @("standby", "global") -notcontains $Mode) {
39
39
 
40
40
  if (-not $Package) {
41
41
  if ($Branch) {
42
- $Package = "github:hellowind777/helloagents#$Branch"
42
+ $Package = "https://github.com/hellowind777/helloagents/archive/refs/heads/$Branch.tar.gz"
43
43
  } else {
44
44
  $Package = "helloagents"
45
45
  }
package/install.sh CHANGED
@@ -4,12 +4,12 @@ set -eu
4
4
  # HelloAGENTS one-shot installer.
5
5
  #
6
6
  # Environment:
7
- # HELLOAGENTS=all|claude|gemini|codex[:standby|global]
7
+ # HELLOAGENTS=all|claude|gemini|codex|deepseek[:standby|global]
8
8
  # HELLOAGENTS_ACTION=install|update|cleanup|uninstall|switch-branch|branch
9
- # HELLOAGENTS_TARGET=all|claude|gemini|codex
9
+ # HELLOAGENTS_TARGET=all|claude|gemini|codex|deepseek
10
10
  # HELLOAGENTS_MODE=standby|global
11
11
  # HELLOAGENTS_BRANCH=main|beta|...
12
- # HELLOAGENTS_PACKAGE=helloagents|github:owner/repo#ref|...
12
+ # HELLOAGENTS_PACKAGE=helloagents|https://github.com/owner/repo/archive/refs/heads/ref.tar.gz|...
13
13
 
14
14
  ACTION="${HELLOAGENTS_ACTION:-install}"
15
15
  TARGET="${HELLOAGENTS_TARGET:-}"
@@ -36,7 +36,7 @@ TARGET="$(printf '%s' "$TARGET" | tr '[:upper:]' '[:lower:]')"
36
36
  MODE="$(printf '%s' "$MODE" | tr '[:upper:]' '[:lower:]')"
37
37
 
38
38
  case "$TARGET" in
39
- all|claude|gemini|codex) ;;
39
+ all|claude|gemini|codex|deepseek) ;;
40
40
  *) echo "Unsupported HELLOAGENTS target: $TARGET" >&2; exit 1 ;;
41
41
  esac
42
42
 
@@ -49,7 +49,7 @@ fi
49
49
 
50
50
  if [ -z "$PACKAGE" ]; then
51
51
  if [ -n "$BRANCH" ]; then
52
- PACKAGE="github:hellowind777/helloagents#$BRANCH"
52
+ PACKAGE="https://github.com/hellowind777/helloagents/archive/refs/heads/$BRANCH.tar.gz"
53
53
  else
54
54
  PACKAGE="helloagents"
55
55
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.28",
3
+ "version": "3.0.30",
4
4
  "type": "module",
5
5
  "description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing, quality verification (Ralph Loop), safety guards, and notifications.",
6
6
  "author": "HelloWind",
@@ -15,7 +15,7 @@ import {
15
15
 
16
16
  export const ADVISOR_EVIDENCE_FILE_NAME = 'advisor.json'
17
17
  const VALID_ADVISOR_OUTCOMES = new Set(['clean', 'findings'])
18
- const VALID_SOURCES = new Set(['claude', 'codex', 'gemini'])
18
+ const VALID_SOURCES = new Set(['claude', 'codex', 'gemini', 'deepseek'])
19
19
 
20
20
  function normalizeStringArray(values) {
21
21
  if (!Array.isArray(values)) return []
@@ -2,7 +2,7 @@ import { spawnSync } from 'node:child_process'
2
2
 
3
3
  import { normalizeHost } from './cli-lifecycle.mjs'
4
4
 
5
- const DEFAULT_REPO_SPEC = 'github:hellowind777/helloagents'
5
+ const DEFAULT_REPO_ARCHIVE_BASE = 'https://github.com/hellowind777/helloagents/archive/refs/heads'
6
6
 
7
7
  function runCommand(command, args) {
8
8
  const needsShell = process.platform === 'win32' && /\.cmd$/i.test(command)
@@ -19,6 +19,10 @@ function runCommand(command, args) {
19
19
  }
20
20
  }
21
21
 
22
+ function getDefaultNpmCommand() {
23
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm'
24
+ }
25
+
22
26
  function parseModeFlag(args) {
23
27
  const hasGlobal = args.includes('--global')
24
28
  const hasStandby = args.includes('--standby')
@@ -56,7 +60,7 @@ function parseBranchArgs(args) {
56
60
 
57
61
  function buildPackageSpec(ref) {
58
62
  if (/^(github:|git\+|https?:|file:)/i.test(ref)) return ref
59
- return `${DEFAULT_REPO_SPEC}#${ref}`
63
+ return `${DEFAULT_REPO_ARCHIVE_BASE}/${ref}.tar.gz`
60
64
  }
61
65
 
62
66
  function buildSyncArgs({ host, mode }) {
@@ -76,7 +80,7 @@ function buildSyncArgs({ host, mode }) {
76
80
 
77
81
  export function runBranchSwitch(args, options = {}) {
78
82
  const parsed = parseBranchArgs(args)
79
- const npmCommand = options.npmCommand || process.env.HELLOAGENTS_NPM_CMD || 'npm'
83
+ const npmCommand = options.npmCommand || process.env.HELLOAGENTS_NPM_CMD || getDefaultNpmCommand()
80
84
 
81
85
  const packageSpec = buildPackageSpec(parsed.branch)
82
86
  runCommand(npmCommand, ['install', '-g', packageSpec])
@@ -121,17 +121,17 @@ function removeCodexMarketplaceEntry(marketplaceFile) {
121
121
  return true;
122
122
  }
123
123
 
124
- function injectCodexRuntimeCarrier(filePath, bootstrapPath, settings) {
124
+ function injectCodexRuntimeCarrier(filePath, bootstrapPath, settings, options = {}) {
125
125
  const bootstrapContent = safeRead(bootstrapPath);
126
126
  if (!bootstrapContent) return false;
127
- injectMarkedContent(filePath, buildRuntimeCarrier(bootstrapContent, settings).trimEnd());
127
+ injectMarkedContent(filePath, buildRuntimeCarrier(bootstrapContent, settings, options).trimEnd());
128
128
  return true;
129
129
  }
130
130
 
131
- function writeCodexRuntimeCarrier(filePath, bootstrapPath, settings) {
131
+ function writeCodexRuntimeCarrier(filePath, bootstrapPath, settings, options = {}) {
132
132
  const bootstrapContent = safeRead(bootstrapPath);
133
133
  if (!bootstrapContent) return false;
134
- safeWrite(filePath, buildRuntimeCarrier(bootstrapContent, settings));
134
+ safeWrite(filePath, buildRuntimeCarrier(bootstrapContent, settings, options));
135
135
  return true;
136
136
  }
137
137
 
@@ -298,9 +298,10 @@ export function installCodexGlobal(home, pkgRoot) {
298
298
  join(pkgRoot, CODEX_RUNTIME_CARRIER),
299
299
  join(pkgRoot, 'bootstrap.md'),
300
300
  settings,
301
+ { profile: 'full' },
301
302
  );
302
303
  const homeCarrierPath = join(codexDir, CODEX_RUNTIME_CARRIER);
303
- injectCodexRuntimeCarrier(homeCarrierPath, join(pkgRoot, 'bootstrap.md'), settings);
304
+ injectCodexRuntimeCarrier(homeCarrierPath, join(pkgRoot, 'bootstrap.md'), settings, { profile: 'full' });
304
305
 
305
306
  ensureDir(join(home, '.agents', 'plugins'));
306
307
  updateCodexMarketplace(marketplaceFile);
@@ -0,0 +1,131 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+
5
+ import { buildRuntimeCarrier, readCarrierSettings } from './cli-runtime-carrier.mjs'
6
+ import { createLink, ensureDir, injectMarkedContent, removeLink, removeMarkedContent, safeRead } from './cli-utils.mjs'
7
+
8
+ export const DEEPSEEK_COMMAND = process.env.HELLOAGENTS_DEEPSEEK_CMD || 'deepseek'
9
+
10
+ function looksLikeCommandPath(command = '') {
11
+ return /[\\/]/.test(command) || /^[A-Za-z]:/.test(command)
12
+ }
13
+
14
+ function injectDeepseekCarrier(home, pkgRoot, bootstrapFile, options = {}) {
15
+ const deepseekDir = join(home, '.deepseek')
16
+ ensureDir(deepseekDir)
17
+
18
+ const bootstrapContent = safeRead(join(pkgRoot, bootstrapFile))
19
+ if (!bootstrapContent) return false
20
+
21
+ injectMarkedContent(
22
+ join(deepseekDir, 'AGENTS.md'),
23
+ buildRuntimeCarrier(bootstrapContent, readCarrierSettings(home), options).trimEnd(),
24
+ )
25
+ createLink(pkgRoot, join(deepseekDir, 'helloagents'))
26
+ return true
27
+ }
28
+
29
+ export function installDeepseekStandby(home, pkgRoot) {
30
+ return injectDeepseekCarrier(home, pkgRoot, 'bootstrap-lite.md')
31
+ }
32
+
33
+ export function installDeepseekGlobal(home, pkgRoot) {
34
+ return injectDeepseekCarrier(home, pkgRoot, 'bootstrap.md', { profile: 'full' })
35
+ }
36
+
37
+ export function uninstallDeepseekStandby(home) {
38
+ const deepseekDir = join(home, '.deepseek')
39
+ if (!existsSync(deepseekDir)) return false
40
+
41
+ removeMarkedContent(join(deepseekDir, 'AGENTS.md'))
42
+ removeLink(join(deepseekDir, 'helloagents'))
43
+ return true
44
+ }
45
+
46
+ export function uninstallDeepseekGlobal(home) {
47
+ return uninstallDeepseekStandby(home)
48
+ }
49
+
50
+ function runDeepseekCommand(command, args = []) {
51
+ if (looksLikeCommandPath(command) && !existsSync(command)) {
52
+ return {
53
+ ok: false,
54
+ missing: true,
55
+ exitCode: null,
56
+ stdout: '',
57
+ stderr: '',
58
+ errorMessage: '',
59
+ combinedOutput: '',
60
+ }
61
+ }
62
+ const needsShell = process.platform === 'win32' && /\.cmd$/i.test(command)
63
+ const result = spawnSync(command, args, {
64
+ encoding: 'utf-8',
65
+ errors: 'replace',
66
+ shell: needsShell,
67
+ windowsHide: true,
68
+ })
69
+ const stdout = String(result.stdout || '').trim()
70
+ const stderr = String(result.stderr || '').trim()
71
+ const errorMessage = result.error?.message || ''
72
+ return {
73
+ ok: result.status === 0,
74
+ missing: result.error?.code === 'ENOENT',
75
+ exitCode: typeof result.status === 'number' ? result.status : null,
76
+ stdout,
77
+ stderr,
78
+ errorMessage,
79
+ combinedOutput: [stdout, stderr, errorMessage].filter(Boolean).join('\n').trim(),
80
+ }
81
+ }
82
+
83
+ function tryParseJson(text = '') {
84
+ if (!text) return null
85
+ try {
86
+ return JSON.parse(text)
87
+ } catch {
88
+ return null
89
+ }
90
+ }
91
+
92
+ function buildDoctorSummary(report = {}) {
93
+ const skills = report.skills || {}
94
+ const capability = report.capability || {}
95
+ const sandbox = report.sandbox || {}
96
+ const mcp = report.mcp || {}
97
+ return {
98
+ version: typeof report.version === 'string' ? report.version : '',
99
+ configPath: typeof report.config_path === 'string' ? report.config_path : '',
100
+ skillsSelected: Array.isArray(skills.selected) ? skills.selected : [],
101
+ mcpPresent: Boolean(mcp.present),
102
+ sandboxAvailable: typeof sandbox.available === 'boolean' ? sandbox.available : null,
103
+ resolvedProvider: typeof capability.resolved_provider === 'string' ? capability.resolved_provider : '',
104
+ resolvedModel: typeof capability.resolved_model === 'string' ? capability.resolved_model : '',
105
+ }
106
+ }
107
+
108
+ export function inspectNativeDeepseekDoctor() {
109
+ const result = runDeepseekCommand(DEEPSEEK_COMMAND, ['doctor', '--json'])
110
+ if (result.missing) {
111
+ return {
112
+ available: false,
113
+ command: DEEPSEEK_COMMAND,
114
+ exitCode: null,
115
+ ok: false,
116
+ summary: null,
117
+ note: 'command-not-found',
118
+ }
119
+ }
120
+
121
+ const parsed = tryParseJson(result.stdout) || tryParseJson(result.combinedOutput)
122
+ return {
123
+ available: true,
124
+ command: DEEPSEEK_COMMAND,
125
+ exitCode: result.exitCode,
126
+ ok: result.ok,
127
+ summary: parsed ? buildDoctorSummary(parsed) : null,
128
+ parseError: parsed ? '' : (result.combinedOutput ? 'invalid-json' : ''),
129
+ output: parsed ? '' : result.combinedOutput,
130
+ }
131
+ }
@@ -64,9 +64,9 @@ function readExpectedHooks(runtime, hooksFile, pathVar) {
64
64
  return pickManagedHooks(loadHooksWithCliEntry(runtime.pkgRoot, hooksFile, pathVar)?.hooks || {})
65
65
  }
66
66
 
67
- function readExpectedCarrierContent(runtime, fileName, settings) {
67
+ function readExpectedCarrierContent(runtime, fileName, settings, options = {}) {
68
68
  const bootstrap = safeRead(join(runtime.pkgRoot, fileName)) || ''
69
- return normalizeText(buildRuntimeCarrier(bootstrap, settings))
69
+ return normalizeText(buildRuntimeCarrier(bootstrap, settings, options))
70
70
  }
71
71
 
72
72
  function buildDoctorIssue(runtime, code, cn, en) {
@@ -168,7 +168,12 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
168
168
  checks: {
169
169
  carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
170
170
  carrierContentMatch: normalizeText((safeRead(join(codexDir, 'AGENTS.md')) || '').match(/<!-- HELLOAGENTS_START -->([\s\S]*?)<!-- HELLOAGENTS_END -->/)?.[1] || '')
171
- === readExpectedCarrierContent(runtime, expectedHomeCarrier, settings),
171
+ === readExpectedCarrierContent(
172
+ runtime,
173
+ expectedHomeCarrier,
174
+ settings,
175
+ expectedHomeCarrier === 'bootstrap.md' ? { profile: 'full' } : {},
176
+ ),
172
177
  homeLink: homeLinkTarget === (safeRealTarget(runtime.pkgRoot) || normalizePath(runtime.pkgRoot)),
173
178
  globalHomeLink: homeLinkTarget === runtimeRoot,
174
179
  modelInstructionsFile: !!modelInstructionsLine,
@@ -187,8 +192,8 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
187
192
  pluginCache: existsSync(pluginCacheRoot),
188
193
  pluginRootLink: pluginRootTarget === runtimeRoot,
189
194
  pluginCacheLink: pluginCacheTarget === runtimeRoot,
190
- pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings),
191
- pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings),
195
+ pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings, { profile: 'full' }),
196
+ pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings, { profile: 'full' }),
192
197
  marketplaceEntry: Array.isArray(marketplace.plugins) && marketplace.plugins.some((plugin) => plugin?.name === CODEX_PLUGIN_NAME),
193
198
  pluginEnabled: codexConfig.includes(CODEX_PLUGIN_CONFIG_HEADER) && codexConfig.includes('enabled = true'),
194
199
  globalNotifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
@@ -17,6 +17,22 @@ export function printDoctorText(runtime, report) {
17
17
  for (const [key, value] of Object.entries(entry.checks)) {
18
18
  console.log(` ${key}: ${value ? 'ok' : 'missing'}`)
19
19
  }
20
+ if (entry.nativeDoctor) {
21
+ console.log(` native_doctor.available: ${entry.nativeDoctor.available ? 'ok' : 'missing'}`)
22
+ if (entry.nativeDoctor.available) {
23
+ console.log(` native_doctor.ok: ${entry.nativeDoctor.ok ? 'ok' : 'missing'}`)
24
+ }
25
+ if (entry.nativeDoctor.summary) {
26
+ if (entry.nativeDoctor.summary.version) console.log(` native_doctor.version: ${entry.nativeDoctor.summary.version}`)
27
+ if (entry.nativeDoctor.summary.configPath) console.log(` native_doctor.config_path: ${entry.nativeDoctor.summary.configPath}`)
28
+ if (entry.nativeDoctor.summary.resolvedProvider) console.log(` native_doctor.resolved_provider: ${entry.nativeDoctor.summary.resolvedProvider}`)
29
+ if (entry.nativeDoctor.summary.resolvedModel) console.log(` native_doctor.resolved_model: ${entry.nativeDoctor.summary.resolvedModel}`)
30
+ if (entry.nativeDoctor.summary.sandboxAvailable !== null) console.log(` native_doctor.sandbox_available: ${entry.nativeDoctor.summary.sandboxAvailable}`)
31
+ console.log(` native_doctor.mcp_present: ${entry.nativeDoctor.summary.mcpPresent ? 'ok' : 'missing'}`)
32
+ console.log(` native_doctor.skills_selected: ${entry.nativeDoctor.summary.skillsSelected.join(', ') || '(none)'}`)
33
+ }
34
+ if (entry.nativeDoctor.output) console.log(` native_doctor.output: ${entry.nativeDoctor.output}`)
35
+ }
20
36
  for (const note of entry.notes) {
21
37
  console.log(` note: ${note}`)
22
38
  }
@@ -3,10 +3,10 @@ import { join } from 'node:path'
3
3
 
4
4
  import { DEFAULTS } from './cli-config.mjs'
5
5
  import { inspectCodexDoctor as inspectCodexDoctorImpl } from './cli-doctor-codex.mjs'
6
+ import { inspectNativeDeepseekDoctor } from './cli-deepseek.mjs'
6
7
  import { printDoctorText } from './cli-doctor-render.mjs'
7
8
  import { buildRuntimeCarrier } from './cli-runtime-carrier.mjs'
8
- import { readTopLevelTomlLine } from './cli-toml.mjs'
9
- import { loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
9
+ import { FULL_CARRIER_PROFILE_MARKER, loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
10
10
 
11
11
  const runtime = {
12
12
  home: '',
@@ -78,9 +78,9 @@ function managedHooksMatch(actualHooks, expectedHooks) {
78
78
  return stringifySorted(pickManagedHooks(actualHooks || {})) === stringifySorted(expectedHooks || {})
79
79
  }
80
80
 
81
- function readExpectedCarrierContent(fileName, settings) {
81
+ function readExpectedCarrierContent(fileName, settings, options = {}) {
82
82
  const bootstrap = safeRead(join(runtime.pkgRoot, fileName)) || ''
83
- return normalizeText(buildRuntimeCarrier(bootstrap, settings))
83
+ return normalizeText(buildRuntimeCarrier(bootstrap, settings, options))
84
84
  }
85
85
 
86
86
  function buildDoctorIssue(code, cn, en) {
@@ -219,6 +219,78 @@ function inspectGeminiDoctor(settings) {
219
219
  return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
220
220
  }
221
221
 
222
+ function inspectDeepseekDoctor(settings) {
223
+ const host = 'deepseek'
224
+ const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
225
+ const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
226
+ const deepseekDir = join(runtime.home, '.deepseek')
227
+ const carrierPath = join(deepseekDir, 'AGENTS.md')
228
+ const expectedCarrierFile = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
229
+ ? 'bootstrap.md'
230
+ : 'bootstrap-lite.md'
231
+ const checks = {
232
+ carrierMarker: (safeRead(carrierPath) || '').includes('HELLOAGENTS_START'),
233
+ carrierContentMatch: extractManagedCarrierContent(carrierPath)
234
+ === readExpectedCarrierContent(
235
+ expectedCarrierFile,
236
+ settings,
237
+ expectedCarrierFile === 'bootstrap.md' ? { profile: 'full' } : {},
238
+ ),
239
+ homeLink: safeRealTarget(join(deepseekDir, 'helloagents')) === runtime.pkgRoot,
240
+ fullProfile: (safeRead(carrierPath) || '').includes(FULL_CARRIER_PROFILE_MARKER),
241
+ }
242
+ const nativeDoctor = inspectNativeDeepseekDoctor()
243
+ const issues = []
244
+ const notes = []
245
+
246
+ if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
247
+ issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
248
+ }
249
+ if (detectedMode === 'standby') {
250
+ if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby `~/.deepseek/AGENTS.md` 缺少 HelloAGENTS 标记', 'Standby `~/.deepseek/AGENTS.md` is missing the HELLOAGENTS marker'))
251
+ if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby `~/.deepseek/AGENTS.md` 与当前标准模式规则不一致', 'Standby `~/.deepseek/AGENTS.md` differs from the current standby rules'))
252
+ if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby `~/.deepseek/helloagents` 链接缺失或未指向稳定运行根目录', 'Standby `~/.deepseek/helloagents` link is missing or points to a different runtime root'))
253
+ if (checks.fullProfile) issues.push(buildDoctorIssue('standby-full-profile-residue', 'standby 模式下不应保留 full 标记', 'Standby mode should not keep the full profile marker'))
254
+ }
255
+ if (detectedMode === 'global') {
256
+ if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-carrier-missing', 'global `~/.deepseek/AGENTS.md` 缺少 HelloAGENTS 标记', 'Global `~/.deepseek/AGENTS.md` is missing the HELLOAGENTS marker'))
257
+ if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('global-carrier-drift', 'global `~/.deepseek/AGENTS.md` 与当前全局模式规则不一致', 'Global `~/.deepseek/AGENTS.md` differs from the current global rules'))
258
+ if (!checks.homeLink) issues.push(buildDoctorIssue('global-link-missing', 'global `~/.deepseek/helloagents` 链接缺失或未指向稳定运行根目录', 'Global `~/.deepseek/helloagents` link is missing or points to a different runtime root'))
259
+ if (!checks.fullProfile) issues.push(buildDoctorIssue('global-full-profile-missing', 'global 模式缺少 full 标记', 'Global mode is missing the full profile marker'))
260
+ }
261
+ if (trackedMode === 'none' && detectedMode !== 'none') {
262
+ issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
263
+ }
264
+ if (trackedMode !== 'none' && detectedMode === 'none') {
265
+ issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
266
+ }
267
+ if (!nativeDoctor.available) {
268
+ notes.push(runtime.msg(
269
+ '未找到 deepseek 命令;已跳过 DeepSeek 原生 doctor。',
270
+ 'The deepseek command was not found; DeepSeek native doctor was skipped.',
271
+ ))
272
+ } else if (!nativeDoctor.ok) {
273
+ issues.push(buildDoctorIssue('native-doctor-failed', 'DeepSeek 原生 doctor 返回非零状态', 'DeepSeek native doctor returned a non-zero status'))
274
+ }
275
+ if (nativeDoctor.available && nativeDoctor.parseError) {
276
+ issues.push(buildDoctorIssue('native-doctor-invalid-json', 'DeepSeek 原生 doctor 输出不是可解析的 JSON', 'DeepSeek native doctor output was not valid JSON'))
277
+ }
278
+
279
+ const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
280
+ return {
281
+ host,
282
+ label: runtime.getHostLabel(host),
283
+ trackedMode,
284
+ detectedMode,
285
+ status,
286
+ checks,
287
+ issues,
288
+ notes,
289
+ nativeDoctor,
290
+ suggestedFix: suggestDoctorFix(host, status, trackedMode),
291
+ }
292
+ }
293
+
222
294
  function parseDoctorArgs(args) {
223
295
  const wantsJson = args.includes('--json')
224
296
  const unknownFlags = args.filter((arg) => arg.startsWith('--') && arg !== '--json' && arg !== '--all')
@@ -239,12 +311,13 @@ function parseDoctorArgs(args) {
239
311
  function inspectDoctorHost(host, settings) {
240
312
  if (host === 'claude') return inspectClaudeDoctor(settings)
241
313
  if (host === 'gemini') return inspectGeminiDoctor(settings)
314
+ if (host === 'deepseek') return inspectDeepseekDoctor(settings)
242
315
  return inspectCodexDoctorImpl(runtime, settings)
243
316
  }
244
317
 
245
318
  function buildDoctorReport(host) {
246
319
  const settings = runtime.readSettings(true)
247
- const hosts = host === 'all' ? ['claude', 'gemini', 'codex'] : [host]
320
+ const hosts = host === 'all' ? ['claude', 'gemini', 'codex', 'deepseek'] : [host]
248
321
  const reports = hosts.map((target) => inspectDoctorHost(target, settings))
249
322
  const summary = reports.reduce((acc, report) => {
250
323
  acc[report.status] = (acc[report.status] || 0) + 1
@@ -7,6 +7,7 @@ import {
7
7
  CODEX_PLUGIN_NAME,
8
8
  } from './cli-codex.mjs'
9
9
  import { getStableRuntimeRoot } from './cli-runtime-root.mjs'
10
+ import { FULL_CARRIER_PROFILE_MARKER } from './cli-utils.mjs'
10
11
  import { safeJson, safeRead } from './cli-utils.mjs'
11
12
 
12
13
  const HOST_ALIASES = new Map([
@@ -18,6 +19,8 @@ const HOST_ALIASES = new Map([
18
19
  ['gemini-cli', 'gemini'],
19
20
  ['codex', 'codex'],
20
21
  ['codex-cli', 'codex'],
22
+ ['deepseek', 'deepseek'],
23
+ ['deepseek-tui', 'deepseek'],
21
24
  ])
22
25
 
23
26
  function hasHelloagentsMarker(filePath) {
@@ -28,6 +31,10 @@ function hasHelloagentsSettings(filePath) {
28
31
  return JSON.stringify(safeJson(filePath) || {}).includes('helloagents')
29
32
  }
30
33
 
34
+ function hasFullCarrierProfile(filePath) {
35
+ return (safeRead(filePath) || '').includes(FULL_CARRIER_PROFILE_MARKER)
36
+ }
37
+
31
38
  function normalizePath(value = '') {
32
39
  return String(value || '').replace(/\\/g, '/').toLowerCase()
33
40
  }
@@ -90,6 +97,26 @@ function detectCodexMode(home) {
90
97
  return ''
91
98
  }
92
99
 
100
+ function detectDeepseekMode(home) {
101
+ const deepseekDir = join(home, '.deepseek')
102
+ const carrierPath = join(deepseekDir, 'AGENTS.md')
103
+ const runtimeRoot = normalizePath(getStableRuntimeRoot(home))
104
+ const homeLinkTarget = safeRealTarget(join(deepseekDir, 'helloagents'))
105
+ if (
106
+ (hasHelloagentsMarker(carrierPath) || (existsSync(join(deepseekDir, 'helloagents')) && homeLinkTarget === runtimeRoot))
107
+ && hasFullCarrierProfile(carrierPath)
108
+ ) {
109
+ return 'global'
110
+ }
111
+ if (
112
+ (existsSync(join(deepseekDir, 'helloagents')) && homeLinkTarget === runtimeRoot)
113
+ || hasHelloagentsMarker(carrierPath)
114
+ ) {
115
+ return 'standby'
116
+ }
117
+ return ''
118
+ }
119
+
93
120
  export function normalizeHost(value = '') {
94
121
  return HOST_ALIASES.get(String(value || '').toLowerCase()) || ''
95
122
  }
@@ -98,6 +125,7 @@ export function getHostLabel(host) {
98
125
  if (host === 'claude') return 'Claude Code'
99
126
  if (host === 'gemini') return 'Gemini CLI'
100
127
  if (host === 'codex') return 'Codex CLI'
128
+ if (host === 'deepseek') return 'DeepSeek TUI'
101
129
  return 'All CLIs'
102
130
  }
103
131
 
@@ -105,5 +133,6 @@ export function detectHostMode(host, runtime) {
105
133
  if (host === 'claude') return detectClaudeMode(runtime.home)
106
134
  if (host === 'gemini') return detectGeminiMode(runtime.home)
107
135
  if (host === 'codex') return detectCodexMode(runtime.home)
136
+ if (host === 'deepseek') return detectDeepseekMode(runtime.home)
108
137
  return ''
109
138
  }
@@ -12,6 +12,7 @@ import {
12
12
  loadHooksWithCliEntry,
13
13
  } from './cli-utils.mjs';
14
14
  import { buildRuntimeCarrier, readCarrierSettings } from './cli-runtime-carrier.mjs';
15
+ import { installDeepseekGlobal, installDeepseekStandby, uninstallDeepseekGlobal, uninstallDeepseekStandby } from './cli-deepseek.mjs'
15
16
 
16
17
  export function installClaudeStandby(home, pkgRoot) {
17
18
  const claudeDir = join(home, '.claude');
@@ -81,3 +82,10 @@ export function uninstallGeminiStandby(home) {
81
82
 
82
83
  return true;
83
84
  }
85
+
86
+ export {
87
+ installDeepseekGlobal,
88
+ installDeepseekStandby,
89
+ uninstallDeepseekGlobal,
90
+ uninstallDeepseekStandby,
91
+ }