@xdxer/dingtalk-agent 0.1.4-beta.9 → 0.1.4

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 (118) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.en.md +98 -326
  3. package/README.md +95 -673
  4. package/dist/bin/dingtalk-agent.js +200 -18
  5. package/dist/bin/dingtalk-agent.js.map +1 -1
  6. package/dist/src/agent-audit.js +1014 -89
  7. package/dist/src/agent-audit.js.map +1 -1
  8. package/dist/src/agent-enhance.js +41 -8
  9. package/dist/src/agent-enhance.js.map +1 -1
  10. package/dist/src/agent-platform.js +299 -0
  11. package/dist/src/agent-platform.js.map +1 -0
  12. package/dist/src/config.js +1 -7
  13. package/dist/src/config.js.map +1 -1
  14. package/dist/src/development-workspace.js +31 -5
  15. package/dist/src/development-workspace.js.map +1 -1
  16. package/dist/src/doctor.js +74 -15
  17. package/dist/src/doctor.js.map +1 -1
  18. package/dist/src/host-detect.js +146 -0
  19. package/dist/src/host-detect.js.map +1 -0
  20. package/dist/src/instruction-path.js +270 -0
  21. package/dist/src/instruction-path.js.map +1 -0
  22. package/dist/src/map.js +157 -0
  23. package/dist/src/map.js.map +1 -0
  24. package/dist/src/multica-deploy.js +78 -20
  25. package/dist/src/multica-deploy.js.map +1 -1
  26. package/dist/src/multica-provider.js +1 -1
  27. package/dist/src/multica-provider.js.map +1 -1
  28. package/dist/src/opencode-evals.js +710 -225
  29. package/dist/src/opencode-evals.js.map +1 -1
  30. package/dist/src/opencode-isolation.js +124 -0
  31. package/dist/src/opencode-isolation.js.map +1 -0
  32. package/dist/src/opencode-provider.js +1 -0
  33. package/dist/src/opencode-provider.js.map +1 -1
  34. package/dist/src/opencode-workspace.js +21 -10
  35. package/dist/src/opencode-workspace.js.map +1 -1
  36. package/dist/src/remote-state-evals.js +2 -1
  37. package/dist/src/remote-state-evals.js.map +1 -1
  38. package/dist/src/robot-evals.js +2 -1
  39. package/dist/src/robot-evals.js.map +1 -1
  40. package/dist/src/setup.js +6 -5
  41. package/dist/src/setup.js.map +1 -1
  42. package/dist/src/skill-manager.js +141 -12
  43. package/dist/src/skill-manager.js.map +1 -1
  44. package/dist/src/skills.js +2 -1
  45. package/dist/src/skills.js.map +1 -1
  46. package/dist/src/types.js.map +1 -1
  47. package/dist/src/upgrade.js +23 -27
  48. package/dist/src/upgrade.js.map +1 -1
  49. package/dist/src/version.js +73 -0
  50. package/dist/src/version.js.map +1 -0
  51. package/docs/INSTALLATION.md +3 -3
  52. package/docs/assets/agent-delivery-lifecycle.svg +103 -0
  53. package/docs/schemas/agent-platform.schema.json +13 -0
  54. package/docs/schemas/project.schema.json +3 -0
  55. package/docs/schemas/release-readiness.schema.json +2 -1
  56. package/evals/README.md +17 -0
  57. package/lab/README.md +3 -3
  58. package/lab/agent-eval/catalog.json +5 -5
  59. package/lab/agent-eval/classic-failures.json +9 -9
  60. package/lab/agent-eval/completion-gate-regression.json +6 -6
  61. package/lab/agent-eval/remote-state-workspace/opencode.json +1 -1
  62. package/lab/agent-eval/workspace/AGENTS.md +1 -1
  63. package/lab/project-workspace/fake-multica-provider.mjs +17 -6
  64. package/lab/project-workspace/opencode-provider-suite.json +1 -1
  65. package/lab/robot-eval/suite.json +1 -1
  66. package/lab/robot-eval/workspace/AGENTS.md +1 -1
  67. package/lab/schemas/agent-eval-catalog.schema.json +1 -1
  68. package/package.json +13 -12
  69. package/skills/README.md +23 -0
  70. package/skills/core/dingtalk-agent-compose/SKILL.md +151 -0
  71. package/skills/core/dingtalk-agent-compose/assets/AGENTS.template.md +35 -0
  72. package/skills/core/dingtalk-agent-compose/assets/role-skill.template.md +32 -0
  73. package/skills/core/dingtalk-agent-compose/evals/evals.json +129 -0
  74. package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/references/agent-definition-contract.md +3 -3
  75. package/skills/core/dingtalk-agent-compose/references/host-loading-contract.md +58 -0
  76. package/skills/core/dingtalk-agent-compose/references/hosts/claude-code.md +48 -0
  77. package/skills/core/dingtalk-agent-compose/references/hosts/opencode.md +77 -0
  78. package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/SKILL.md +31 -7
  79. package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/assets/eval-catalog.template.json +1 -1
  80. package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/evals/evals.json +22 -0
  81. package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/eval-topology.md +14 -0
  82. package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/evidence-contract.md +21 -0
  83. package/skills/core/dingtalk-agent-eval/references/failure-to-case.md +35 -0
  84. package/skills/core/dingtalk-agent-eval/references/interactive-debug-channels.md +93 -0
  85. package/skills/core/dingtalk-agent-eval/references/local-connector-smoke.md +75 -0
  86. package/skills/core/dingtalk-basic-behavior/SKILL.md +87 -0
  87. package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/memory-and-evolution.md +12 -0
  88. package/skills/core/dingtalk-basic-behavior/references/risk-authority-and-privacy.md +62 -0
  89. package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/task-lifecycle.md +15 -3
  90. package/skills/core/dingtalk-basic-behavior/references/truth-and-recovery.md +65 -0
  91. package/skills/platforms/deap/PLATFORM.md +3 -0
  92. package/skills/platforms/deap/README.md +3 -0
  93. package/skills/platforms/multica-dingtalk/PLATFORM.md +40 -0
  94. package/skills/{dingtalk-agent-boot-multica → platforms/multica-dingtalk/dingtalk-agent-boot-multica}/SKILL.md +4 -4
  95. package/skills/{dingtalk-agent-deploy → platforms/multica-dingtalk/dingtalk-agent-deploy-multica}/SKILL.md +1 -1
  96. package/skills/platforms/multica-dingtalk/multica-external/SKILL.md +282 -0
  97. package/skills/platforms/multica-dingtalk/multica-external/scripts/bootstrap.sh +78 -0
  98. package/skills/platforms/multica-dingtalk/multica-external/scripts/multica_ext.py +1180 -0
  99. package/skills/dingtalk-agent-compose/SKILL.md +0 -110
  100. package/skills/dingtalk-agent-compose/assets/AGENTS.template.md +0 -26
  101. package/skills/dingtalk-agent-compose/assets/role-skill.template.md +0 -24
  102. package/skills/dingtalk-agent-compose/evals/evals.json +0 -94
  103. package/skills/dingtalk-agent-compose/references/opencode-host-contract.md +0 -65
  104. package/skills/dingtalk-basic-behavior/SKILL.md +0 -146
  105. /package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +0 -0
  106. /package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/assets/agent.bindings.local.template.json +0 -0
  107. /package/skills/{dingtalk-agent-compose/assets → core/dingtalk-agent-compose/assets/hosts/opencode}/opencode.template.json +0 -0
  108. /package/skills/{dingtalk-agent-compose → core/dingtalk-agent-compose}/references/storage-routing.md +0 -0
  109. /package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/scenario-taxonomy.md +0 -0
  110. /package/skills/{dingtalk-agent-eval → core/dingtalk-agent-eval}/references/storage-modes.md +0 -0
  111. /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
  112. /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/assets/task-checkpoint.json +0 -0
  113. /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/action-contract.md +0 -0
  114. /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/event-to-behavior.md +0 -0
  115. /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/perception-and-gates.md +0 -0
  116. /package/skills/{dingtalk-basic-behavior → core/dingtalk-basic-behavior}/references/runtime-modes.md +0 -0
  117. /package/skills/{dingtalk-agent-deploy → platforms/multica-dingtalk/dingtalk-agent-deploy-multica}/references/multica-deployment-contract.md +0 -0
  118. /package/skills/{dingtalk-agent-deploy → platforms/multica-dingtalk/dingtalk-agent-deploy-multica}/references/promotion-observation-contract.md +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/doctor.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants, existsSync, readFileSync } from 'node:fs'\nimport { delimiter, dirname, join, resolve } from 'node:path'\nimport { homedir } from 'node:os'\nimport { spawnSync } from 'node:child_process'\n\nimport { skillStatus, type GlobalSkillStatus } from './skill-manager.js'\n\nexport type CheckLevel = 'pass' | 'warn' | 'fail'\n\nexport interface DoctorCheck {\n id: 'node' | 'cli' | 'dws' | 'dws-auth' | 'skill' | 'claude-code' | 'codex' | 'opencode'\n level: CheckLevel\n summary: string\n detail?: string\n fix?: string\n}\n\nexport interface DoctorReport {\n $schema: 'dingtalk-agent/doctor@1'\n ready: boolean\n checks: DoctorCheck[]\n nextSteps: string[]\n skill: GlobalSkillStatus\n}\n\nexport interface DoctorOptions {\n home?: string\n env?: NodeJS.ProcessEnv\n}\n\nexport function doctor(packageRoot: string, options: DoctorOptions = {}): DoctorReport {\n const home = options.home || homedir()\n const env = options.env || process.env\n const checks: DoctorCheck[] = []\n const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))\n const version = String(pkg.version || '')\n\n const nodeVersion = process.versions.node\n checks.push(versionAtLeast(nodeVersion, '18.3.0')\n ? { id: 'node', level: 'pass', summary: `Node.js ${nodeVersion}` }\n : {\n id: 'node', level: 'fail', summary: `Node.js ${nodeVersion} 过旧`,\n fix: '升级到 Node.js 18.3 或更高版本',\n })\n\n const cli = findExecutable('dingtalk-agent', env)\n const alias = findExecutable('dta', env)\n if (cli && alias) {\n const installed = runText(cli, ['--version'], env)\n const aliasRun = runText(alias, ['--version'], env)\n const installedVersion = installed.ok ? firstLine(installed.stdout) : ''\n const aliasVersion = aliasRun.ok ? firstLine(aliasRun.stdout) : ''\n checks.push(installedVersion === version && aliasVersion === version\n ? {\n id: 'cli', level: 'pass', summary: `CLI ${installedVersion} 已在 PATH 中(dta 可用)`,\n detail: `${cli} · ${alias}`,\n }\n : {\n id: 'cli', level: 'warn', summary: 'PATH 中的 CLI 版本与当前包不一致',\n detail: `${cli} (${installedVersion || '无法识别版本'}) · ${alias} (${aliasVersion || '无法识别版本'})`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else if (cli || alias) {\n checks.push({\n id: 'cli', level: 'fail', summary: 'CLI 入口不完整:dingtalk-agent 与 dta 应同时存在',\n detail: `dingtalk-agent=${cli || 'missing'} · dta=${alias || 'missing'}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else {\n checks.push({\n id: 'cli', level: 'fail', summary: 'PATH 找不到 dingtalk-agent',\n detail: `当前 PATH=${env.PATH || ''}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n }\n\n const dws = findExecutable('dws', env)\n if (!dws) {\n checks.push({\n id: 'dws', level: 'fail', summary: 'PATH 找不到 dws',\n fix: '安装 DWS,并确认 `dws version --format json` 可以执行',\n })\n checks.push({\n id: 'dws-auth', level: 'fail', summary: '无法检查 DWS 认证',\n fix: 'DWS 可用后运行 `dws auth login`',\n })\n } else {\n const versionRun = runJson(dws, ['version', '--format', 'json'], env)\n const dwsVersion = versionRun.json?.version || firstLine(versionRun.stdout)\n const supported = versionRun.ok && versionAtLeast(String(dwsVersion || '').replace(/^v/, ''), '1.0.15')\n checks.push(supported\n ? { id: 'dws', level: 'pass', summary: `DWS ${dwsVersion || '可执行'}`, detail: dws }\n : {\n id: 'dws', level: 'fail',\n summary: versionRun.ok ? `DWS ${dwsVersion || ''} 低于最低版本 1.0.15` : 'DWS 存在但无法运行',\n detail: versionRun.ok ? dws : versionRun.error,\n fix: versionRun.ok ? '运行 `dws upgrade`' : '运行 `dws doctor` 检查安装',\n })\n\n const authRun = runJson(dws, ['auth', 'status', '--format', 'json'], env)\n const authenticated = authRun.ok && authRun.json?.authenticated === true &&\n authRun.json?.token_valid !== false\n checks.push(authenticated\n ? {\n id: 'dws-auth', level: 'pass', summary: 'DWS 已认证',\n detail: [authRun.json?.corp_name, authRun.json?.user_name].filter(Boolean).join(' · '),\n }\n : {\n id: 'dws-auth', level: 'fail', summary: 'DWS 尚未认证或凭据已失效',\n detail: authRun.error || 'authenticated=false', fix: '运行 `dws auth login`',\n })\n }\n\n const skill = skillStatus(packageRoot, { home })\n checks.push(skill.canonical.current\n ? {\n id: 'skill', level: 'pass',\n summary: `Basic Behavior Skill ${skill.bundled.skillVersion} 已安装`,\n detail: skill.canonical.path,\n }\n : {\n id: 'skill', level: skill.canonical.exists ? 'warn' : 'fail',\n summary: skill.canonical.exists ? 'Basic Behavior Skill 需要升级或存在本地修改' : 'Basic Behavior Skill 未安装',\n detail: skill.canonical.path,\n fix: skill.canonical.exists\n ? '运行 `dingtalk-agent skill status`,确认后执行 `dingtalk-agent skill upgrade`'\n : '运行 `dingtalk-agent skill install`',\n })\n\n for (const client of skill.clients) {\n checks.push({\n id: client.name,\n level: client.state === 'ok' ? 'pass' : client.state === 'conflict' ? 'fail' : 'warn',\n summary: `${client.label}: ${client.state === 'ok' ? '可发现 Skill' : client.state === 'conflict' ? 'Skill 路径冲突' : '尚不可发现 Skill'}`,\n detail: `${client.discovery} · ${client.path}`,\n fix: client.state === 'ok' ? undefined : '运行 `dingtalk-agent skill install`',\n })\n }\n\n const nextSteps = checks\n .filter((check) => check.level !== 'pass' && check.fix)\n .map((check) => check.fix as string)\n .filter((step, index, all) => all.indexOf(step) === index)\n return {\n $schema: 'dingtalk-agent/doctor@1',\n ready: !checks.some((check) => check.level === 'fail'),\n checks,\n nextSteps,\n skill,\n }\n}\n\nexport function findExecutable(command: string, env: NodeJS.ProcessEnv = process.env): string {\n const path = env.PATH || ''\n const extensions = process.platform === 'win32'\n ? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';')\n : ['']\n for (const directory of path.split(delimiter).filter(Boolean)) {\n for (const extension of extensions) {\n const candidate = resolve(directory, command + extension.toLowerCase())\n try {\n accessSync(candidate, process.platform === 'win32' ? constants.F_OK : constants.X_OK)\n return candidate\n } catch { /* continue */ }\n }\n }\n return ''\n}\n\nfunction runJson(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n const stdout = String(run.stdout || '').trim()\n const stderr = String(run.stderr || '').trim()\n let json: any = null\n try { json = stdout ? JSON.parse(stdout) : null } catch { /* human output is allowed */ }\n return {\n ok: run.status === 0 && !run.error,\n json,\n stdout,\n error: run.error?.message || stderr || (run.signal ? `被 ${run.signal} 终止` : `退出码 ${run.status}`),\n }\n}\n\nfunction runText(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n return {\n ok: run.status === 0 && !run.error,\n stdout: String(run.stdout || '').trim(),\n }\n}\n\nfunction firstLine(text: string): string {\n return text.split(/\\r?\\n/, 1)[0]?.trim() || ''\n}\n\nfunction versionAtLeast(actual: string, minimum: string): boolean {\n const a = actual.split('.').map((part) => Number.parseInt(part, 10) || 0)\n const b = minimum.split('.').map((part) => Number.parseInt(part, 10) || 0)\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n if ((a[i] || 0) > (b[i] || 0)) return true\n if ((a[i] || 0) < (b[i] || 0)) return false\n }\n return true\n}\n"]}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/doctor.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants, existsSync, readFileSync } from 'node:fs'\nimport { delimiter, dirname, join, resolve } from 'node:path'\nimport { homedir } from 'node:os'\nimport { spawnSync } from 'node:child_process'\n\nimport {\n BUNDLED_SKILL_NAMES, GLOBAL_SKILL_NAME, INSTALLABLE_SKILL_NAMES, skillStatus, skillSuiteStatus,\n type GlobalSkillStatus,\n} from './skill-manager.js'\nimport { detectHosts, type HostDetection } from './host-detect.js'\n\nexport type CheckLevel = 'pass' | 'warn' | 'fail'\n\nexport interface DoctorCheck {\n id: 'node' | 'cli' | 'dws' | 'dws-auth' | 'skill' | 'claude-code' | 'codex' | 'opencode'\n level: CheckLevel\n summary: string\n detail?: string\n fix?: string\n}\n\nexport interface DoctorReport {\n $schema: 'dingtalk-agent/doctor@1'\n ready: boolean\n checks: DoctorCheck[]\n nextSteps: string[]\n skill: GlobalSkillStatus\n skills: GlobalSkillStatus[]\n /**\n * 本机 coding agent 观测面:哪些 Host CLI 装了、Skill 对它们是否可见。\n * 它只是选路依据,不参与 `ready` 判定——没装 Claude Code 不等于环境不可用。\n */\n hosts: HostDetection[]\n}\n\nexport interface DoctorOptions {\n home?: string\n env?: NodeJS.ProcessEnv\n}\n\nexport function doctor(packageRoot: string, options: DoctorOptions = {}): DoctorReport {\n const home = options.home || homedir()\n const env = options.env || process.env\n const checks: DoctorCheck[] = []\n const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))\n const version = String(pkg.version || '')\n\n const nodeVersion = process.versions.node\n checks.push(versionAtLeast(nodeVersion, '18.3.0')\n ? { id: 'node', level: 'pass', summary: `Node.js ${nodeVersion}` }\n : {\n id: 'node', level: 'fail', summary: `Node.js ${nodeVersion} 过旧`,\n fix: '升级到 Node.js 18.3 或更高版本',\n })\n\n const cli = findExecutable('dingtalk-agent', env)\n const alias = findExecutable('dta', env)\n if (cli && alias) {\n const installed = runText(cli, ['--version'], env)\n const aliasRun = runText(alias, ['--version'], env)\n const installedVersion = installed.ok ? firstLine(installed.stdout) : ''\n const aliasVersion = aliasRun.ok ? firstLine(aliasRun.stdout) : ''\n checks.push(installedVersion === version && aliasVersion === version\n ? {\n id: 'cli', level: 'pass', summary: `CLI ${installedVersion} 已在 PATH 中(dta 可用)`,\n detail: `${cli} · ${alias}`,\n }\n : {\n id: 'cli', level: 'warn', summary: 'PATH 中的 CLI 版本与当前包不一致',\n detail: `${cli} (${installedVersion || '无法识别版本'}) · ${alias} (${aliasVersion || '无法识别版本'})`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else if (cli || alias) {\n checks.push({\n id: 'cli', level: 'fail', summary: 'CLI 入口不完整:dingtalk-agent 与 dta 应同时存在',\n detail: `dingtalk-agent=${cli || 'missing'} · dta=${alias || 'missing'}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n } else {\n checks.push({\n id: 'cli', level: 'fail', summary: 'PATH 找不到 dingtalk-agent',\n detail: `当前 PATH=${env.PATH || ''}`,\n fix: `运行 npx --yes --registry=https://registry.npmjs.org @xdxer/dingtalk-agent@${version} setup`,\n })\n }\n\n const dws = findExecutable('dws', env)\n if (!dws) {\n checks.push({\n id: 'dws', level: 'fail', summary: 'PATH 找不到 dws',\n fix: '安装 DWS,并确认 `dws version --format json` 可以执行',\n })\n checks.push({\n id: 'dws-auth', level: 'fail', summary: '无法检查 DWS 认证',\n fix: 'DWS 可用后运行 `dws auth login`',\n })\n } else {\n const versionRun = runJson(dws, ['version', '--format', 'json'], env)\n const dwsVersion = versionRun.json?.version || firstLine(versionRun.stdout)\n const supported = versionRun.ok && versionAtLeast(String(dwsVersion || '').replace(/^v/, ''), '1.0.15')\n checks.push(supported\n ? { id: 'dws', level: 'pass', summary: `DWS ${dwsVersion || '可执行'}`, detail: dws }\n : {\n id: 'dws', level: 'fail',\n summary: versionRun.ok ? `DWS ${dwsVersion || ''} 低于最低版本 1.0.15` : 'DWS 存在但无法运行',\n detail: versionRun.ok ? dws : versionRun.error,\n fix: versionRun.ok ? '运行 `dws upgrade`' : '运行 `dws doctor` 检查安装',\n })\n\n const authRun = runJson(dws, ['auth', 'status', '--format', 'json'], env)\n const authenticated = authRun.ok && authRun.json?.authenticated === true &&\n authRun.json?.token_valid !== false\n checks.push(authenticated\n ? {\n id: 'dws-auth', level: 'pass', summary: 'DWS 已认证',\n detail: [authRun.json?.corp_name, authRun.json?.user_name].filter(Boolean).join(' · '),\n }\n : {\n id: 'dws-auth', level: 'fail', summary: 'DWS 尚未认证或凭据已失效',\n detail: authRun.error || 'authenticated=false', fix: '运行 `dws auth login`',\n })\n }\n\n const suite = skillSuiteStatus(packageRoot, { home })\n const skill = suite.skills.find((item) => item.name === GLOBAL_SKILL_NAME) || suite.skills[0]\n const allCurrent = suite.skills.every((item) => item.canonical.current)\n const anyMissing = suite.skills.some((item) => !item.canonical.exists)\n const installedSummary = suite.skills\n .map((item) => `${item.name} ${item.bundled.skillVersion}`).join(' + ')\n const staleSummary = suite.skills\n .filter((item) => !item.canonical.current)\n .map((item) => `${item.name} ${driftPhrase(item)}`).join('; ')\n // 平台 Skill 不属于默认套装:已安装才纳入报告,未安装既不提示也不影响 ready。\n const platformDrift = installedPlatformSkills(packageRoot, home)\n .filter((item) => !item.canonical.current)\n .map((item) => `${item.name} ${driftPhrase(item)}`)\n const detail = [\n suite.skills.map((item) => item.canonical.path).join(' · '),\n platformDrift.length ? `平台 Skill: ${platformDrift.join('; ')}` : '',\n ].filter(Boolean).join(' | ')\n checks.push(allCurrent && !platformDrift.length\n ? {\n id: 'skill', level: 'pass',\n summary: `内置 Skill 已安装: ${installedSummary}`,\n detail,\n }\n : {\n id: 'skill', level: anyMissing ? 'fail' : 'warn',\n summary: allCurrent\n ? `平台 Skill 需要更新: ${platformDrift.join('; ')}`\n : `内置 Skill 不完整: ${staleSummary}`,\n detail,\n fix: anyMissing\n ? '运行 `dingtalk-agent skill install`'\n : '运行 `dingtalk-agent skill status`,确认后执行 `dingtalk-agent skill upgrade`',\n })\n\n // 本机 Host 观测独立于 Skill 可见性:只补 detail,不参与 level,也不参与 ready。\n const hosts = detectHosts({ home, env, skills: [...INSTALLABLE_SKILL_NAMES] })\n for (const client of skill.clients) {\n const states = suite.skills.map((item) => ({\n name: item.name,\n state: item.clients.find((entry) => entry.name === client.name)?.state || 'missing',\n }))\n const level: CheckLevel = states.some((item) => item.state === 'conflict')\n ? 'fail'\n : states.some((item) => item.state === 'missing') ? 'warn' : 'pass'\n const host = hosts.find((item) => item.name === client.name)\n checks.push({\n id: client.name,\n level,\n summary: `${client.label}: ${level === 'pass' ? '可发现内置 Skill' : level === 'fail' ? 'Skill 路径冲突' : '尚不可发现全部内置 Skill'}`,\n detail: [\n `${client.discovery} · ${states.map((item) => `${item.name}=${item.state}`).join('; ')}`,\n `CLI ${host?.cli.found ? host.cli.path : `未安装(${host?.cli.command || client.name})`}`,\n ].join(' | '),\n fix: level === 'pass' ? undefined : '运行 `dingtalk-agent skill install`',\n })\n }\n\n const nextSteps = checks\n .filter((check) => check.level !== 'pass' && check.fix)\n .map((check) => check.fix as string)\n .filter((step, index, all) => all.indexOf(step) === index)\n return {\n $schema: 'dingtalk-agent/doctor@1',\n ready: !checks.some((check) => check.level === 'fail'),\n checks,\n nextSteps,\n skill,\n skills: suite.skills,\n hosts,\n }\n}\n\n/** 把漂移状态渲染成\"本机版本→包内版本(状态)\",让 doctor 输出能直接指出差距。 */\nfunction driftPhrase(item: GlobalSkillStatus): string {\n const installed = item.canonical.installedVersion || '—'\n return item.canonical.state === 'missing'\n ? '未安装(missing)'\n : `${installed}→${item.bundled.skillVersion}(${item.canonical.state})`\n}\n\n/** 已落地的平台 Skill;未安装的不返回,避免把 opt-in 能力报成缺失。 */\nfunction installedPlatformSkills(packageRoot: string, home?: string): GlobalSkillStatus[] {\n const core = new Set<string>(BUNDLED_SKILL_NAMES)\n const result: GlobalSkillStatus[] = []\n for (const name of INSTALLABLE_SKILL_NAMES) {\n if (core.has(name)) continue\n try {\n const status = skillStatus(packageRoot, { home, name })\n if (status.canonical.exists) result.push(status)\n } catch {\n // 平台 Skill 不可用不应让 doctor 失败;核心套装的问题由上面的 skill check 负责。\n }\n }\n return result\n}\n\nexport function findExecutable(command: string, env: NodeJS.ProcessEnv = process.env): string {\n const path = env.PATH || ''\n const extensions = process.platform === 'win32'\n ? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';')\n : ['']\n for (const directory of path.split(delimiter).filter(Boolean)) {\n for (const extension of extensions) {\n const candidate = resolve(directory, command + extension.toLowerCase())\n try {\n accessSync(candidate, process.platform === 'win32' ? constants.F_OK : constants.X_OK)\n return candidate\n } catch { /* continue */ }\n }\n }\n return ''\n}\n\nfunction runJson(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n const stdout = String(run.stdout || '').trim()\n const stderr = String(run.stderr || '').trim()\n let json: any = null\n try { json = stdout ? JSON.parse(stdout) : null } catch { /* human output is allowed */ }\n return {\n ok: run.status === 0 && !run.error,\n json,\n stdout,\n error: run.error?.message || stderr || (run.signal ? `被 ${run.signal} 终止` : `退出码 ${run.status}`),\n }\n}\n\nfunction runText(command: string, args: string[], env: NodeJS.ProcessEnv) {\n const run = spawnSync(command, args, { encoding: 'utf8', env, timeout: 8_000 })\n return {\n ok: run.status === 0 && !run.error,\n stdout: String(run.stdout || '').trim(),\n }\n}\n\nfunction firstLine(text: string): string {\n return text.split(/\\r?\\n/, 1)[0]?.trim() || ''\n}\n\nfunction versionAtLeast(actual: string, minimum: string): boolean {\n const a = actual.split('.').map((part) => Number.parseInt(part, 10) || 0)\n const b = minimum.split('.').map((part) => Number.parseInt(part, 10) || 0)\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n if ((a[i] || 0) > (b[i] || 0)) return true\n if ((a[i] || 0) < (b[i] || 0)) return false\n }\n return true\n}\n"]}
@@ -0,0 +1,146 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { findExecutable } from './doctor.js';
6
+ /** 版本探测的硬上限:Host CLI 卡住不能拖住 doctor。 */
7
+ const VERSION_TIMEOUT_MS = 5_000;
8
+ const HOSTS = [
9
+ {
10
+ name: 'claude-code',
11
+ label: 'Claude Code',
12
+ command: 'claude',
13
+ // Claude Code 读 ~/.claude/skills 下的 symlink,不读 ~/.agents/skills。
14
+ discovery: 'symlink',
15
+ configRoots: (context) => [claudeHome(context)],
16
+ skillRoot: (context) => join(claudeHome(context), 'skills'),
17
+ },
18
+ {
19
+ name: 'codex',
20
+ label: 'Codex',
21
+ command: 'codex',
22
+ // Codex 与 OpenCode 共用 ~/.agents/skills,因此二者的可见性永远同状态。
23
+ discovery: 'shared-agent-skills',
24
+ configRoots: (context) => [
25
+ overrideDir(context, context.env.CODEX_HOME) || join(context.home, '.codex'),
26
+ ],
27
+ skillRoot: (context) => join(context.home, '.agents', 'skills'),
28
+ },
29
+ {
30
+ name: 'opencode',
31
+ label: 'OpenCode',
32
+ command: 'opencode',
33
+ discovery: 'shared-agent-skills',
34
+ configRoots: (context) => [
35
+ join(overrideDir(context, context.env.XDG_CONFIG_HOME) || join(context.home, '.config'), 'opencode'),
36
+ join(context.home, '.opencode'),
37
+ ],
38
+ skillRoot: (context) => join(context.home, '.agents', 'skills'),
39
+ },
40
+ ];
41
+ function claudeHome(context) {
42
+ return overrideDir(context, context.env.CLAUDE_CONFIG_DIR) || join(context.home, '.claude');
43
+ }
44
+ /**
45
+ * 探测本机装了哪些 coding agent CLI,以及内置 Skill 对它们是否可见。
46
+ *
47
+ * 这是只读观测:除 `<cli> --version` 外不执行任何命令,不写盘,也不改变任何 Host 的配置。
48
+ * 它给出选择依据,不替用户选 Host,也不等于该 Host 已经能跑通一次真实 Run。
49
+ */
50
+ export function detectHosts(options = {}) {
51
+ const env = options.env || process.env;
52
+ const home = options.home || env.HOME || homedir();
53
+ // 调用方钉住的 home 与 env 描述的 home 不一致时,env 的目录覆盖属于另一台环境,不予采信。
54
+ const context = {
55
+ home,
56
+ env,
57
+ trustEnvDirs: !options.home || !env.HOME || env.HOME === options.home,
58
+ };
59
+ const probeVersion = options.probeVersion !== false;
60
+ const watched = options.skills?.length ? new Set(options.skills) : null;
61
+ const roots = new Map();
62
+ for (const spec of HOSTS) {
63
+ const root = spec.skillRoot(context);
64
+ roots.set(root, [...(roots.get(root) || []), spec.name]);
65
+ }
66
+ return HOSTS.map((spec) => {
67
+ const root = spec.skillRoot(context);
68
+ const path = safeFind(spec.command, env);
69
+ const present = listSkills(root);
70
+ return {
71
+ name: spec.name,
72
+ label: spec.label,
73
+ cli: {
74
+ command: spec.command,
75
+ found: Boolean(path),
76
+ path,
77
+ version: path && probeVersion ? probeCliVersion(path, env) : '',
78
+ },
79
+ configRoots: spec.configRoots(context).filter((candidate) => isDirectory(candidate)),
80
+ skillExposureRoot: root,
81
+ skillExposure: {
82
+ root,
83
+ exists: isDirectory(root),
84
+ discovery: spec.discovery,
85
+ sharedWith: (roots.get(root) || []).filter((name) => name !== spec.name),
86
+ skillCount: present.length,
87
+ skills: watched ? present.filter((name) => watched.has(name)) : present,
88
+ },
89
+ };
90
+ });
91
+ }
92
+ /**
93
+ * 环境变量覆盖只在 `trustEnvDirs` 成立时生效;空串、纯空白或不存在的目录一律退回默认路径。
94
+ * 覆盖来自另一个 home 上下文时必须失效,否则被钉住的探测会报出调用方的真实配置目录。
95
+ */
96
+ function overrideDir(context, value) {
97
+ if (!context.trustEnvDirs)
98
+ return '';
99
+ const trimmed = String(value || '').trim();
100
+ return trimmed && isDirectory(trimmed) ? trimmed : '';
101
+ }
102
+ /** PATH 为空或不可解析时返回空串;探测不能让 doctor 崩溃。 */
103
+ function safeFind(command, env) {
104
+ try {
105
+ return findExecutable(command, env);
106
+ }
107
+ catch {
108
+ return '';
109
+ }
110
+ }
111
+ /** 超时、非零退出、二进制损坏都记为空版本;只记录,不上报为 Host 缺失。 */
112
+ function probeCliVersion(path, env) {
113
+ try {
114
+ const run = spawnSync(path, ['--version'], {
115
+ encoding: 'utf8', env, timeout: VERSION_TIMEOUT_MS, windowsHide: true,
116
+ });
117
+ if (run.error || run.status !== 0)
118
+ return '';
119
+ return String(run.stdout || '').split(/\r?\n/, 1)[0]?.trim() || '';
120
+ }
121
+ catch {
122
+ return '';
123
+ }
124
+ }
125
+ /** 只有含 SKILL.md 的目录才算一个可被发现的 Skill;散落文件与空目录不计入。 */
126
+ function listSkills(root) {
127
+ try {
128
+ return readdirSync(root, { withFileTypes: true })
129
+ .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
130
+ .map((entry) => entry.name)
131
+ .filter((name) => existsSync(join(root, name, 'SKILL.md')))
132
+ .sort();
133
+ }
134
+ catch {
135
+ return [];
136
+ }
137
+ }
138
+ function isDirectory(path) {
139
+ try {
140
+ return statSync(path).isDirectory();
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ //# sourceMappingURL=host-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-detect.js","sourceRoot":"","sources":["../../src/host-detect.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { spawnSync } from 'node:child_process'\n\nimport { findExecutable } from './doctor.js'\n\nexport type HostName = 'claude-code' | 'codex' | 'opencode'\n\n/** 二进制是否在 PATH 上。这只回答\"能不能启动这个 Host\",不回答它能不能看见 Skill。 */\nexport interface HostCli {\n command: string\n found: boolean\n path: string\n /** `<cli> --version` 的首行;探测失败、超时或不可执行时为空串,不抛错也不降级成 found=false。 */\n version: string\n}\n\n/**\n * Skill 对该 Host 的可见性。与 `cli.found` 是两条独立轨迹:\n * 装了 CLI 不代表 Skill 已暴露,暴露了 Skill 也不代表本机装过 CLI。\n */\nexport interface HostSkillExposure {\n root: string\n exists: boolean\n discovery: 'symlink' | 'shared-agent-skills'\n /** 与该 Host 共用同一个 exposure root 的其它 Host;共用意味着它们的可见性天然同状态。 */\n sharedWith: HostName[]\n /** root 下含 SKILL.md 的目录总数,包含与本项目无关的第三方 Skill。 */\n skillCount: number\n /** 其中命中 `skills` 关注名单的目录名,按字典序;未指定名单时等于全部。 */\n skills: string[]\n}\n\nexport interface HostDetection {\n name: HostName\n label: string\n cli: HostCli\n /** 本机真实存在的配置目录;不存在的候选路径不进入该数组。 */\n configRoots: string[]\n /** `skillExposure.root` 的平铺别名,便于调用方直接引用。 */\n skillExposureRoot: string\n skillExposure: HostSkillExposure\n}\n\nexport interface HostDetectOptions {\n home?: string\n env?: NodeJS.ProcessEnv\n /** 默认 true。置 false 只做文件系统与 PATH 观测,不 spawn 任何子进程。 */\n probeVersion?: boolean\n /** 关注的 Skill 名单;缺省时列出 exposure root 下的全部 Skill。 */\n skills?: string[]\n}\n\n/**\n * 解析出的路径上下文。`env` 只在它确实描述 `home` 这台环境时才参与解析:\n * 调用方钉住一个与 `env.HOME` 不同的 home 时,环境变量属于另一个上下文,必须失效。\n */\ninterface HostContext {\n home: string\n env: NodeJS.ProcessEnv\n /** true 表示可以采信 CLAUDE_CONFIG_DIR / CODEX_HOME / XDG_CONFIG_HOME。 */\n trustEnvDirs: boolean\n}\n\ninterface HostSpec {\n name: HostName\n label: string\n command: string\n discovery: 'symlink' | 'shared-agent-skills'\n configRoots: (context: HostContext) => string[]\n skillRoot: (context: HostContext) => string\n}\n\n/** 版本探测的硬上限:Host CLI 卡住不能拖住 doctor。 */\nconst VERSION_TIMEOUT_MS = 5_000\n\nconst HOSTS: HostSpec[] = [\n {\n name: 'claude-code',\n label: 'Claude Code',\n command: 'claude',\n // Claude Code 读 ~/.claude/skills 下的 symlink,不读 ~/.agents/skills。\n discovery: 'symlink',\n configRoots: (context) => [claudeHome(context)],\n skillRoot: (context) => join(claudeHome(context), 'skills'),\n },\n {\n name: 'codex',\n label: 'Codex',\n command: 'codex',\n // Codex 与 OpenCode 共用 ~/.agents/skills,因此二者的可见性永远同状态。\n discovery: 'shared-agent-skills',\n configRoots: (context) => [\n overrideDir(context, context.env.CODEX_HOME) || join(context.home, '.codex'),\n ],\n skillRoot: (context) => join(context.home, '.agents', 'skills'),\n },\n {\n name: 'opencode',\n label: 'OpenCode',\n command: 'opencode',\n discovery: 'shared-agent-skills',\n configRoots: (context) => [\n join(\n overrideDir(context, context.env.XDG_CONFIG_HOME) || join(context.home, '.config'),\n 'opencode',\n ),\n join(context.home, '.opencode'),\n ],\n skillRoot: (context) => join(context.home, '.agents', 'skills'),\n },\n]\n\nfunction claudeHome(context: HostContext): string {\n return overrideDir(context, context.env.CLAUDE_CONFIG_DIR) || join(context.home, '.claude')\n}\n\n/**\n * 探测本机装了哪些 coding agent CLI,以及内置 Skill 对它们是否可见。\n *\n * 这是只读观测:除 `<cli> --version` 外不执行任何命令,不写盘,也不改变任何 Host 的配置。\n * 它给出选择依据,不替用户选 Host,也不等于该 Host 已经能跑通一次真实 Run。\n */\nexport function detectHosts(options: HostDetectOptions = {}): HostDetection[] {\n const env = options.env || process.env\n const home = options.home || env.HOME || homedir()\n // 调用方钉住的 home 与 env 描述的 home 不一致时,env 的目录覆盖属于另一台环境,不予采信。\n const context: HostContext = {\n home,\n env,\n trustEnvDirs: !options.home || !env.HOME || env.HOME === options.home,\n }\n const probeVersion = options.probeVersion !== false\n const watched = options.skills?.length ? new Set(options.skills) : null\n const roots = new Map<string, HostName[]>()\n for (const spec of HOSTS) {\n const root = spec.skillRoot(context)\n roots.set(root, [...(roots.get(root) || []), spec.name])\n }\n return HOSTS.map((spec) => {\n const root = spec.skillRoot(context)\n const path = safeFind(spec.command, env)\n const present = listSkills(root)\n return {\n name: spec.name,\n label: spec.label,\n cli: {\n command: spec.command,\n found: Boolean(path),\n path,\n version: path && probeVersion ? probeCliVersion(path, env) : '',\n },\n configRoots: spec.configRoots(context).filter((candidate) => isDirectory(candidate)),\n skillExposureRoot: root,\n skillExposure: {\n root,\n exists: isDirectory(root),\n discovery: spec.discovery,\n sharedWith: (roots.get(root) || []).filter((name) => name !== spec.name),\n skillCount: present.length,\n skills: watched ? present.filter((name) => watched.has(name)) : present,\n },\n }\n })\n}\n\n/**\n * 环境变量覆盖只在 `trustEnvDirs` 成立时生效;空串、纯空白或不存在的目录一律退回默认路径。\n * 覆盖来自另一个 home 上下文时必须失效,否则被钉住的探测会报出调用方的真实配置目录。\n */\nfunction overrideDir(context: HostContext, value: string | undefined): string {\n if (!context.trustEnvDirs) return ''\n const trimmed = String(value || '').trim()\n return trimmed && isDirectory(trimmed) ? trimmed : ''\n}\n\n/** PATH 为空或不可解析时返回空串;探测不能让 doctor 崩溃。 */\nfunction safeFind(command: string, env: NodeJS.ProcessEnv): string {\n try {\n return findExecutable(command, env)\n } catch {\n return ''\n }\n}\n\n/** 超时、非零退出、二进制损坏都记为空版本;只记录,不上报为 Host 缺失。 */\nfunction probeCliVersion(path: string, env: NodeJS.ProcessEnv): string {\n try {\n const run = spawnSync(path, ['--version'], {\n encoding: 'utf8', env, timeout: VERSION_TIMEOUT_MS, windowsHide: true,\n })\n if (run.error || run.status !== 0) return ''\n return String(run.stdout || '').split(/\\r?\\n/, 1)[0]?.trim() || ''\n } catch {\n return ''\n }\n}\n\n/** 只有含 SKILL.md 的目录才算一个可被发现的 Skill;散落文件与空目录不计入。 */\nfunction listSkills(root: string): string[] {\n try {\n return readdirSync(root, { withFileTypes: true })\n .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(root, name, 'SKILL.md')))\n .sort()\n } catch {\n return []\n }\n}\n\nfunction isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n"]}
@@ -0,0 +1,270 @@
1
+ import { existsSync, lstatSync, readdirSync, realpathSync, statSync, } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { isAbsolute, join, relative, resolve } from 'node:path';
4
+ export function instructionTargetsPath(workspace, instruction, expected) {
5
+ const raw = instruction.trim();
6
+ if (!raw || !expected.trim())
7
+ return false;
8
+ if (/^https?:\/\//i.test(raw))
9
+ return false;
10
+ const lexicalRoot = resolve(workspace);
11
+ const root = existsSync(lexicalRoot) ? realpathSync(lexicalRoot) : lexicalRoot;
12
+ const target = resolve(root, expected);
13
+ if (instructionUsesGlob(raw)) {
14
+ return globTargetsPath(root, raw, target);
15
+ }
16
+ const expanded = expandHome(raw);
17
+ const candidate = isAbsolute(expanded) ? resolve(expanded) : resolve(root, expanded);
18
+ if (existsSync(candidate) && existsSync(target))
19
+ return sameEntry(candidate, target);
20
+ // For paths that do not exist, only an exact lexical path inside the workspace
21
+ // can be proven equivalent. Existing aliases are compared above across workspace
22
+ // boundaries because OpenCode also accepts absolute and ~/ instructions.
23
+ return isInside(root, candidate) && candidate === target;
24
+ }
25
+ export function instructionUsesGlob(instruction) {
26
+ return /[*?\[\]{}\\]/.test(instruction) || /(?:^!|[@+!]\()/.test(instruction);
27
+ }
28
+ export function countInstructionsForPath(workspace, instructions, expected) {
29
+ return instructions.filter((instruction) => instructionTargetsPath(workspace, instruction, expected)).length;
30
+ }
31
+ function globTargetsPath(root, raw, target) {
32
+ // npm glob/minimatch treats backslash as an escape on POSIX. Avoid treating
33
+ // escaped managed paths as unrelated lexical filenames.
34
+ if (raw.includes('\\'))
35
+ return true;
36
+ let expanded = expandHome(raw).replace(/\\/g, '/');
37
+ // Absolute and home-relative globs can match aliases anywhere on disk. Without
38
+ // traversing an unbounded external tree we cannot prove they exclude a managed
39
+ // instruction, so reject them fail-closed.
40
+ if (raw.startsWith('~/') || isAbsolute(expanded))
41
+ return true;
42
+ // OpenCode/glob accepts cwd-relative `./` forms. Normalize harmless current
43
+ // directory segments before matching; parent traversal is handled fail-closed
44
+ // by compileRelativeGlob because its search boundary is no longer finite.
45
+ expanded = expanded.split('/').filter((segment) => segment !== '.' && segment !== '').join('/');
46
+ const matchers = compileRelativeGlob(expanded);
47
+ if (!matchers)
48
+ return true;
49
+ const targetRelative = slash(relative(root, target));
50
+ if (matchers.some((matcher) => matcher.test(targetRelative)))
51
+ return true;
52
+ if (!existsSync(target))
53
+ return false;
54
+ // A textually unrelated pattern may still load the managed file through a
55
+ // symlink or hardlink. Scan names inside the finite workspace without following
56
+ // directory links and compare every matching entry by realpath/inode.
57
+ const targetReal = realpathSync(target);
58
+ const pending = [root];
59
+ while (pending.length) {
60
+ const directory = pending.pop();
61
+ let entries;
62
+ try {
63
+ entries = readdirSync(directory, { withFileTypes: true });
64
+ }
65
+ catch {
66
+ return true;
67
+ }
68
+ for (const entry of entries) {
69
+ const path = join(directory, entry.name);
70
+ const rel = slash(relative(root, path));
71
+ let info;
72
+ try {
73
+ info = lstatSync(path);
74
+ }
75
+ catch {
76
+ return true;
77
+ }
78
+ if (info.isSymbolicLink()) {
79
+ let linked = '';
80
+ try {
81
+ linked = realpathSync(path);
82
+ }
83
+ catch {
84
+ return true;
85
+ }
86
+ if (matchers.some((matcher) => matcher.test(rel)) && sameEntry(path, target))
87
+ return true;
88
+ try {
89
+ const linkedInfo = statSync(path);
90
+ if (linkedInfo.isDirectory()) {
91
+ // npm glob may traverse a symlink when that directory is named by an
92
+ // explicit pattern segment. Its external descendants are unbounded,
93
+ // so any potentially traversable directory link is fail-closed.
94
+ if (globMayTraverseDirectory(expanded, rel))
95
+ return true;
96
+ if (isInside(linked, targetReal)) {
97
+ const alias = slash(join(rel, relative(linked, targetReal)));
98
+ if (matchers.some((matcher) => matcher.test(alias)))
99
+ return true;
100
+ }
101
+ }
102
+ }
103
+ catch {
104
+ return true;
105
+ }
106
+ continue;
107
+ }
108
+ if (info.isDirectory()) {
109
+ pending.push(path);
110
+ continue;
111
+ }
112
+ if (info.isFile() && matchers.some((matcher) => matcher.test(rel)) &&
113
+ sameEntry(path, target))
114
+ return true;
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+ function globMayTraverseDirectory(pattern, directory) {
120
+ const magic = pattern.search(/[*?\[\]{}()]/);
121
+ const literal = magic < 0 ? pattern : pattern.slice(0, magic);
122
+ const prefix = literal.endsWith('/')
123
+ ? literal.slice(0, -1)
124
+ : literal.includes('/') ? literal.slice(0, literal.lastIndexOf('/')) : '';
125
+ if (!prefix)
126
+ return true;
127
+ return directory === prefix || directory.startsWith(`${prefix}/`) ||
128
+ prefix.startsWith(`${directory}/`);
129
+ }
130
+ function compileRelativeGlob(pattern) {
131
+ // Minimatch supports POSIX/nested character classes (for example
132
+ // `[[:punct:]]`) that the small deterministic compiler below does not.
133
+ // Treat any nested class syntax as unknown and therefore targetable.
134
+ if (/\[[^\]]*\[/.test(pattern))
135
+ return null;
136
+ const expanded = expandSimpleAlternatives(pattern);
137
+ if (!expanded || expanded.length > 64)
138
+ return null;
139
+ if (expanded.some((item) => item.split('/').includes('..')))
140
+ return null;
141
+ const flags = ['darwin', 'win32'].includes(process.platform) ? 'i' : '';
142
+ try {
143
+ return expanded.map((item) => new RegExp(`^${globBody(item)}$`, flags));
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }
149
+ function expandSimpleAlternatives(pattern) {
150
+ const special = findAlternative(pattern);
151
+ if (!special) {
152
+ // Negation and repetition extglobs need full minimatch semantics; fail closed.
153
+ if (/(?:^!|[?+*!]\()/.test(pattern))
154
+ return null;
155
+ return [pattern];
156
+ }
157
+ const alternatives = splitAlternatives(special.body, special.separator);
158
+ // Only comma alternatives are implemented for braces. Numeric/alphabetic ranges
159
+ // and empty branches have minimatch-specific semantics, so reject them rather
160
+ // than incorrectly proving that they cannot target a managed instruction.
161
+ if (!alternatives.length || alternatives.some((item) => item.length === 0) ||
162
+ (special.separator === ',' && alternatives.length < 2))
163
+ return null;
164
+ const results = [];
165
+ for (const alternative of alternatives) {
166
+ const nested = expandSimpleAlternatives(pattern.slice(0, special.start) + alternative + pattern.slice(special.end + 1));
167
+ if (!nested)
168
+ return null;
169
+ results.push(...nested);
170
+ }
171
+ return results;
172
+ }
173
+ function findAlternative(pattern) {
174
+ for (let index = 0; index < pattern.length; index++) {
175
+ const char = pattern[index];
176
+ const extglob = char === '@' && pattern[index + 1] === '(';
177
+ if (char !== '{' && !extglob)
178
+ continue;
179
+ const open = extglob ? '(' : '{';
180
+ const close = extglob ? ')' : '}';
181
+ const bodyStart = index + (extglob ? 2 : 1);
182
+ let depth = 1;
183
+ for (let cursor = bodyStart; cursor < pattern.length; cursor++) {
184
+ if (pattern[cursor] === open)
185
+ depth++;
186
+ else if (pattern[cursor] === close && --depth === 0) {
187
+ return {
188
+ start: index, end: cursor, body: pattern.slice(bodyStart, cursor),
189
+ separator: extglob ? '|' : ',',
190
+ };
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ return null;
196
+ }
197
+ function splitAlternatives(body, separator) {
198
+ const values = [];
199
+ let start = 0;
200
+ let depth = 0;
201
+ for (let index = 0; index < body.length; index++) {
202
+ if ('({['.includes(body[index]))
203
+ depth++;
204
+ else if (')}]'.includes(body[index]))
205
+ depth--;
206
+ else if (body[index] === separator && depth === 0) {
207
+ values.push(body.slice(start, index));
208
+ start = index + 1;
209
+ }
210
+ }
211
+ values.push(body.slice(start));
212
+ return values;
213
+ }
214
+ function globBody(pattern) {
215
+ let result = '';
216
+ for (let index = 0; index < pattern.length; index++) {
217
+ const char = pattern[index];
218
+ if (char === '*') {
219
+ if (pattern[index + 1] === '*') {
220
+ index++;
221
+ if (pattern[index + 1] === '/') {
222
+ index++;
223
+ result += '(?:.*/)?';
224
+ }
225
+ else
226
+ result += '.*';
227
+ }
228
+ else
229
+ result += '[^/]*';
230
+ }
231
+ else if (char === '?')
232
+ result += '[^/]';
233
+ else if (char === '[') {
234
+ const end = pattern.indexOf(']', index + 1);
235
+ if (end < 0)
236
+ throw new Error('unterminated character class');
237
+ const body = pattern.slice(index + 1, end);
238
+ result += `[${body.startsWith('!') ? '^' + body.slice(1) : body}]`;
239
+ index = end;
240
+ }
241
+ else {
242
+ result += char.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ function expandHome(path) {
248
+ return path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
249
+ }
250
+ function sameEntry(left, right) {
251
+ try {
252
+ if (realpathSync(left) === realpathSync(right))
253
+ return true;
254
+ const leftStat = statSync(left);
255
+ const rightStat = statSync(right);
256
+ return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
257
+ }
258
+ catch {
259
+ return false;
260
+ }
261
+ }
262
+ function slash(value) {
263
+ return value.replace(/\\/g, '/');
264
+ }
265
+ function isInside(root, candidate) {
266
+ const rel = relative(root, candidate);
267
+ const separator = process.platform === 'win32' ? '\\' : '/';
268
+ return rel === '' || (rel !== '..' && !rel.startsWith(`..${separator}`));
269
+ }
270
+ //# sourceMappingURL=instruction-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instruction-path.js","sourceRoot":"","sources":["../../src/instruction-path.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n existsSync, lstatSync, readdirSync, realpathSync, statSync,\n} from 'node:fs'\nimport type { Dirent } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { isAbsolute, join, relative, resolve } from 'node:path'\n\nexport function instructionTargetsPath(\n workspace: string, instruction: string, expected: string,\n): boolean {\n const raw = instruction.trim()\n if (!raw || !expected.trim()) return false\n if (/^https?:\\/\\//i.test(raw)) return false\n const lexicalRoot = resolve(workspace)\n const root = existsSync(lexicalRoot) ? realpathSync(lexicalRoot) : lexicalRoot\n const target = resolve(root, expected)\n\n if (instructionUsesGlob(raw)) {\n return globTargetsPath(root, raw, target)\n }\n\n const expanded = expandHome(raw)\n const candidate = isAbsolute(expanded) ? resolve(expanded) : resolve(root, expanded)\n if (existsSync(candidate) && existsSync(target)) return sameEntry(candidate, target)\n\n // For paths that do not exist, only an exact lexical path inside the workspace\n // can be proven equivalent. Existing aliases are compared above across workspace\n // boundaries because OpenCode also accepts absolute and ~/ instructions.\n return isInside(root, candidate) && candidate === target\n}\n\nexport function instructionUsesGlob(instruction: string): boolean {\n return /[*?\\[\\]{}\\\\]/.test(instruction) || /(?:^!|[@+!]\\()/.test(instruction)\n}\n\nexport function countInstructionsForPath(\n workspace: string, instructions: string[], expected: string,\n): number {\n return instructions.filter((instruction) =>\n instructionTargetsPath(workspace, instruction, expected)).length\n}\n\nfunction globTargetsPath(root: string, raw: string, target: string): boolean {\n // npm glob/minimatch treats backslash as an escape on POSIX. Avoid treating\n // escaped managed paths as unrelated lexical filenames.\n if (raw.includes('\\\\')) return true\n let expanded = expandHome(raw).replace(/\\\\/g, '/')\n // Absolute and home-relative globs can match aliases anywhere on disk. Without\n // traversing an unbounded external tree we cannot prove they exclude a managed\n // instruction, so reject them fail-closed.\n if (raw.startsWith('~/') || isAbsolute(expanded)) return true\n // OpenCode/glob accepts cwd-relative `./` forms. Normalize harmless current\n // directory segments before matching; parent traversal is handled fail-closed\n // by compileRelativeGlob because its search boundary is no longer finite.\n expanded = expanded.split('/').filter((segment) => segment !== '.' && segment !== '').join('/')\n const matchers = compileRelativeGlob(expanded)\n if (!matchers) return true\n const targetRelative = slash(relative(root, target))\n if (matchers.some((matcher) => matcher.test(targetRelative))) return true\n if (!existsSync(target)) return false\n\n // A textually unrelated pattern may still load the managed file through a\n // symlink or hardlink. Scan names inside the finite workspace without following\n // directory links and compare every matching entry by realpath/inode.\n const targetReal = realpathSync(target)\n const pending = [root]\n while (pending.length) {\n const directory = pending.pop()!\n let entries: Dirent[]\n try { entries = readdirSync(directory, { withFileTypes: true }) }\n catch { return true }\n for (const entry of entries) {\n const path = join(directory, entry.name)\n const rel = slash(relative(root, path))\n let info: ReturnType<typeof lstatSync>\n try { info = lstatSync(path) } catch { return true }\n if (info.isSymbolicLink()) {\n let linked = ''\n try { linked = realpathSync(path) } catch { return true }\n if (matchers.some((matcher) => matcher.test(rel)) && sameEntry(path, target)) return true\n try {\n const linkedInfo = statSync(path)\n if (linkedInfo.isDirectory()) {\n // npm glob may traverse a symlink when that directory is named by an\n // explicit pattern segment. Its external descendants are unbounded,\n // so any potentially traversable directory link is fail-closed.\n if (globMayTraverseDirectory(expanded, rel)) return true\n if (isInside(linked, targetReal)) {\n const alias = slash(join(rel, relative(linked, targetReal)))\n if (matchers.some((matcher) => matcher.test(alias))) return true\n }\n }\n } catch { return true }\n continue\n }\n if (info.isDirectory()) {\n pending.push(path)\n continue\n }\n if (info.isFile() && matchers.some((matcher) => matcher.test(rel)) &&\n sameEntry(path, target)) return true\n }\n }\n return false\n}\n\nfunction globMayTraverseDirectory(pattern: string, directory: string): boolean {\n const magic = pattern.search(/[*?\\[\\]{}()]/)\n const literal = magic < 0 ? pattern : pattern.slice(0, magic)\n const prefix = literal.endsWith('/')\n ? literal.slice(0, -1)\n : literal.includes('/') ? literal.slice(0, literal.lastIndexOf('/')) : ''\n if (!prefix) return true\n return directory === prefix || directory.startsWith(`${prefix}/`) ||\n prefix.startsWith(`${directory}/`)\n}\n\nfunction compileRelativeGlob(pattern: string): RegExp[] | null {\n // Minimatch supports POSIX/nested character classes (for example\n // `[[:punct:]]`) that the small deterministic compiler below does not.\n // Treat any nested class syntax as unknown and therefore targetable.\n if (/\\[[^\\]]*\\[/.test(pattern)) return null\n const expanded = expandSimpleAlternatives(pattern)\n if (!expanded || expanded.length > 64) return null\n if (expanded.some((item) => item.split('/').includes('..'))) return null\n const flags = ['darwin', 'win32'].includes(process.platform) ? 'i' : ''\n try { return expanded.map((item) => new RegExp(`^${globBody(item)}$`, flags)) }\n catch { return null }\n}\n\nfunction expandSimpleAlternatives(pattern: string): string[] | null {\n const special = findAlternative(pattern)\n if (!special) {\n // Negation and repetition extglobs need full minimatch semantics; fail closed.\n if (/(?:^!|[?+*!]\\()/.test(pattern)) return null\n return [pattern]\n }\n const alternatives = splitAlternatives(special.body, special.separator)\n // Only comma alternatives are implemented for braces. Numeric/alphabetic ranges\n // and empty branches have minimatch-specific semantics, so reject them rather\n // than incorrectly proving that they cannot target a managed instruction.\n if (!alternatives.length || alternatives.some((item) => item.length === 0) ||\n (special.separator === ',' && alternatives.length < 2)) return null\n const results: string[] = []\n for (const alternative of alternatives) {\n const nested = expandSimpleAlternatives(\n pattern.slice(0, special.start) + alternative + pattern.slice(special.end + 1))\n if (!nested) return null\n results.push(...nested)\n }\n return results\n}\n\nfunction findAlternative(pattern: string): {\n start: number; end: number; body: string; separator: ',' | '|'\n} | null {\n for (let index = 0; index < pattern.length; index++) {\n const char = pattern[index]\n const extglob = char === '@' && pattern[index + 1] === '('\n if (char !== '{' && !extglob) continue\n const open = extglob ? '(' : '{'\n const close = extglob ? ')' : '}'\n const bodyStart = index + (extglob ? 2 : 1)\n let depth = 1\n for (let cursor = bodyStart; cursor < pattern.length; cursor++) {\n if (pattern[cursor] === open) depth++\n else if (pattern[cursor] === close && --depth === 0) {\n return {\n start: index, end: cursor, body: pattern.slice(bodyStart, cursor),\n separator: extglob ? '|' : ',',\n }\n }\n }\n return null\n }\n return null\n}\n\nfunction splitAlternatives(body: string, separator: ',' | '|'): string[] {\n const values: string[] = []\n let start = 0\n let depth = 0\n for (let index = 0; index < body.length; index++) {\n if ('({['.includes(body[index])) depth++\n else if (')}]'.includes(body[index])) depth--\n else if (body[index] === separator && depth === 0) {\n values.push(body.slice(start, index))\n start = index + 1\n }\n }\n values.push(body.slice(start))\n return values\n}\n\nfunction globBody(pattern: string): string {\n let result = ''\n for (let index = 0; index < pattern.length; index++) {\n const char = pattern[index]\n if (char === '*') {\n if (pattern[index + 1] === '*') {\n index++\n if (pattern[index + 1] === '/') {\n index++\n result += '(?:.*/)?'\n } else result += '.*'\n } else result += '[^/]*'\n } else if (char === '?') result += '[^/]'\n else if (char === '[') {\n const end = pattern.indexOf(']', index + 1)\n if (end < 0) throw new Error('unterminated character class')\n const body = pattern.slice(index + 1, end)\n result += `[${body.startsWith('!') ? '^' + body.slice(1) : body}]`\n index = end\n } else {\n result += char.replace(/[|\\\\{}()[\\]^$+?.]/g, '\\\\$&')\n }\n }\n return result\n}\n\nfunction expandHome(path: string): string {\n return path.startsWith('~/') ? join(homedir(), path.slice(2)) : path\n}\n\nfunction sameEntry(left: string, right: string): boolean {\n try {\n if (realpathSync(left) === realpathSync(right)) return true\n const leftStat = statSync(left)\n const rightStat = statSync(right)\n return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino\n } catch { return false }\n}\n\nfunction slash(value: string): string {\n return value.replace(/\\\\/g, '/')\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n const separator = process.platform === 'win32' ? '\\\\' : '/'\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${separator}`))\n}\n"]}