deepseek-foreman 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/lib/index.d.ts +13 -0
- package/lib/index.js +70 -0
- package/package.json +6 -2
- package/scripts/setup.mjs +32 -0
- package/skill/deepseek-foreman/SKILL.md +20 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)。
|
|
10
10
|
|
|
11
|
-
**小白三步**(没碰过 cordis / YAML / CLI
|
|
11
|
+
**小白三步**(没碰过 cordis / YAML / CLI 也能上手,全程有 setup 向导兜底):
|
|
12
12
|
|
|
13
13
|
1. 装 dsh,配好 ≥2 家厂商的模型;
|
|
14
14
|
2. 插件页点装 `deepseek-foreman`;
|
|
@@ -184,6 +184,14 @@ bundle 的 `cordis.patch.yml` 里**新增插件行要包在 `insert:` 下**:
|
|
|
184
184
|
|
|
185
185
|
成本台账随每张回执记录(施工 token/effort/wall-clock),`_receipts/progress.md` 周汇总;无人托管有预算闸(到限即停)。分工逻辑与实测数据见 [可量化测试数据](docs/metrics-2026-10-01.md)。
|
|
186
186
|
|
|
187
|
+
## 首次配置向导(R15)
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npx deepseek-foreman-setup # 核对白名单、指路角色表、给试跑话术
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
故障自助见 [故障速查](docs/install-flow.md#故障速查10-条与-doctor-hints-对应)(10 条,与 doctor hints 对应)。
|
|
194
|
+
|
|
187
195
|
## 状态
|
|
188
196
|
|
|
189
197
|
已装进 dsh 桌面版 0.2.0-rc.2(Intel iMac)并 live 验证:冷启动正常,`pick_route` 可被模型调用,视觉约束会真的拦截并给 fallback;`subagent_readonly` 最终态同样 live 实测通过(只读工具集生效、委派工具被拦死)。
|
package/lib/index.d.ts
CHANGED
|
@@ -43,6 +43,19 @@ export interface Config {
|
|
|
43
43
|
}
|
|
44
44
|
/** Loader schema for the role table. */
|
|
45
45
|
export declare const Config: z<Config>;
|
|
46
|
+
/** R5 mechanical acceptance verdict as the model sees it. */
|
|
47
|
+
export interface AcceptCheck {
|
|
48
|
+
receipt: string;
|
|
49
|
+
ticket?: string;
|
|
50
|
+
headSha: string;
|
|
51
|
+
headMatches: boolean;
|
|
52
|
+
recordedHead?: string;
|
|
53
|
+
checks: {
|
|
54
|
+
name: string;
|
|
55
|
+
status: 'pass' | 'fail' | 'missing' | 'stale';
|
|
56
|
+
}[];
|
|
57
|
+
ready: boolean;
|
|
58
|
+
}
|
|
46
59
|
/** One resolved role route as the model sees it. */
|
|
47
60
|
export interface Route {
|
|
48
61
|
role: string;
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Route adjudication for ticket dispatch: role -> provider/model, with hard constraints. */
|
|
2
2
|
import { constants, copyFileSync, cpSync, lstatSync, mkdirSync, readFileSync, readdirSync, statSync, symlinkSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
3
4
|
import { homedir } from 'node:os';
|
|
4
5
|
import { basename, dirname, join } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -125,6 +126,27 @@ const SETUP_SCHEMA = {
|
|
|
125
126
|
hints: { type: 'array', required: true, items: { type: 'string' } },
|
|
126
127
|
},
|
|
127
128
|
};
|
|
129
|
+
/** Accept-check (R5 fingerprint) block: receipt vs code version, mechanically. */
|
|
130
|
+
const ACCEPT_CHECK_SCHEMA = {
|
|
131
|
+
type: 'object',
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
properties: {
|
|
134
|
+
receipt: { type: 'string', required: true },
|
|
135
|
+
ticket: { type: 'string' },
|
|
136
|
+
headSha: { type: 'string', required: true },
|
|
137
|
+
headMatches: { type: 'boolean', required: true },
|
|
138
|
+
recordedHead: { type: 'string' },
|
|
139
|
+
checks: {
|
|
140
|
+
type: 'array', required: true, items: {
|
|
141
|
+
type: 'object', additionalProperties: false, properties: {
|
|
142
|
+
name: { type: 'string', required: true },
|
|
143
|
+
status: { type: 'string', enum: ['pass', 'fail', 'missing', 'stale'], required: true },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
ready: { type: 'boolean', required: true },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
128
150
|
const DECISION_SCHEMA = {
|
|
129
151
|
type: 'object',
|
|
130
152
|
additionalProperties: false,
|
|
@@ -134,6 +156,7 @@ const DECISION_SCHEMA = {
|
|
|
134
156
|
route: ROUTE_SCHEMA,
|
|
135
157
|
alternatives: { type: 'array', required: true, items: ROUTE_SCHEMA },
|
|
136
158
|
setup: SETUP_SCHEMA,
|
|
159
|
+
acceptCheck: ACCEPT_CHECK_SCHEMA,
|
|
137
160
|
},
|
|
138
161
|
};
|
|
139
162
|
/** Long-output threshold below which a capped route is refused. */
|
|
@@ -501,6 +524,8 @@ export function apply(ctx, config) {
|
|
|
501
524
|
needs_vision: { type: 'boolean', description: 'The work reads images or screenshots.' },
|
|
502
525
|
needs_long_output: { type: 'boolean', description: 'The work must produce a large single output (whole document, big file).' },
|
|
503
526
|
review_for: { type: 'string', description: 'When this dispatch is a code review, name the role that wrote the code. Same-vendor reviewers are refused: correlated models make correlated mistakes.' },
|
|
527
|
+
accept_check: { type: 'string', description: 'R5 mechanical acceptance: pass an absolute receipt path (the receipt must contain a 「指纹」 section with HEAD sha and per-check status). Verifies the receipt matches the current git HEAD and every acceptance command passed. Returns acceptCheck instead of a route.' },
|
|
528
|
+
project_root: { type: 'string', description: 'Git working directory for accept_check (defaults to the repo containing the receipt).' },
|
|
504
529
|
},
|
|
505
530
|
output: {
|
|
506
531
|
schema: DECISION_SCHEMA,
|
|
@@ -508,6 +533,10 @@ export function apply(ctx, config) {
|
|
|
508
533
|
},
|
|
509
534
|
async execute(args) {
|
|
510
535
|
refresh();
|
|
536
|
+
// R5: accept_check is a mode switch, not a route query.
|
|
537
|
+
if (args.accept_check !== undefined && args.accept_check !== '') {
|
|
538
|
+
return { ok: false, reason: 'accept-check mode', alternatives: [], acceptCheck: runAcceptCheck(args.accept_check, args.project_root) };
|
|
539
|
+
}
|
|
511
540
|
const now = new Date();
|
|
512
541
|
const all = () => roles.map(toRoute);
|
|
513
542
|
// Omitting the role is the doctor call: the answer carries `setup` even when the table
|
|
@@ -547,6 +576,47 @@ export function apply(ctx, config) {
|
|
|
547
576
|
},
|
|
548
577
|
}));
|
|
549
578
|
}
|
|
579
|
+
/** R5: mechanically verify a receipt's fingerprint block against the current git HEAD. */
|
|
580
|
+
function runAcceptCheck(receiptPath, projectRoot) {
|
|
581
|
+
const receipt = (() => {
|
|
582
|
+
try {
|
|
583
|
+
return readFileSync(receiptPath, 'utf8');
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
return '';
|
|
587
|
+
}
|
|
588
|
+
})();
|
|
589
|
+
const root = projectRoot ?? dirname(receiptPath);
|
|
590
|
+
const headSha = (() => {
|
|
591
|
+
try {
|
|
592
|
+
return execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
return '';
|
|
596
|
+
}
|
|
597
|
+
})();
|
|
598
|
+
const recordedHead = /HEAD sha[::]\s*`?([0-9a-f]{7,40})`?/.exec(receipt)?.[1] ?? '';
|
|
599
|
+
const ticket = /工单[::]\s*`?([^`\n]+)`?/.exec(receipt)?.[1]?.trim();
|
|
600
|
+
// Fingerprint block: each acceptance line as `- [<name>]: <status>` where status ∈ pass/fail/missing/stale.
|
|
601
|
+
const checks = [];
|
|
602
|
+
for (const line of receipt.split('\n')) {
|
|
603
|
+
const m = /^- \[([^\]]+)\]:\s*(pass|fail|missing|stale)\s*$/.exec(line.trim());
|
|
604
|
+
if (m !== null)
|
|
605
|
+
checks.push({ name: m[1], status: m[2] });
|
|
606
|
+
}
|
|
607
|
+
const headMatches = recordedHead !== '' && recordedHead === headSha.slice(0, recordedHead.length);
|
|
608
|
+
const ready = receipt !== '' && headSha !== '' && headMatches
|
|
609
|
+
&& checks.length > 0 && checks.every(c => c.status === 'pass');
|
|
610
|
+
return {
|
|
611
|
+
receipt: receiptPath,
|
|
612
|
+
...(ticket === undefined ? {} : { ticket }),
|
|
613
|
+
headSha,
|
|
614
|
+
headMatches,
|
|
615
|
+
...(recordedHead === '' ? {} : { recordedHead }),
|
|
616
|
+
checks,
|
|
617
|
+
ready,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
550
620
|
/** mtime of a file, or undefined when it cannot be stat'ed at all. */
|
|
551
621
|
function mtimeOf(file) {
|
|
552
622
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-foreman",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Foreman for DeepSeek Harness: your best model leads, cheaper models build, a rival vendor reviews. Role-to-route adjudication with peak-window, vision, output-size and cross-vendor-review constraints.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"cordis.patch.yml",
|
|
20
20
|
"roles.example.yml",
|
|
21
21
|
"skill/",
|
|
22
|
-
"persona.example.md"
|
|
22
|
+
"persona.example.md",
|
|
23
|
+
"scripts/setup.mjs"
|
|
23
24
|
],
|
|
24
25
|
"scripts": {
|
|
25
26
|
"build": "tsc -p tsconfig.json"
|
|
@@ -47,5 +48,8 @@
|
|
|
47
48
|
"bundle": {
|
|
48
49
|
"patch": "./cordis.patch.yml"
|
|
49
50
|
}
|
|
51
|
+
},
|
|
52
|
+
"bin": {
|
|
53
|
+
"deepseek-foreman-setup": "scripts/setup.mjs"
|
|
50
54
|
}
|
|
51
55
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// deepseek-foreman setup 向导(R15):核对白名单配置 + 指路角色表 + 给试单话术。
|
|
3
|
+
// 用法:node node_modules/deepseek-foreman/scripts/setup.mjs 或 npx deepseek-foreman-setup
|
|
4
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
|
5
|
+
import { homedir } from 'node:os'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { parse as parseYaml } from 'yaml'
|
|
8
|
+
|
|
9
|
+
const home = homedir()
|
|
10
|
+
const profilesDir = join(home, '.dsh', 'profiles')
|
|
11
|
+
const rows = []
|
|
12
|
+
if (existsSync(profilesDir)) {
|
|
13
|
+
for (const name of readdirSync(profilesDir)) {
|
|
14
|
+
const file = join(profilesDir, name, 'cordis.patch.yml')
|
|
15
|
+
if (!existsSync(file)) continue
|
|
16
|
+
let found = false
|
|
17
|
+
try {
|
|
18
|
+
const doc = parseYaml(readFileSync(file, 'utf8'))
|
|
19
|
+
for (const entry of Array.isArray(doc) ? doc : []) {
|
|
20
|
+
if (entry?.config?.allowedModels !== undefined) { found = true; break }
|
|
21
|
+
if (entry?.insert) for (const sub of entry.insert) if (sub?.config?.allowedModels !== undefined) { found = true; break }
|
|
22
|
+
}
|
|
23
|
+
} catch { /* 解析失败当没配 */ }
|
|
24
|
+
rows.push({ profile: name, hasAllowlist: found })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log('=== deepseek-foreman setup ===')
|
|
28
|
+
console.log('1) allowedModels 白名单检查:')
|
|
29
|
+
for (const r of rows) console.log(' profile ' + r.profile + ': ' + (r.hasAllowlist ? '已配置 ok' : '未配置 -> 在该 profile 的 cordis.patch.yml 加 subagent-model-selection-settings 的 allowedModels(模板见 README 前置条件),改完开新会话'))
|
|
30
|
+
if (rows.length === 0) console.log(' 未发现 profile(先安装并运行一次 dsh)')
|
|
31
|
+
console.log('2) 角色表:编辑 ~/.dsh/foreman.roles.yml,provider/model 用「调 pick_route 看看 setup」返回的可用清单里的值。')
|
|
32
|
+
console.log('3) 试跑一张小工单:对 dsh 说「走工单:把 docs/README.md 里错的日期改成今天,difficulty: trivial」。')
|
|
@@ -19,7 +19,8 @@ description: 工单托管(DeepSeek Harness 版):你当经理,不自己
|
|
|
19
19
|
- 谁施工、谁审查。**审查必须和施工来自不同厂商**——同族模型会犯同一种错。
|
|
20
20
|
- 施工是否不止一个,按活分(长输出给输出上限高的,机械批量给便宜档)。
|
|
21
21
|
- 审查员是否只读。**首选 `subagent_readonly` 物理只读**:经它派出的子会话运行时只有 `read` / `grep` / `glob`,写工具改不了文件;会话里没有这个工具时(preset 层未放行)退回普通 `subagent` + 提示词只读 + Lead 审查前后各看一次 `git status`(详见第六节)。
|
|
22
|
-
4.
|
|
22
|
+
4. **跑 setup 向导**:`node node_modules/deepseek-foreman/scripts/setup.mjs`(或 npx deepseek-foreman-setup)——核对白名单、指路角色表、给出试跑小工单话术。**首次配置一律先跑它**,别徒手改 YAML。
|
|
23
|
+
5. **写进 `_tickets/workers.md`**,格式见下。以后照它派,不重新摸。
|
|
23
24
|
|
|
24
25
|
```markdown
|
|
25
26
|
# 工人配置
|
|
@@ -166,6 +167,24 @@ _receipts/ 回执、审查报告、进度、报告
|
|
|
166
167
|
|
|
167
168
|
**停下或被叫回时**:写 `_receipts/report-<日期>.md`,大白话说清哪些能用了、哪些没做完、哪些等他拍板(每条一行附你的建议)、出了什么问题、工人花了多少钱。看不到自己的用量就直说看不到,不要猜。
|
|
168
169
|
|
|
170
|
+
## 十、bundle 改动冒烟清单(R12)
|
|
171
|
+
|
|
172
|
+
改 `cordis.patch.yml`(加实例/改 toolFilter/改 config)是全项目最危险的操作——**离线 110 项全绿也可能 live 才炸**(T205 preset scope、T207 全局工具表都是这么来的)。每次改完必须做:
|
|
173
|
+
|
|
174
|
+
1. `npm run build && node test/smoke.mjs` 全绿(离线层)
|
|
175
|
+
2. **插件页关→开(或重启 app)**——手改文件不会通知运行中的 Host
|
|
176
|
+
3. live 三查:模型侧 `pick_route` 有返回;新实例在模型工具列表里;**用一次该实例**(真派一个最小子任务)确认行为
|
|
177
|
+
4. 报错对照两条已知坑:`standing ... requires a scoped preset Context`(bundle 层不能开 modelSelectionSettings);`restrict() names unknown global tool`(deny/allow 名字必须在子组合全局工具表里)
|
|
178
|
+
5. 以上任一失败:立即回退该行 patch,写进回执存疑,另开修复单
|
|
179
|
+
|
|
180
|
+
## 十一、换会话交接(R7)
|
|
181
|
+
|
|
182
|
+
**任何会话收尾前**(包括普通单、不只无人托管)写 `_tickets/handoff.md`,上限 2KB,只放七样:用户目标原话、「不做清单」、下一件的第一动作、当前 HEAD、工单/回执路径、未定决定、预算闸状态。新会话开工先读它,**不重跑盘点、不靠记忆**——只核 HEAD 与下一件工单在不在,相符就干。
|
|
183
|
+
|
|
184
|
+
## 十二、上下文四档(R3)
|
|
185
|
+
|
|
186
|
+
Lead 会话上下文占用自估(工单+回执+审查读入量):**0-30% 全速**(并行派单、完整读回执);**30-50% 正常**(优先只读结论层);**50-70% 收缩**(只读 frontmatter,警告用户);**70%+ 切割**(立即写交接、收尾当前单、停新派)。判断信号:措辞变虚、跳步骤、半完成——都是上下文压力前兆。
|
|
187
|
+
|
|
169
188
|
## 规矩
|
|
170
189
|
|
|
171
190
|
- 工人不挪工单、不提交、不审自己的活、不合并别的单。
|