dsh-thesis 1.0.1 → 2.0.2
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/LICENSE +1 -1
- package/NOTICE.md +1 -1
- package/README.md +51 -86
- package/docs/autopilot.md +73 -0
- package/docs/config.md +113 -0
- package/{DESIGN.md → docs/design.md} +69 -74
- package/docs/host-api.md +49 -6
- package/docs/release.md +138 -0
- package/docs/tools.md +76 -0
- package/docs/troubleshooting.md +88 -0
- package/docs/usage.md +87 -0
- package/lib/autopilot/index.d.ts +38 -0
- package/lib/autopilot/index.js +159 -0
- package/lib/autopilot/intent.d.ts +40 -0
- package/lib/autopilot/intent.js +82 -0
- package/lib/autopilot/messages.d.ts +23 -0
- package/lib/autopilot/messages.js +36 -0
- package/lib/autopilot/types.d.ts +60 -0
- package/lib/autopilot/types.js +10 -0
- package/lib/codewalk/engine.d.ts +1 -1
- package/lib/codewalk/engine.js +1 -1
- package/lib/codewalk/gate.d.ts +1 -1
- package/lib/codewalk/gate.js +1 -1
- package/lib/codewalk/host-types.d.ts +2 -31
- package/lib/codewalk/prestep.js +3 -3
- package/lib/codewalk/tools.js +2 -2
- package/lib/config.d.ts +17 -0
- package/lib/config.js +9 -1
- package/lib/dedup/index.js +0 -6
- package/lib/gates/messages.d.ts +7 -6
- package/lib/gates/messages.js +8 -5
- package/lib/index.d.ts +3 -1
- package/lib/index.js +10 -7
- package/lib/ingest/index.d.ts +1 -1
- package/lib/ingest/index.js +1 -3
- package/lib/intake/gate-rules.d.ts +1 -2
- package/lib/intake/gate-rules.js +2 -2
- package/lib/intake/index.d.ts +0 -17
- package/lib/intake/index.js +0 -51
- package/lib/intake/questions.d.ts +1 -1
- package/lib/intake/questions.js +1 -1
- package/lib/memory/recall.d.ts +1 -2
- package/lib/memory/recall.js +2 -2
- package/lib/paper/lib/layout.d.ts +1 -1
- package/lib/paper/lib/layout.js +2 -2
- package/lib/paper/lib/ledger.d.ts +1 -1
- package/lib/paper/lib/ledger.js +1 -1
- package/lib/paper/register.d.ts +2 -2
- package/lib/paper/register.js +2 -8
- package/lib/paper/tools/decision.js +1 -1
- package/lib/paper/tools/init.d.ts +1 -1
- package/lib/paper/tools/init.js +1 -1
- package/lib/ppt/index.d.ts +1 -2
- package/lib/ppt/index.js +1 -2
- package/lib/shared/define-tool.d.ts +1 -1
- package/lib/shared/define-tool.js +1 -1
- package/lib/shared/message-source.d.ts +21 -0
- package/lib/shared/message-source.js +19 -0
- package/package.json +17 -17
- package/skills/code-walkthrough/SKILL.md +5 -3
- package/skills/thesis-defense/SKILL.md +1 -1
- package/skills/thesis-pipeline/SKILL.md +3 -3
- package/lib/codewalk/commands.d.ts +0 -23
- package/lib/codewalk/commands.js +0 -168
- package/lib/commands.d.ts +0 -33
- package/lib/commands.js +0 -60
- package/lib/dedup/commands.d.ts +0 -21
- package/lib/dedup/commands.js +0 -93
- package/lib/ingest/commands.d.ts +0 -23
- package/lib/ingest/commands.js +0 -74
- package/lib/paper/commands.d.ts +0 -13
- package/lib/paper/commands.js +0 -84
- package/lib/ppt/commands.d.ts +0 -28
- package/lib/ppt/commands.js +0 -71
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶:插件在后台把流程推起来,人只在两处出现——**开头选一次意图**、**过关卡时拍一次板**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不让人敲命令:命令是给人操作 CLI 的入口,而这里真正的主角是模型与流水线。
|
|
5
|
+
* 插件要做的不是再提供一套动词,而是:
|
|
6
|
+
*
|
|
7
|
+
* 1. `agent/pre-step`:认出「这是论文工作区、用户还没定过意图」→ 问一道选择题
|
|
8
|
+
* (`ctx.userQuestions.ask`,三四档常见起点)→ 把答案写进决定日志 →
|
|
9
|
+
* `ctx.goals.create` 建目标(宿主的 goal-round-driver 此后自动续轮)→
|
|
10
|
+
* 注入一条 operating contract(怎么推进、怎么并行、哪里必须停)。
|
|
11
|
+
* 2. `tools/pre-execute`:模型要过 G1-G4 时拦一次 `ask`,由人在审批界面拍板。
|
|
12
|
+
*
|
|
13
|
+
* 两条铁律:**只对主 agent 生效**(子 agent 委派深度 > 0 一律跳过)、
|
|
14
|
+
* **只对论文工作区生效**(找不到台账就完全不介入,别的项目里装了这个插件也不受干扰)。
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-thesis/autopilot
|
|
17
|
+
*/
|
|
18
|
+
import { findThesisRoot } from "../paper/lib/project.js";
|
|
19
|
+
import { runDecision } from "../paper/tools/decision.js";
|
|
20
|
+
import { choiceOfLabel, INTENT_QUESTION_ID, intentQuestion } from "./intent.js";
|
|
21
|
+
import { gateApprovalReason, makeAutopilotMessage } from "./messages.js";
|
|
22
|
+
/** 一个 agent 的委派深度:与宿主 `delegationDepthOf` 同一条规则(头里的深度只增不减)。 */
|
|
23
|
+
export function agentDepth(agent) {
|
|
24
|
+
const fromHeader = agent.session?.header?.subagentDepth ?? 0;
|
|
25
|
+
const fromOptions = agent.options?.subagentDepth ?? 0;
|
|
26
|
+
return Math.max(fromHeader, fromOptions);
|
|
27
|
+
}
|
|
28
|
+
/** 从结构化答案里取出被选中的标签(兼容多选与自定义输入)。 */
|
|
29
|
+
export function selectedLabel(answer) {
|
|
30
|
+
const item = answer?.answers?.find(entry => entry.id === INTENT_QUESTION_ID) ?? answer?.answers?.[0];
|
|
31
|
+
if (item === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const picked = item.selected?.[0];
|
|
34
|
+
if (typeof picked === 'string' && picked.trim() !== '')
|
|
35
|
+
return picked;
|
|
36
|
+
if (typeof item.custom === 'string' && item.custom.trim() !== '')
|
|
37
|
+
return item.custom;
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** 意图规格是否已经存在(存在就不再打扰:用户已经定过要求,或已经跑过一轮)。 */
|
|
41
|
+
async function specExists(ctx, root, specRel, signal) {
|
|
42
|
+
try {
|
|
43
|
+
await ctx.fs.readText(await ctx.fs.resolve(`${root}/${specRel}`, { signal }), signal);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 装上自动驾驶的两处监听。
|
|
52
|
+
*
|
|
53
|
+
* 顺序关键:`agent/pre-step` 先 `await next()` 再注入(与写作意图闸门同一约定),
|
|
54
|
+
* 任何异常都吞成「不介入」,绝不把错误抛进 agent 循环。
|
|
55
|
+
*/
|
|
56
|
+
export function installAutopilot(ctx, options) {
|
|
57
|
+
if (!options.enabled)
|
|
58
|
+
return;
|
|
59
|
+
const started = new Set();
|
|
60
|
+
ctx.on('agent/pre-step', async (payload, next) => {
|
|
61
|
+
const downstream = await next();
|
|
62
|
+
if (downstream.kind !== 'enter')
|
|
63
|
+
return downstream;
|
|
64
|
+
const agent = payload.agent;
|
|
65
|
+
if (agent === undefined)
|
|
66
|
+
return downstream;
|
|
67
|
+
if (agentDepth(agent) > 0)
|
|
68
|
+
return downstream;
|
|
69
|
+
const key = agent.id ?? 'anonymous';
|
|
70
|
+
if (started.has(key))
|
|
71
|
+
return downstream;
|
|
72
|
+
let root = null;
|
|
73
|
+
try {
|
|
74
|
+
root = await findThesisRoot(ctx.fs, agent.session?.header?.cwd, payload.signal);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// 配置的 workspace 写错、台账读不出来:都不属于「自动驾驶」该处理的场景。
|
|
78
|
+
return downstream;
|
|
79
|
+
}
|
|
80
|
+
if (root === null)
|
|
81
|
+
return downstream;
|
|
82
|
+
started.add(key);
|
|
83
|
+
if (await specExists(ctx, root, options.specRel, payload.signal)) {
|
|
84
|
+
ctx.logger.info('dsh-thesis: 已有意图规格,自动驾驶不再重复询问,按现有规格推进。');
|
|
85
|
+
return downstream;
|
|
86
|
+
}
|
|
87
|
+
const choice = await askIntent(ctx, agent, payload.signal);
|
|
88
|
+
if (choice === undefined)
|
|
89
|
+
return downstream;
|
|
90
|
+
try {
|
|
91
|
+
await runDecision(ctx.fs, { content: `自动驾驶启动:本次意图 = 「${choice.label}」(用户在启动选择题里确认;目标上限 ${options.maxGoalRounds} 轮)` }, root, payload.signal);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
ctx.logger.warn(`dsh-thesis: 自动驾驶意图写进决定日志失败:${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
}
|
|
96
|
+
armGoal(ctx, agent, choice, options.maxGoalRounds);
|
|
97
|
+
return { kind: 'enter', messages: [...downstream.messages, makeAutopilotMessage(choice, options.maxGoalRounds)] };
|
|
98
|
+
});
|
|
99
|
+
if (options.gateApproval)
|
|
100
|
+
installGateApproval(ctx);
|
|
101
|
+
}
|
|
102
|
+
/** 问那一道选择题;拿不到答案(没有答题面 / 用户取消 / 选了「先别自动跑」)时返回 undefined。 */
|
|
103
|
+
async function askIntent(ctx, agent, signal) {
|
|
104
|
+
const service = ctx.get('userQuestions');
|
|
105
|
+
if (service === undefined || typeof service.ask !== 'function') {
|
|
106
|
+
ctx.logger.info('dsh-thesis: 宿主没有 userQuestions 服务,自动驾驶跳过意图询问(改为按现有上下文推进)。');
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const answer = await service.ask({ questions: [intentQuestion()], agent, ...(signal === undefined ? {} : { signal }) });
|
|
111
|
+
const label = selectedLabel(answer);
|
|
112
|
+
if (label === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
const choice = choiceOfLabel(label);
|
|
115
|
+
// 认不出的标签(例如用户自己输入了别的)按「关掉自动驾驶」处理,绝不猜。
|
|
116
|
+
if (choice === undefined || choice.objective === '') {
|
|
117
|
+
ctx.logger.info(`dsh-thesis: 用户没有选择自动推进(${label})。`);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return choice;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
ctx.logger.info(`dsh-thesis: 意图选择题没有拿到答案(${error instanceof Error ? error.message : String(error)}),本次不自动推进。`);
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** 建目标:宿主的目标轮驱动器会按它自动续轮,直到完成或轮数用尽。 */
|
|
128
|
+
function armGoal(ctx, agent, choice, maxGoalRounds) {
|
|
129
|
+
const goals = ctx.get('goals');
|
|
130
|
+
if (goals === undefined || typeof goals.create !== 'function') {
|
|
131
|
+
ctx.logger.info('dsh-thesis: 宿主没有 goals 服务,自动驾驶只推进当前一轮(不会自动续轮)。');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
goals.create(agent, { objective: choice.objective, maxGoalRounds });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
ctx.logger.warn(`dsh-thesis: 建目标失败,本轮流完就停:${error instanceof Error ? error.message : String(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** 人工关卡拦截:模型要过 G1-G4 时,先让人在审批界面拍一次板。 */
|
|
142
|
+
function installGateApproval(ctx) {
|
|
143
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
144
|
+
if (exec.agent === undefined)
|
|
145
|
+
return next();
|
|
146
|
+
if (exec.name !== 'thesis_progress')
|
|
147
|
+
return next();
|
|
148
|
+
const args = exec.arguments;
|
|
149
|
+
if (args === undefined || args.action !== 'gate' || args.pass !== true)
|
|
150
|
+
return next();
|
|
151
|
+
const gate = typeof args.gate === 'string' ? args.gate.toUpperCase() : '关卡';
|
|
152
|
+
const chapter = typeof args.chapter === 'string' ? args.chapter : undefined;
|
|
153
|
+
const note = typeof args.note === 'string' ? args.note : undefined;
|
|
154
|
+
return {
|
|
155
|
+
kind: 'ask',
|
|
156
|
+
reason: gateApprovalReason({ gate, ...(chapter === undefined ? {} : { chapter }), ...(note === undefined ? {} : { note }) }),
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶的文案与意图选项:**一次只问一个问题**,答案决定接下来跑到哪一步。
|
|
3
|
+
*
|
|
4
|
+
* 这里的文本是插件直接呈现给用户的(不是给模型的),所以用词要能让人一眼选:
|
|
5
|
+
* 三档常见起点 + 一个「先别自动跑」。选完就不再打断,直到撞上人工关卡。
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-thesis/autopilot
|
|
8
|
+
*/
|
|
9
|
+
/** 意图选择题的问题 id(宿主按 id 把答案回传)。 */
|
|
10
|
+
export declare const INTENT_QUESTION_ID = "dsh-thesis.intent";
|
|
11
|
+
/** 一档意图:选项文案 + 目标 + 启动指令里要向模型交代的边界。 */
|
|
12
|
+
export interface IntentChoice {
|
|
13
|
+
/** 选项标签(用户看到的那一行)。 */
|
|
14
|
+
readonly label: string;
|
|
15
|
+
/** 选项说明(UI 可渲染在标签下方)。 */
|
|
16
|
+
readonly description: string;
|
|
17
|
+
/** 写进 goal 的目标(宿主按它渲染续跑提示)。 */
|
|
18
|
+
readonly objective: string;
|
|
19
|
+
/** 注入给模型的第一条指令正文;空串表示「用户不要自动跑」。 */
|
|
20
|
+
readonly instruction: string;
|
|
21
|
+
}
|
|
22
|
+
/** 「先别自动跑」的选项标签:回答它等于关掉本次自动驾驶。 */
|
|
23
|
+
export declare const HANDS_OFF_LABEL = "\u5148\u522B\u81EA\u52A8\u8DD1\uFF0C\u6211\u81EA\u5DF1\u6765";
|
|
24
|
+
/** 三档起点 + 一个关闭项(顺序即 UI 顺序)。 */
|
|
25
|
+
export declare const INTENT_CHOICES: readonly IntentChoice[];
|
|
26
|
+
/** 把用户选的标签映射回一档意图;认不出来时返回 undefined(按「关掉」处理)。 */
|
|
27
|
+
export declare function choiceOfLabel(label: string): IntentChoice | undefined;
|
|
28
|
+
/** 构造那一道选择题。 */
|
|
29
|
+
export declare function intentQuestion(): {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly header: string;
|
|
32
|
+
readonly question: string;
|
|
33
|
+
readonly detail: string;
|
|
34
|
+
readonly options: readonly {
|
|
35
|
+
readonly label: string;
|
|
36
|
+
readonly description: string;
|
|
37
|
+
}[];
|
|
38
|
+
};
|
|
39
|
+
/** 启动指令消息的正文(含「这条不是用户需求」的免责声明)。 */
|
|
40
|
+
export declare function autopilotInstruction(choice: IntentChoice, maxGoalRounds: number): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶的文案与意图选项:**一次只问一个问题**,答案决定接下来跑到哪一步。
|
|
3
|
+
*
|
|
4
|
+
* 这里的文本是插件直接呈现给用户的(不是给模型的),所以用词要能让人一眼选:
|
|
5
|
+
* 三档常见起点 + 一个「先别自动跑」。选完就不再打断,直到撞上人工关卡。
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-thesis/autopilot
|
|
8
|
+
*/
|
|
9
|
+
/** 意图选择题的问题 id(宿主按 id 把答案回传)。 */
|
|
10
|
+
export const INTENT_QUESTION_ID = 'dsh-thesis.intent';
|
|
11
|
+
/** 「先别自动跑」的选项标签:回答它等于关掉本次自动驾驶。 */
|
|
12
|
+
export const HANDS_OFF_LABEL = '先别自动跑,我自己来';
|
|
13
|
+
/** 三档起点 + 一个关闭项(顺序即 UI 顺序)。 */
|
|
14
|
+
export const INTENT_CHOICES = [
|
|
15
|
+
{
|
|
16
|
+
label: '从零走到答辩(九阶段全跑)',
|
|
17
|
+
description: '材料 → 追问 → 文献 → 设计 → 实现 → 测试 → 论文 → 合规 → 答辩,中间只在人工关卡停下来问你。',
|
|
18
|
+
objective: '在 dsh-thesis 论文工作区里把毕业设计从材料推进到论文 docx 与答辩材料,按九阶段与 G1-G4 关卡推进,撞关卡时停下来问用户一次。',
|
|
19
|
+
instruction: [
|
|
20
|
+
'本次会话已开启**自动驾驶**:用户刚选了「从零走到答辩」。',
|
|
21
|
+
'按 `thesis-pipeline` 技能推进,顺序:`thesis_progress action=report` 看当前阶段 → 缺工作区就 `thesis_init` → 有材料就 `thesis_ingest` → 用 `thesis_intake` 逐题追问(一次一个)把要求问成意图规格 → 文献 → 逐章写作与评审 → 全文检查 → 构建 → 原创性自查 → 答辩。',
|
|
22
|
+
'省时间的并行部分(例如各章写作、逐章检查、文献笔记)用 subagent / workflow 扇出,但**结论必须落盘到工作区**,不要只留在子 agent 的上下文里。',
|
|
23
|
+
].join('\n'),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: '已有材料/初稿,补齐合规与答辩',
|
|
27
|
+
description: '不重写已有章节;先做引用与格式检查、降重自查,再出答辩材料与幻灯。',
|
|
28
|
+
objective: '在已有的 dsh-thesis 论文工作区里补齐合规检查(引用/字数/图表/术语/模板)、原创性自查与答辩材料,不重写已有正文。',
|
|
29
|
+
instruction: [
|
|
30
|
+
'本次会话已开启**自动驾驶**:用户刚选了「已有材料/初稿,补齐合规与答辩」。',
|
|
31
|
+
'先 `thesis_progress action=report` 与 `thesis_check` 看清现状,再按缺口补:引用 bidirectional 一致、字数区间、图表编号、术语定义顺序、样式自查、原创性复测,最后 `thesis_defense` + `thesis_slides`。',
|
|
32
|
+
'**不要重写用户已有的正文**;发现必须改动的地方,先记进决定日志再动。',
|
|
33
|
+
].join('\n'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: '只做答辩准备',
|
|
37
|
+
description: '答辩素材、预答辩问题库、幻灯与讲稿,以及把系统拆成讲得清的模块。',
|
|
38
|
+
objective: '只做答辩准备:答辩素材、六类问题库、答辩幻灯与讲稿,以及代码演练(把系统拆成讲得清的模块)。',
|
|
39
|
+
instruction: [
|
|
40
|
+
'本次会话已开启**自动驾驶**:用户刚选了「只做答辩准备」。',
|
|
41
|
+
'顺序:`thesis_defense` 出素材与问题库 → `thesis_slides outline` 出幻灯骨架并逐页补齐讲稿与证据锚点 → `thesis_slides check` 自查 → 需要讲实现细节时用 `defense_code_*` 做代码演练。',
|
|
42
|
+
'幻灯以 Markdown 为准,pptx 转换探测不到外部引擎时如实说明,不假装成功。',
|
|
43
|
+
].join('\n'),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: HANDS_OFF_LABEL,
|
|
47
|
+
description: '本次不建目标、不自动续跑,照常一问一答。',
|
|
48
|
+
objective: '',
|
|
49
|
+
instruction: '',
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
/** 把用户选的标签映射回一档意图;认不出来时返回 undefined(按「关掉」处理)。 */
|
|
53
|
+
export function choiceOfLabel(label) {
|
|
54
|
+
const normalized = label.trim();
|
|
55
|
+
return INTENT_CHOICES.find(choice => choice.label === normalized);
|
|
56
|
+
}
|
|
57
|
+
/** 构造那一道选择题。 */
|
|
58
|
+
export function intentQuestion() {
|
|
59
|
+
return {
|
|
60
|
+
id: INTENT_QUESTION_ID,
|
|
61
|
+
header: '确认意图',
|
|
62
|
+
question: '这次想让我做到哪一步?选一个我就一路跑下去。',
|
|
63
|
+
detail: '只在人工关卡(选题/逐章验收/开题/提交自查)停下来问你;其余步骤我自己推进。',
|
|
64
|
+
options: INTENT_CHOICES.map(choice => ({ label: choice.label, description: choice.description })),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** 启动指令消息的正文(含「这条不是用户需求」的免责声明)。 */
|
|
68
|
+
export function autopilotInstruction(choice, maxGoalRounds) {
|
|
69
|
+
return [
|
|
70
|
+
'【dsh-thesis · 自动驾驶】',
|
|
71
|
+
choice.instruction,
|
|
72
|
+
'',
|
|
73
|
+
`目标已登记(最多 ${maxGoalRounds} 轮自动续跑)。每轮结束前,用 \`thesis_progress action=report\` 看一眼台账,确认下一步是该继续、该停下来问,还是该收尾。`,
|
|
74
|
+
'规矩:',
|
|
75
|
+
'1. 人工关卡(G1-G4)由**用户**拍板——轮到你过闸时,插件会弹一次确认,不要自己替用户过。',
|
|
76
|
+
'2. 数据与文献只能来自真实运行与真实检索;拿不到就如实说,绝不编造。',
|
|
77
|
+
'3. 大段独立的写作/检查用子 agent 并行,但**产出必须写进工作区文件**,子 agent 的结论不落盘等于没做。',
|
|
78
|
+
'4. 该收尾时把 `update_goal` 标完成,并给出一页「做了什么 / 待你确认」的清单。',
|
|
79
|
+
'',
|
|
80
|
+
'注意:这条消息由插件注入,不是用户需求的一部分。',
|
|
81
|
+
].join('\n');
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶注入的消息与关卡确认文案。
|
|
3
|
+
*
|
|
4
|
+
* 与闸门消息共用同一条 source 约定:`{ kind: 'plugin:dsh-thesis', form: 'instructions' }`
|
|
5
|
+
* (v4 会话格式禁止兜底 kind `plugin`,见 `shared/message-source.ts`)。
|
|
6
|
+
* `form` 是宿主渲染指令的语义形态;`sections` 只属于 snapshot 形态,这里不写。
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-thesis/autopilot
|
|
9
|
+
*/
|
|
10
|
+
import type { UserMessage } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import { type IntentChoice } from './intent.ts';
|
|
12
|
+
/** 构造启动指令消息(插件来源、instructions 形态)。 */
|
|
13
|
+
export declare function makeAutopilotMessage(choice: IntentChoice, maxGoalRounds: number): UserMessage;
|
|
14
|
+
/**
|
|
15
|
+
* 人工关卡的确认文案(`tools/pre-execute` 的 ask 决策,显示在审批界面)。
|
|
16
|
+
*
|
|
17
|
+
* 一关只问一次,问题只有一个:这一步你认不认。认了流程才会继续推进。
|
|
18
|
+
*/
|
|
19
|
+
export declare function gateApprovalReason(request: {
|
|
20
|
+
readonly gate: string;
|
|
21
|
+
readonly chapter?: string;
|
|
22
|
+
readonly note?: string;
|
|
23
|
+
}): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶注入的消息与关卡确认文案。
|
|
3
|
+
*
|
|
4
|
+
* 与闸门消息共用同一条 source 约定:`{ kind: 'plugin:dsh-thesis', form: 'instructions' }`
|
|
5
|
+
* (v4 会话格式禁止兜底 kind `plugin`,见 `shared/message-source.ts`)。
|
|
6
|
+
* `form` 是宿主渲染指令的语义形态;`sections` 只属于 snapshot 形态,这里不写。
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-thesis/autopilot
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { PLUGIN_SOURCE } from "../gates/messages.js";
|
|
12
|
+
import { autopilotInstruction } from "./intent.js";
|
|
13
|
+
/** 构造启动指令消息(插件来源、instructions 形态)。 */
|
|
14
|
+
export function makeAutopilotMessage(choice, maxGoalRounds) {
|
|
15
|
+
return {
|
|
16
|
+
id: randomUUID(),
|
|
17
|
+
role: 'user',
|
|
18
|
+
content: [{ type: 'text', text: autopilotInstruction(choice, maxGoalRounds) }],
|
|
19
|
+
source: { ...PLUGIN_SOURCE, form: 'instructions' },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 人工关卡的确认文案(`tools/pre-execute` 的 ask 决策,显示在审批界面)。
|
|
24
|
+
*
|
|
25
|
+
* 一关只问一次,问题只有一个:这一步你认不认。认了流程才会继续推进。
|
|
26
|
+
*/
|
|
27
|
+
export function gateApprovalReason(request) {
|
|
28
|
+
const target = request.chapter === undefined ? request.gate : `${request.gate}(${request.chapter})`;
|
|
29
|
+
return [
|
|
30
|
+
`【dsh-thesis · 人工关卡】流程要过「${target}」这一关。`,
|
|
31
|
+
'这是插件里唯一必须由你拍板的地方:过了这关,后面的产出就按它往下走。',
|
|
32
|
+
'',
|
|
33
|
+
'允许 = 这一关通过,流程继续;拒绝 = 打回,让模型先去补该补的东西。',
|
|
34
|
+
request.note === undefined ? '' : `模型给的备注:${request.note}`,
|
|
35
|
+
].filter(line => line !== '').join('\n');
|
|
36
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶用到的**最小宿主面**:只要真实宿主对象在结构上满足这些字段即可。
|
|
3
|
+
*
|
|
4
|
+
* 与 `codewalk/host-types.ts` 同一取舍:不在插件里 import `@deepseek-ai/dsh-agent` /
|
|
5
|
+
* `-goal` / `-user-questions`,离线单测才能在没有宿主包树的环境里跑;
|
|
6
|
+
* 真实宿主那边由 `scripts/host-smoke.ts` 复核。
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-thesis/autopilot
|
|
9
|
+
*/
|
|
10
|
+
/** 一个 agent 上真正用到的字段(委派深度、会话 cwd)。 */
|
|
11
|
+
export interface AutopilotAgent {
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
readonly options?: {
|
|
14
|
+
readonly subagentDepth?: number;
|
|
15
|
+
};
|
|
16
|
+
readonly session?: {
|
|
17
|
+
readonly header?: {
|
|
18
|
+
readonly cwd?: string;
|
|
19
|
+
/** 持久化会话头里的委派深度:子 agent 大于 0(`delegationDepthOf` 的依据)。 */
|
|
20
|
+
readonly subagentDepth?: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** 一道选择题的选项。 */
|
|
25
|
+
export interface AutopilotOption {
|
|
26
|
+
readonly label: string;
|
|
27
|
+
readonly description?: string;
|
|
28
|
+
}
|
|
29
|
+
/** 一道选择题(宿主 `AskUserQuestionItem` 的子集)。 */
|
|
30
|
+
export interface AutopilotQuestion {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly question: string;
|
|
33
|
+
readonly header?: string;
|
|
34
|
+
readonly detail?: string;
|
|
35
|
+
readonly options?: readonly AutopilotOption[];
|
|
36
|
+
readonly multiSelect?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** 一个问题的答案(宿主 `AskUserQuestionAnswerItem`)。 */
|
|
39
|
+
export interface AutopilotAnswerItem {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly selected: readonly string[];
|
|
42
|
+
readonly custom?: string;
|
|
43
|
+
}
|
|
44
|
+
/** `ctx.userQuestions` 的最小面:问一次、拿结构化答案。 */
|
|
45
|
+
export interface UserQuestionsService {
|
|
46
|
+
ask(request: {
|
|
47
|
+
readonly questions: readonly AutopilotQuestion[];
|
|
48
|
+
readonly agent?: unknown;
|
|
49
|
+
readonly signal?: AbortSignal;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
readonly answers?: readonly AutopilotAnswerItem[];
|
|
52
|
+
} | undefined>;
|
|
53
|
+
}
|
|
54
|
+
/** `ctx.goals` 的最小面:建一个目标,剩下的轮次由宿主的 goal-round-driver 驱动。 */
|
|
55
|
+
export interface GoalsService {
|
|
56
|
+
create(agent: unknown, request: {
|
|
57
|
+
readonly objective: string;
|
|
58
|
+
readonly maxGoalRounds?: number;
|
|
59
|
+
}): unknown;
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动驾驶用到的**最小宿主面**:只要真实宿主对象在结构上满足这些字段即可。
|
|
3
|
+
*
|
|
4
|
+
* 与 `codewalk/host-types.ts` 同一取舍:不在插件里 import `@deepseek-ai/dsh-agent` /
|
|
5
|
+
* `-goal` / `-user-questions`,离线单测才能在没有宿主包树的环境里跑;
|
|
6
|
+
* 真实宿主那边由 `scripts/host-smoke.ts` 复核。
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-thesis/autopilot
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/lib/codewalk/engine.d.ts
CHANGED
|
@@ -103,6 +103,6 @@ export declare class AiLearningEngine {
|
|
|
103
103
|
recordGate(state: LearningState, milestoneId: string, input: Omit<GateRecord, 'milestone' | 'at'>): GateOutcome;
|
|
104
104
|
/** 只读快照,供状态视图、模型工具与注入使用。 */
|
|
105
105
|
snapshot(state: LearningState): EngineStateSnapshot;
|
|
106
|
-
/** `/thesis
|
|
106
|
+
/** `/thesis defense status` 用的人类可读状态文本。 */
|
|
107
107
|
describe(state: LearningState): string;
|
|
108
108
|
}
|
package/lib/codewalk/engine.js
CHANGED
|
@@ -358,7 +358,7 @@ export class AiLearningEngine {
|
|
|
358
358
|
doneQuestionCount,
|
|
359
359
|
};
|
|
360
360
|
}
|
|
361
|
-
/** `/thesis
|
|
361
|
+
/** `/thesis defense status` 用的人类可读状态文本。 */
|
|
362
362
|
describe(state) {
|
|
363
363
|
const view = this.snapshot(state);
|
|
364
364
|
const verified = state.milestones.filter((milestone) => milestone.status === 'verified').length;
|
package/lib/codewalk/gate.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 共享的验证门执行逻辑:通过 `ctx.shell` 跑一个里程碑的验证命令,把结论折进
|
|
3
3
|
* 引擎(记录 + 状态 + 落盘),再把结算后的结果交回给调用方渲染。
|
|
4
4
|
*
|
|
5
|
-
* 人类的 `/thesis
|
|
5
|
+
* 人类的 `/thesis defense check` 命令与模型侧的 `defense_code_update` `check_gate` 动作共用这段
|
|
6
6
|
* 逻辑,因此两个表面永远不会对「什么叫 verified」产生分歧。
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-thesis/codewalk
|
package/lib/codewalk/gate.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 共享的验证门执行逻辑:通过 `ctx.shell` 跑一个里程碑的验证命令,把结论折进
|
|
3
3
|
* 引擎(记录 + 状态 + 落盘),再把结算后的结果交回给调用方渲染。
|
|
4
4
|
*
|
|
5
|
-
* 人类的 `/thesis
|
|
5
|
+
* 人类的 `/thesis defense check` 命令与模型侧的 `defense_code_update` `check_gate` 动作共用这段
|
|
6
6
|
* 逻辑,因此两个表面永远不会对「什么叫 verified」产生分歧。
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-thesis/codewalk
|
|
@@ -56,35 +56,6 @@ export interface AgentShape {
|
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
/** 传给某个已注册命令 handler 的调用信息。 */
|
|
60
|
-
export interface CommandInvocationShape {
|
|
61
|
-
readonly commandId: unknown;
|
|
62
|
-
readonly agent: AgentShape;
|
|
63
|
-
readonly rawInput: string;
|
|
64
|
-
readonly signal: AbortSignal;
|
|
65
|
-
}
|
|
66
|
-
/** 由调度 UI 直接渲染的命令结果。 */
|
|
67
|
-
export type CommandResultShape = {
|
|
68
|
-
readonly kind: 'success';
|
|
69
|
-
readonly text?: string;
|
|
70
|
-
} | {
|
|
71
|
-
readonly kind: 'error';
|
|
72
|
-
readonly text: string;
|
|
73
|
-
};
|
|
74
|
-
/** 插件自有命令注册(`ctx.commands.register`)。 */
|
|
75
|
-
export interface CommandDefinitionShape {
|
|
76
|
-
readonly name: string;
|
|
77
|
-
readonly description: string;
|
|
78
|
-
readonly input?: {
|
|
79
|
-
readonly hint: string;
|
|
80
|
-
};
|
|
81
|
-
readonly recordInput?: boolean;
|
|
82
|
-
handler(invocation: CommandInvocationShape): CommandResultShape | Promise<CommandResultShape>;
|
|
83
|
-
}
|
|
84
|
-
/** 命令注册表(`ctx.commands`);在 headless 组合里是可选的。 */
|
|
85
|
-
export interface CommandsServiceShape {
|
|
86
|
-
register(definition: CommandDefinitionShape): unknown;
|
|
87
|
-
}
|
|
88
59
|
/** 面向模型的消息或工具渲染中的一个文本内容块。 */
|
|
89
60
|
export interface TextBlockShape {
|
|
90
61
|
readonly type: 'text';
|
|
@@ -120,8 +91,8 @@ export interface InjectedUserMessageShape {
|
|
|
120
91
|
readonly role: 'user';
|
|
121
92
|
readonly content: readonly TextBlockShape[];
|
|
122
93
|
readonly source: {
|
|
123
|
-
|
|
124
|
-
readonly
|
|
94
|
+
/** 生产者自有 kind(本插件是 `plugin:dsh-thesis`);v4 会话格式不接受兜底值 `plugin`。 */
|
|
95
|
+
readonly kind: string;
|
|
125
96
|
/** 上下文形态:instructions / catalog / snapshot / notice / relay / recall(可缺省 = 不透明)。 */
|
|
126
97
|
readonly form?: string;
|
|
127
98
|
/** 只有 `form: 'snapshot'` 携带分节;`instructions` 形态的有效字段是 `changes`。 */
|
package/lib/codewalk/prestep.js
CHANGED
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { AiLearningEngine } from "./engine.js";
|
|
12
12
|
import { newMessageId } from "./tools.js";
|
|
13
|
+
import { THESIS_SOURCE } from "../shared/message-source.js";
|
|
13
14
|
/** 为一份状态快照构造注入用的进度消息。 */
|
|
14
15
|
export function buildProgressMessage(state) {
|
|
15
16
|
const text = [
|
|
16
17
|
'当前工作区有一个进行中的代码演练(把自己的系统拆成讲得清的模块,并用可评分的追问确认你答辩时讲得出实现细节)。请带学生把它走完。',
|
|
17
18
|
'',
|
|
18
|
-
'和学生一起推进:带他做完当前里程碑的 todo,逐个提出追问,先沿提示阶梯升级再公布答案,并对照期望要点评分。用 defense_code_status、defense_code_next、defense_code_update 三个工具;只有学生说“做完了”才跑 "/thesis
|
|
19
|
+
'和学生一起推进:带他做完当前里程碑的 todo,逐个提出追问,先沿提示阶梯升级再公布答案,并对照期望要点评分。用 defense_code_status、defense_code_next、defense_code_update 三个工具;只有学生说“做完了”才跑 "/thesis defense check <milestone>"。',
|
|
19
20
|
'',
|
|
20
21
|
].join('\n');
|
|
21
22
|
const full = `${text}${JSON.stringify(state, null, 2)}`;
|
|
@@ -24,8 +25,7 @@ export function buildProgressMessage(state) {
|
|
|
24
25
|
role: 'user',
|
|
25
26
|
content: [{ type: 'text', text: full }],
|
|
26
27
|
source: {
|
|
27
|
-
|
|
28
|
-
plugin: 'dsh-thesis',
|
|
28
|
+
...THESIS_SOURCE,
|
|
29
29
|
form: 'snapshot',
|
|
30
30
|
sections: [{ name: '代码演练进度', text: full }],
|
|
31
31
|
},
|
package/lib/codewalk/tools.js
CHANGED
|
@@ -84,7 +84,7 @@ async function toolCwd(engine, exec) {
|
|
|
84
84
|
}
|
|
85
85
|
const state = await engine.load(cwd);
|
|
86
86
|
if (state === undefined) {
|
|
87
|
-
throw new Error(`${cwd} 下没有代码演练状态;请先执行 "/thesis
|
|
87
|
+
throw new Error(`${cwd} 下没有代码演练状态;请先执行 "/thesis defense new <原始项目路径>"`);
|
|
88
88
|
}
|
|
89
89
|
return state;
|
|
90
90
|
}
|
|
@@ -138,7 +138,7 @@ function nextPayload(state) {
|
|
|
138
138
|
guidance: state.phase === 'analyzing'
|
|
139
139
|
? '先分析原始项目,用 defense_code_update 把架构记成 todo 与里程碑,然后推进阶段。'
|
|
140
140
|
: state.phase === 'skeletonizing'
|
|
141
|
-
? '开始搭骨架:剥掉非核心功能,留下带注释的 TODO 桩,用 /thesis
|
|
141
|
+
? '开始搭骨架:剥掉非核心功能,留下带注释的 TODO 桩,用 /thesis defense check 跑通编译验证门。'
|
|
142
142
|
: state.phase === 'learning'
|
|
143
143
|
? '推进当前里程碑:带学生走完它的 todo,逐个提出追问,并对照期望要点评分。'
|
|
144
144
|
: '全部里程碑已验证:总结学生现在能讲清哪些设计。',
|
package/lib/config.d.ts
CHANGED
|
@@ -76,6 +76,19 @@ export interface CodeWalkthroughOptions {
|
|
|
76
76
|
/** 每条门命令输出保留的最大字符数。 */
|
|
77
77
|
maxCapturedOutput: number;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* 自动驾驶:插件在后台把流程推起来,人只在开头选一次意图、过关卡时拍一次板。
|
|
81
|
+
*
|
|
82
|
+
* 关掉它就是「纯工具模式」:没有启动选择题,也没有关卡确认,一切靠人和模型对话驱动。
|
|
83
|
+
*/
|
|
84
|
+
export interface AutopilotOptions {
|
|
85
|
+
/** 总开关(默认开)。 */
|
|
86
|
+
enabled: boolean;
|
|
87
|
+
/** 目标最多自动跑多少轮(防止跑飞)。 */
|
|
88
|
+
maxGoalRounds: number;
|
|
89
|
+
/** 模型要过 G1-G4 时,是否必须先由人在审批界面拍板(默认开)。 */
|
|
90
|
+
gateApproval: boolean;
|
|
91
|
+
}
|
|
79
92
|
/** 插件配置(全部可选,均有出厂默认值)。 */
|
|
80
93
|
export interface PaperConfig extends GateOptions {
|
|
81
94
|
/** 论文工作区根;缺省从会话 cwd 向上探测 `00-管理/进度台账.md`。 */
|
|
@@ -90,6 +103,8 @@ export interface PaperConfig extends GateOptions {
|
|
|
90
103
|
defense?: Partial<DefenseOptions>;
|
|
91
104
|
/** 代码演练(答辩层)。 */
|
|
92
105
|
codeWalkthrough?: Partial<CodeWalkthroughOptions>;
|
|
106
|
+
/** 自动驾驶(后台推进 + 开头一道选择题 + 关卡拍板)。 */
|
|
107
|
+
autopilot?: Partial<AutopilotOptions>;
|
|
93
108
|
/** 相关时把已确认的稳定事实放回上下文。 */
|
|
94
109
|
recall?: Partial<RecallOptions>;
|
|
95
110
|
}
|
|
@@ -102,6 +117,7 @@ export declare const PAPER_DEFAULTS: {
|
|
|
102
117
|
ppt: PptOptions;
|
|
103
118
|
defense: DefenseOptions;
|
|
104
119
|
codeWalkthrough: CodeWalkthroughOptions;
|
|
120
|
+
autopilot: AutopilotOptions;
|
|
105
121
|
recall: RecallOptions;
|
|
106
122
|
};
|
|
107
123
|
/** 解析后的配置:嵌套块已补全,调用方不再处理 undefined。 */
|
|
@@ -115,6 +131,7 @@ export interface ResolvedPaperConfig {
|
|
|
115
131
|
readonly ppt: PptOptions;
|
|
116
132
|
readonly defense: DefenseOptions;
|
|
117
133
|
readonly codeWalkthrough: CodeWalkthroughOptions;
|
|
134
|
+
readonly autopilot: AutopilotOptions;
|
|
118
135
|
readonly recall: RecallOptions;
|
|
119
136
|
/** 闸门配置:两个开关可选(缺省即由闸门自身按「开」处理),列表均已补全。 */
|
|
120
137
|
readonly gates: GateOptions;
|
package/lib/config.js
CHANGED
|
@@ -38,11 +38,12 @@ export const PAPER_DEFAULTS = {
|
|
|
38
38
|
},
|
|
39
39
|
maxCapturedOutput: 8000,
|
|
40
40
|
},
|
|
41
|
+
autopilot: { enabled: true, maxGoalRounds: 40, gateApproval: true },
|
|
41
42
|
recall: RECALL_DEFAULTS,
|
|
42
43
|
};
|
|
43
44
|
/** 补全嵌套块默认值。 */
|
|
44
45
|
export function resolveConfig(config = {}) {
|
|
45
|
-
const { workspace, memoryPath, ingest, intake, similarity, stylecheck, ppt, defense, codeWalkthrough, recall, writingGate, destructiveGate, writingIntentPatterns, criteriaMarkers, bypassMarkers, maxQuestions, destructiveToolNames, destructiveRules, } = config;
|
|
46
|
+
const { workspace, memoryPath, ingest, intake, similarity, stylecheck, ppt, defense, codeWalkthrough, autopilot, recall, writingGate, destructiveGate, writingIntentPatterns, criteriaMarkers, bypassMarkers, maxQuestions, destructiveToolNames, destructiveRules, } = config;
|
|
46
47
|
return {
|
|
47
48
|
workspace,
|
|
48
49
|
memoryPath,
|
|
@@ -57,6 +58,7 @@ export function resolveConfig(config = {}) {
|
|
|
57
58
|
...codeWalkthrough,
|
|
58
59
|
gates: { ...PAPER_DEFAULTS.codeWalkthrough.gates, ...codeWalkthrough?.gates },
|
|
59
60
|
},
|
|
61
|
+
autopilot: { ...PAPER_DEFAULTS.autopilot, ...autopilot },
|
|
60
62
|
recall: { ...PAPER_DEFAULTS.recall, ...recall },
|
|
61
63
|
gates: {
|
|
62
64
|
...(writingGate !== undefined ? { writingGate } : {}),
|
|
@@ -118,6 +120,12 @@ export const Config = z.object({
|
|
|
118
120
|
})).default(PAPER_DEFAULTS.codeWalkthrough.gates),
|
|
119
121
|
maxCapturedOutput: z.natural().default(PAPER_DEFAULTS.codeWalkthrough.maxCapturedOutput),
|
|
120
122
|
}),
|
|
123
|
+
// ---- 自动驾驶 ----
|
|
124
|
+
autopilot: z.object({
|
|
125
|
+
enabled: z.boolean().default(PAPER_DEFAULTS.autopilot.enabled),
|
|
126
|
+
maxGoalRounds: z.natural().min(1).default(PAPER_DEFAULTS.autopilot.maxGoalRounds),
|
|
127
|
+
gateApproval: z.boolean().default(PAPER_DEFAULTS.autopilot.gateApproval),
|
|
128
|
+
}),
|
|
121
129
|
// ---- 跨会话事实 ----
|
|
122
130
|
recall: z.object({
|
|
123
131
|
maxEntries: z.natural().default(PAPER_DEFAULTS.recall.maxEntries),
|
package/lib/dedup/index.js
CHANGED
|
@@ -18,7 +18,6 @@ import * as nodePath from 'node:path';
|
|
|
18
18
|
import { bulletList, definePaperTool, sessionCwd, textOutput } from "../shared/index.js";
|
|
19
19
|
import { CHAPTER_META, isWrittenChapter } from "../paper/lib/layout.js";
|
|
20
20
|
import { findThesisRoot } from "../paper/lib/project.js";
|
|
21
|
-
import { dedupCommand } from "./commands.js";
|
|
22
21
|
import { segmentsFromText } from "./normalize.js";
|
|
23
22
|
import { scan } from "./similarity.js";
|
|
24
23
|
import { buildReport, parseBaseline, renderReport, renderVerifyReport, verifyDelta, weightedRateOf, } from "./report.js";
|
|
@@ -322,10 +321,5 @@ export function registerDedup(ctx, options) {
|
|
|
322
321
|
return await runDedup(ctx.fs, sessionCwd(exec), rawArgs, options.similarity, exec.signal);
|
|
323
322
|
},
|
|
324
323
|
}));
|
|
325
|
-
// `/thesis-originality [scan|verify|report] [语料路径] [--chapter 01-绪论]`:与工具同一执行层,
|
|
326
|
-
// 命令只是给人用的快捷入口(命令名只允许 [a-z][a-z0-9_-]*)。
|
|
327
|
-
if (ctx.commands !== undefined) {
|
|
328
|
-
ctx.commands.register(dedupCommand(ctx.fs, options.similarity));
|
|
329
|
-
}
|
|
330
324
|
}
|
|
331
325
|
export { segmentsFromText, scan, buildReport, renderReport, weightedRateOf };
|