dsh-scout 0.8.0

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.
@@ -0,0 +1,108 @@
1
+ # dsh-scout 产品契约
2
+
3
+ 状态:Draft 0.1
4
+ 目标:把公司与岗位尽调收敛为一个可复现、可追溯、能支持下一步行动的 DSH 场景插件。
5
+
6
+ 当前实现进度:首个公开切片已经覆盖 session 隔离的内存 case、封闭来源类型、主体核验、claim 证据历史、保守三态决策、结构化报告、卸载清理和临时 DSH profile 安装门禁;v0.2 已落地持久化五文件导出(`scout_export` / `scout_import`,含可回放事件流 `events.jsonl` 与导入后决策重算);v0.3 已落地可配置存储(`scoutDir` / `autoPersist` 插件配置)与面试问题生成(`scout_questions`);v0.4 已落地信息采集登记(`scout_ingest`:批量登记采集结果并按 URL 自动推断来源类型与证据等级);v0.5 已落地公司/岗位对比(`scout_compare`:2–5 案例并排对比报告与合并面试问题);v0.6 已落地采集→主张流转(`scout_ingest` 支持 claim 草稿,登记来源同时自动添加主张);v0.7 已落地 Provider 深度集成(`scout_search`:接入 DSH Web Provider,搜索结果自动登记为来源)。
7
+
8
+ ## 1. 产品承诺
9
+
10
+ 给定一个公司和一个岗位,`dsh-scout` 不承诺“找到所有信息”,而承诺在证据边界内回答:
11
+
12
+ > 这个机会是否值得进入下一轮?如果值得,面试中必须验证什么?
13
+
14
+ 所有重要结论必须绑定来源、证据等级和下一步动作;无法验证的内容必须保留为 `unknown` 或 `needs_verification`,不能被润色成事实。
15
+
16
+ ## 2. 首个用户流程
17
+
18
+ 输入:
19
+
20
+ - 公司名称或官网
21
+ - 岗位名称或岗位链接
22
+ - 用户目标(默认:是否值得进入下一轮)
23
+
24
+ 流程:
25
+
26
+ 1. 确认公司主体:名称、注册主体、品牌名和地域;存在歧义时暂停。
27
+ 2. 建立九段式调查卡:公司与创始人、产品与技术、市场与竞争、融资与商业化、组织与 HR、风险、岗位匹配、面试反问、待核验事项。
28
+ 3. 采集并归档来源;保存抓取时间、来源类型和原始链接。
29
+ 4. 将事实、推断、用户提供信息和未知项分开。
30
+ 5. 生成三态判断:`PROCEED`、`VERIFY` 或 `STOP`。
31
+ 6. 输出报告、证据清单、风险清单和面试问题。
32
+
33
+ ## 3. 首版输出物
34
+
35
+ 每个 case 生成一个可复制的目录:
36
+
37
+ ```text
38
+ scout-cases/<case-id>/
39
+ case.json
40
+ sources.json
41
+ claims.json
42
+ events.jsonl
43
+ report.md
44
+ ```
45
+
46
+ 首版不要求精确的总分。报告必须包含:
47
+
48
+ - 一句话判断和判断状态
49
+ - 3 个支持判断的关键证据
50
+ - 3 个阻断或待核验风险
51
+ - 岗位可能的真实任务与权限假设
52
+ - 5-10 个面试反问
53
+ - 下一步最小核验动作
54
+
55
+ ## 4. 证据模型
56
+
57
+ 证据等级是来源质量标签,不是事实真伪的自动证明:
58
+
59
+ | 等级 | 含义 |
60
+ | --- | --- |
61
+ | `E0` | 未找到证据或仅为模型猜测 |
62
+ | `E1` | 用户提供、招聘信息或公司自述 |
63
+ | `E2` | 独立媒体、公开数据库或多源交叉信息 |
64
+ | `E3` | 工商、监管、官方文件或可直接复核的一手资料 |
65
+
66
+ 每条 claim 至少包含:`claim_id`、`text`、`status`、`evidence_level`、`source_ids`、`confidence_note` 和 `next_action`。
67
+
68
+ 允许的 `status`:`verified`、`reported`、`inferred`、`contradicted`、`unknown`、`needs_verification`。
69
+
70
+ ## 5. DSH 插件边界
71
+
72
+ 首版作为外部 bundle,优先使用现有工具、子 Agent、任务和文件系统能力:
73
+
74
+ - 工具:启动 case、核验 claim、比较公司、生成面试问题、导出报告。
75
+ - 事件观察:记录工具结果和来源归档,不重写 Agent Loop。
76
+ - 配置:来源目录、报告目录、并发数、超时和输出语言必须是可配置字段。
77
+ - 生命周期:所有 watcher、后台任务、连接和临时文件都必须在卸载时清理。
78
+
79
+ 首版不新增模型可见的持久化事件;如果未来需要让 case 状态进入模型上下文,必须为其设计可回放的 session event。
80
+
81
+ ## 6. 明确非目标
82
+
83
+ - 不重做通用搜索、浏览器或 MCP provider。
84
+ - 不自动发送求职邮件、提交申请或联系第三方。
85
+ - 不把公司融资、估值、专利数量或招聘规模直接转化为成功判断。
86
+ - 不把个人简历、身份信息或求职意图默认发送给第三方服务。
87
+ - 不提供法律、投资或医疗结论。
88
+ - 不在首版实现完整 Web UI、实时数据库或公司评分排行榜。
89
+
90
+ ## 7. 首个验收案例
91
+
92
+ 使用 `docs/fixtures/dsh-scout/snapmaker-hr-head.json` 作为离线 fixture,验证:
93
+
94
+ 1. 公司主体存在未确认时,case 不能直接进入 `PROCEED`。
95
+ 2. 用户历史材料可以作为 `E1/reported`,不能冒充当前独立事实。
96
+ 3. 至少一个 claim 能从 `needs_verification` 转为 `verified`,并保留来源变化。
97
+ 4. 报告能生成面试问题,而不是只输出摘要。
98
+ 5. 没有来源的高影响结论必须显示为未知或待核验。
99
+
100
+ ## 8. 成功标准
101
+
102
+ 首个 MVP 通过的条件:
103
+
104
+ - 一个真实 case 可以从输入运行到报告导出。
105
+ - 每个高影响结论都能追溯到 claim 和 source。
106
+ - 同一 fixture 在无网络模式下可重放并得到结构稳定的结果。
107
+ - 失败时输出缺失证据和下一步动作,而不是编造补全。
108
+ - 插件可以加载、卸载、重载,不遗留 watcher、任务或连接。
@@ -0,0 +1,92 @@
1
+ {
2
+ "schemaVersion": "dsh-scout.case.v0",
3
+ "caseId": "snapmaker-hr-head",
4
+ "title": "Snapmaker HR Head opportunity",
5
+ "language": "zh-CN",
6
+ "decisionObjective": "判断是否值得进入下一轮,并准备面试反问",
7
+ "subject": {
8
+ "name": "Snapmaker",
9
+ "possibleLegalEntity": "深圳快造科技有限公司",
10
+ "identitySourceIds": [],
11
+ "identityStatus": "needs_verification",
12
+ "identityNote": "这是历史调研案例标识,不是当前工商核验结果。运行时必须重新确认主体。"
13
+ },
14
+ "role": {
15
+ "title": "HR Head / HR 负责人",
16
+ "location": "深圳",
17
+ "fitQuestion": "该岗位是否拥有足够的业务授权,值得以 HR 一号位方向继续推进?"
18
+ },
19
+ "expectedDecision": "VERIFY",
20
+ "expectedDecisionReason": "历史材料显示岗位匹配度较高,但公司主体、当前组织规模、授权边界和真实业务优先级必须现场核验。",
21
+ "dimensions": [
22
+ "company_and_founder",
23
+ "product_and_technology",
24
+ "market_and_competition",
25
+ "financing_and_commercialization",
26
+ "organization_and_hr",
27
+ "risks",
28
+ "role_fit",
29
+ "interview_questions",
30
+ "next_verifications"
31
+ ],
32
+ "sources": [
33
+ {
34
+ "sourceId": "historical-snapmaker-research",
35
+ "type": "user_provided_historical_report",
36
+ "title": "Max 既有 Snapmaker 深度调研与面试复盘",
37
+ "url": null,
38
+ "capturedAt": null,
39
+ "evidenceLevel": "E1",
40
+ "status": "historical_only",
41
+ "note": "用于离线 fixture 的方法验证;不能替代运行时实时来源。"
42
+ }
43
+ ],
44
+ "claims": [
45
+ {
46
+ "claimId": "claim-identity",
47
+ "text": "品牌名 Snapmaker 与候选注册主体需要在运行时确认是否对应。",
48
+ "status": "needs_verification",
49
+ "evidenceLevel": "E0",
50
+ "dimension": "identity",
51
+ "impact": "blocking",
52
+ "sourceIds": [],
53
+ "confidenceNote": "历史案例只提供候选主体名称,没有当前工商核验来源。",
54
+ "nextAction": "查询工商主体、法定代表人、注册地和品牌关联信息。"
55
+ },
56
+ {
57
+ "claimId": "claim-role-scope",
58
+ "text": "该岗位可能承担从 0 到 1 搭建 HR 体系的职责。",
59
+ "status": "reported",
60
+ "evidenceLevel": "E1",
61
+ "dimension": "mandate",
62
+ "impact": "blocking",
63
+ "sourceIds": ["historical-snapmaker-research"],
64
+ "confidenceNote": "该判断来自历史调研材料,不能代表岗位当前授权。",
65
+ "nextAction": "面试确认汇报对象、团队现状、预算权限和前三个必须交付的业务结果。"
66
+ },
67
+ {
68
+ "claimId": "claim-fit",
69
+ "text": "Max 的 HRBP、SAP 变革、AI 硬件和 AI Agent 经验与该岗位可能存在较高匹配。",
70
+ "status": "inferred",
71
+ "evidenceLevel": "E1",
72
+ "dimension": "role_fit",
73
+ "impact": "material",
74
+ "sourceIds": ["historical-snapmaker-research"],
75
+ "confidenceNote": "匹配度是基于历史岗位假设的本地推断,尚未由当前招聘方确认。",
76
+ "nextAction": "用岗位真实授权边界和 CEO 对 HR 的期待验证匹配,而不是只比较年限。"
77
+ }
78
+ ],
79
+ "interviewQuestions": [
80
+ "如果 HR 只能在前三个月完成一件事,您希望它是什么?",
81
+ "这个岗位拥有的是执行权、建议权,还是组织变革的共同决策权?",
82
+ "当前最影响业务增长的组织问题是什么?它为什么现在才需要解决?",
83
+ "CEO 对 HR 的期待中,哪些是明确目标,哪些只是临时救火?",
84
+ "这个岗位在 6 个月后被认为成功,具体会看到哪些结果?"
85
+ ],
86
+ "openQuestions": [
87
+ "当前公司主体和品牌关系是否已核验?",
88
+ "岗位是否真实存在,汇报关系和团队规模是否与历史信息一致?",
89
+ "HR 是业务战略角色,还是行政与招聘补位角色?",
90
+ "岗位的预算、团队和决策权限是否足以完成目标?"
91
+ ]
92
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "dsh-scout",
3
+ "version": "0.8.0",
4
+ "description": "Evidence-driven company and job intelligence plugin for DeepSeek Harness.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "cordis.patch.yml",
12
+ "scripts",
13
+ "README.md",
14
+ "LICENSE",
15
+ "docs",
16
+ "tests",
17
+ "README.en.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "npm run build && node --test tests/*.test.mjs",
22
+ "check": "npm test",
23
+ "test:profile": "node scripts/profile-smoke.mjs",
24
+ "check:release": "npm test && npm run test:profile && pnpm pack --dry-run",
25
+ "prepare": "npm run build"
26
+ },
27
+ "engines": {
28
+ "node": ">=22.19.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@deepseek-ai/cordis": ">=4.0.1 <5",
32
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.6 <0.2.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "@deepseek-ai/cordis": {
36
+ "optional": true
37
+ },
38
+ "@deepseek-ai/dsh-tools": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "devDependencies": {
43
+ "@deepseek-ai/cordis": "4.0.1",
44
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.6",
45
+ "typescript": "^5.8.3"
46
+ },
47
+ "dsh": {
48
+ "bundle": {
49
+ "patch": "./cordis.patch.yml"
50
+ }
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/MaxHou-infinity/dsh-scout.git"
55
+ },
56
+ "keywords": [
57
+ "deepseek-harness",
58
+ "dsh-plugin",
59
+ "company-research",
60
+ "job-intelligence",
61
+ "hrbp"
62
+ ]
63
+ }
@@ -0,0 +1,101 @@
1
+ import assert from 'node:assert/strict'
2
+ import { existsSync } from 'node:fs'
3
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { spawnSync } from 'node:child_process'
7
+
8
+ const DSH_VERSION = '0.1.0-rc.6'
9
+ const command = name => process.platform === 'win32' ? `${name}.cmd` : name
10
+ const localDsh = join(process.cwd(), 'node_modules', '.bin', command('dsh'))
11
+ const dshCommand = process.env.DSH_BIN
12
+ ? { program: process.env.DSH_BIN, prefix: [] }
13
+ : existsSync(localDsh)
14
+ ? { program: localDsh, prefix: [] }
15
+ : { program: 'npx', prefix: ['--yes', `@deepseek-ai/dsh@${DSH_VERSION}`] }
16
+
17
+ function run(program, args, options = {}) {
18
+ const result = spawnSync(command(program), args, {
19
+ cwd: process.cwd(),
20
+ env: options.env ?? process.env,
21
+ encoding: 'utf8',
22
+ stdio: options.capture ? 'pipe' : 'inherit',
23
+ })
24
+ if (result.error) throw result.error
25
+ if (result.status !== 0) {
26
+ const details = [result.stdout, result.stderr].filter(Boolean).join('\n').trim()
27
+ throw new Error([
28
+ `${program} ${args.join(' ')} failed with exit code ${result.status}`,
29
+ details,
30
+ ].filter(Boolean).join('\n'))
31
+ }
32
+ return result.stdout ?? ''
33
+ }
34
+
35
+ const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'))
36
+ const gateRoot = await mkdtemp(join(tmpdir(), 'dsh-scout-profile-gate-'))
37
+ const dshHome = join(gateRoot, 'home')
38
+ const tarball = join(gateRoot, `${packageJson.name}-${packageJson.version}.tgz`)
39
+ const verifierPath = join(gateRoot, 'verify-lifecycle.mjs')
40
+ const patchPath = join(gateRoot, 'verify-lifecycle.patch.yml')
41
+ const gateEnv = {
42
+ ...process.env,
43
+ DSH_HOME: dshHome,
44
+ DSH_TELEMETRY_MODE: 'DISABLED',
45
+ }
46
+
47
+ try {
48
+ await writeFile(verifierPath, `
49
+ export const inject = ['tools']
50
+ export async function apply(ctx) {
51
+ await new Promise(resolve => setTimeout(resolve, 25))
52
+ const required = ${JSON.stringify([
53
+ 'scout_start',
54
+ 'scout_add_source',
55
+ 'scout_add_claim',
56
+ 'scout_verify_identity',
57
+ 'scout_verify_claim',
58
+ 'scout_report',
59
+ ])}
60
+ for (const name of required) {
61
+ if (!ctx.tools.get(name)) throw new Error(\`missing dsh-scout tool: \${name}\`)
62
+ }
63
+ if (typeof ctx.appExit !== 'function') throw new Error('missing launcher appExit service')
64
+ process.stdout.write('DSH_SCOUT_LOAD\\n')
65
+ ctx.effect(() => () => process.stdout.write('DSH_SCOUT_UNLOAD\\n'))
66
+ queueMicrotask(() => ctx.appExit(0))
67
+ }
68
+ `)
69
+ await writeFile(patchPath, `- insert:\n - id: dsh-scout-smoke-verifier\n name: ${JSON.stringify(verifierPath)}\n`)
70
+ run('pnpm', ['pack', '--pack-destination', gateRoot])
71
+ run(dshCommand.program, [
72
+ ...dshCommand.prefix,
73
+ 'plugin',
74
+ '--profile',
75
+ 'scout-smoke',
76
+ 'add',
77
+ `file:${tarball}`,
78
+ ], { env: gateEnv })
79
+ const config = run(dshCommand.program, [
80
+ ...dshCommand.prefix,
81
+ '--profile',
82
+ 'scout-smoke',
83
+ '--dump-config',
84
+ ], {
85
+ capture: true,
86
+ env: gateEnv,
87
+ })
88
+ assert.match(config, /# == dsh-scout\n- id: dsh-scout\n name: dsh-scout/)
89
+ const lifecycle = run(dshCommand.program, [
90
+ ...dshCommand.prefix,
91
+ '--profile',
92
+ 'scout-smoke',
93
+ '--patch',
94
+ patchPath,
95
+ ], { capture: true, env: gateEnv })
96
+ assert.match(lifecycle, /DSH_SCOUT_LOAD/)
97
+ assert.match(lifecycle, /DSH_SCOUT_UNLOAD/)
98
+ console.log('dsh-scout profile smoke: PASS')
99
+ } finally {
100
+ await rm(gateRoot, { recursive: true, force: true })
101
+ }