dsh-thesis 1.0.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.
- package/DESIGN.md +390 -0
- package/LICENSE +21 -0
- package/NOTICE.md +10 -0
- package/README.md +253 -0
- package/cordis.patch.yml +17 -0
- package/docs/host-api.md +640 -0
- package/lib/codewalk/commands.d.ts +23 -0
- package/lib/codewalk/commands.js +168 -0
- package/lib/codewalk/engine.d.ts +108 -0
- package/lib/codewalk/engine.js +449 -0
- package/lib/codewalk/errors.d.ts +31 -0
- package/lib/codewalk/errors.js +20 -0
- package/lib/codewalk/fs-types.d.ts +34 -0
- package/lib/codewalk/fs-types.js +10 -0
- package/lib/codewalk/gate.d.ts +30 -0
- package/lib/codewalk/gate.js +60 -0
- package/lib/codewalk/host-types.d.ts +198 -0
- package/lib/codewalk/host-types.js +11 -0
- package/lib/codewalk/prestep.d.ts +17 -0
- package/lib/codewalk/prestep.js +62 -0
- package/lib/codewalk/register.d.ts +23 -0
- package/lib/codewalk/register.js +31 -0
- package/lib/codewalk/skill.d.ts +15 -0
- package/lib/codewalk/skill.js +57 -0
- package/lib/codewalk/tools.d.ts +18 -0
- package/lib/codewalk/tools.js +314 -0
- package/lib/codewalk/types.d.ts +151 -0
- package/lib/codewalk/types.js +9 -0
- package/lib/commands.d.ts +33 -0
- package/lib/commands.js +60 -0
- package/lib/config.d.ts +125 -0
- package/lib/config.js +139 -0
- package/lib/dedup/commands.d.ts +21 -0
- package/lib/dedup/commands.js +93 -0
- package/lib/dedup/index.d.ts +75 -0
- package/lib/dedup/index.js +331 -0
- package/lib/dedup/normalize.d.ts +125 -0
- package/lib/dedup/normalize.js +329 -0
- package/lib/dedup/report.d.ts +114 -0
- package/lib/dedup/report.js +309 -0
- package/lib/dedup/rewrite.d.ts +71 -0
- package/lib/dedup/rewrite.js +228 -0
- package/lib/dedup/similarity.d.ts +169 -0
- package/lib/dedup/similarity.js +323 -0
- package/lib/gates/dangerous.d.ts +22 -0
- package/lib/gates/dangerous.js +47 -0
- package/lib/gates/detect.d.ts +54 -0
- package/lib/gates/detect.js +106 -0
- package/lib/gates/messages.d.ts +39 -0
- package/lib/gates/messages.js +74 -0
- package/lib/gates/prompt.d.ts +21 -0
- package/lib/gates/prompt.js +56 -0
- package/lib/gates/writing-intent.d.ts +49 -0
- package/lib/gates/writing-intent.js +68 -0
- package/lib/index.d.ts +36 -0
- package/lib/index.js +76 -0
- package/lib/ingest/code.d.ts +71 -0
- package/lib/ingest/code.js +356 -0
- package/lib/ingest/commands.d.ts +23 -0
- package/lib/ingest/commands.js +74 -0
- package/lib/ingest/docx.d.ts +29 -0
- package/lib/ingest/docx.js +92 -0
- package/lib/ingest/index.d.ts +179 -0
- package/lib/ingest/index.js +640 -0
- package/lib/ingest/ooxml.d.ts +58 -0
- package/lib/ingest/ooxml.js +393 -0
- package/lib/ingest/pdf.d.ts +86 -0
- package/lib/ingest/pdf.js +1016 -0
- package/lib/ingest/pptx.d.ts +32 -0
- package/lib/ingest/pptx.js +116 -0
- package/lib/ingest/text.d.ts +25 -0
- package/lib/ingest/text.js +180 -0
- package/lib/ingest/types.d.ts +48 -0
- package/lib/ingest/types.js +47 -0
- package/lib/ingest/xlsx.d.ts +72 -0
- package/lib/ingest/xlsx.js +411 -0
- package/lib/ingest/zip.d.ts +76 -0
- package/lib/ingest/zip.js +393 -0
- package/lib/intake/definitions.d.ts +65 -0
- package/lib/intake/definitions.js +42 -0
- package/lib/intake/gate-rules.d.ts +57 -0
- package/lib/intake/gate-rules.js +118 -0
- package/lib/intake/gate.d.ts +24 -0
- package/lib/intake/gate.js +86 -0
- package/lib/intake/index.d.ts +76 -0
- package/lib/intake/index.js +468 -0
- package/lib/intake/questions.d.ts +169 -0
- package/lib/intake/questions.js +720 -0
- package/lib/intake/spec.d.ts +66 -0
- package/lib/intake/spec.js +221 -0
- package/lib/intake/state.d.ts +192 -0
- package/lib/intake/state.js +420 -0
- package/lib/memory/recall.d.ts +69 -0
- package/lib/memory/recall.js +177 -0
- package/lib/memory/register.d.ts +37 -0
- package/lib/memory/register.js +52 -0
- package/lib/memory/service.d.ts +39 -0
- package/lib/memory/service.js +39 -0
- package/lib/memory/store.d.ts +45 -0
- package/lib/memory/store.js +123 -0
- package/lib/memory/tools.d.ts +19 -0
- package/lib/memory/tools.js +195 -0
- package/lib/memory/types.d.ts +55 -0
- package/lib/memory/types.js +6 -0
- package/lib/paper/commands.d.ts +13 -0
- package/lib/paper/commands.js +84 -0
- package/lib/paper/lib/aicheck.d.ts +38 -0
- package/lib/paper/lib/aicheck.js +241 -0
- package/lib/paper/lib/bibtex.d.ts +21 -0
- package/lib/paper/lib/bibtex.js +114 -0
- package/lib/paper/lib/check.d.ts +32 -0
- package/lib/paper/lib/check.js +215 -0
- package/lib/paper/lib/defense.d.ts +48 -0
- package/lib/paper/lib/defense.js +220 -0
- package/lib/paper/lib/docx.d.ts +35 -0
- package/lib/paper/lib/docx.js +213 -0
- package/lib/paper/lib/layout.d.ts +41 -0
- package/lib/paper/lib/layout.js +572 -0
- package/lib/paper/lib/ledger.d.ts +112 -0
- package/lib/paper/lib/ledger.js +349 -0
- package/lib/paper/lib/lit-api.d.ts +59 -0
- package/lib/paper/lib/lit-api.js +296 -0
- package/lib/paper/lib/lit-cache.d.ts +32 -0
- package/lib/paper/lib/lit-cache.js +91 -0
- package/lib/paper/lib/project.d.ts +29 -0
- package/lib/paper/lib/project.js +93 -0
- package/lib/paper/lib/review.d.ts +37 -0
- package/lib/paper/lib/review.js +194 -0
- package/lib/paper/lib/zip.d.ts +14 -0
- package/lib/paper/lib/zip.js +124 -0
- package/lib/paper/register.d.ts +20 -0
- package/lib/paper/register.js +183 -0
- package/lib/paper/tools/aicheck.d.ts +6 -0
- package/lib/paper/tools/aicheck.js +38 -0
- package/lib/paper/tools/build.d.ts +27 -0
- package/lib/paper/tools/build.js +166 -0
- package/lib/paper/tools/check.d.ts +7 -0
- package/lib/paper/tools/check.js +79 -0
- package/lib/paper/tools/decision.d.ts +13 -0
- package/lib/paper/tools/decision.js +53 -0
- package/lib/paper/tools/defense.d.ts +11 -0
- package/lib/paper/tools/defense.js +74 -0
- package/lib/paper/tools/init.d.ts +23 -0
- package/lib/paper/tools/init.js +99 -0
- package/lib/paper/tools/lit.d.ts +27 -0
- package/lib/paper/tools/lit.js +236 -0
- package/lib/paper/tools/progress.d.ts +18 -0
- package/lib/paper/tools/progress.js +77 -0
- package/lib/paper/tools/review.d.ts +11 -0
- package/lib/paper/tools/review.js +41 -0
- package/lib/ppt/actions.d.ts +97 -0
- package/lib/ppt/actions.js +263 -0
- package/lib/ppt/check.d.ts +64 -0
- package/lib/ppt/check.js +185 -0
- package/lib/ppt/commands.d.ts +28 -0
- package/lib/ppt/commands.js +71 -0
- package/lib/ppt/convert.d.ts +146 -0
- package/lib/ppt/convert.js +423 -0
- package/lib/ppt/index.d.ts +45 -0
- package/lib/ppt/index.js +57 -0
- package/lib/ppt/marp.d.ts +74 -0
- package/lib/ppt/marp.js +165 -0
- package/lib/ppt/outline.d.ts +206 -0
- package/lib/ppt/outline.js +695 -0
- package/lib/shared/define-tool.d.ts +77 -0
- package/lib/shared/define-tool.js +235 -0
- package/lib/shared/fs-errors.d.ts +50 -0
- package/lib/shared/fs-errors.js +90 -0
- package/lib/shared/index.d.ts +31 -0
- package/lib/shared/index.js +32 -0
- package/lib/shared/spawn.d.ts +38 -0
- package/lib/shared/spawn.js +50 -0
- package/lib/shared/text.d.ts +14 -0
- package/lib/shared/text.js +26 -0
- package/package.json +82 -0
- package/skills/code-walkthrough/SKILL.md +94 -0
- package/skills/code-walkthrough/references/examples.md +129 -0
- package/skills/paper-dedup/SKILL.md +58 -0
- package/skills/paper-intake/SKILL.md +64 -0
- package/skills/thesis-citation/SKILL.md +67 -0
- package/skills/thesis-defense/SKILL.md +87 -0
- package/skills/thesis-eng-design/SKILL.md +97 -0
- package/skills/thesis-inquiry/SKILL.md +55 -0
- package/skills/thesis-inquiry/references/intent-spec-template.md +39 -0
- package/skills/thesis-literature/SKILL.md +56 -0
- package/skills/thesis-opener/SKILL.md +77 -0
- package/skills/thesis-pipeline/SKILL.md +121 -0
- package/skills/thesis-writing/SKILL.md +86 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 面向前模型的工具:`defense_code_status`、`defense_code_next`、`defense_code_update`。
|
|
3
|
+
*
|
|
4
|
+
* 这三个工具是模型操作引擎的手柄:分析架构、安排教学由模型完成,但每一次改动
|
|
5
|
+
* 都必须穿过引擎校验过的状态迁移,并立刻落盘。工具注册走 `ctx.tools`(只有在
|
|
6
|
+
* 组合了工具注册表时才注册);定义是纯对象,因此不需要任何 `@deepseek-ai/dsh-*`
|
|
7
|
+
* 的运行时导入。
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-thesis/codewalk
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { AiLearningEngine } from "./engine.js";
|
|
13
|
+
import { isLearningError } from "./errors.js";
|
|
14
|
+
import { runMilestoneGate } from "./gate.js";
|
|
15
|
+
const TODO_STATUS = ['pending', 'in_progress', 'done'];
|
|
16
|
+
const MILESTONE_STATUS = ['pending', 'in_progress', 'verified', 'failed'];
|
|
17
|
+
const QUESTION_STATUS = ['unasked', 'asked', 'passed', 'failed'];
|
|
18
|
+
const PHASES = ['analyzing', 'skeletonizing', 'learning', 'complete'];
|
|
19
|
+
const LEVELS = ['beginner', 'intermediate', 'advanced'];
|
|
20
|
+
const TODO_SCHEMA = {
|
|
21
|
+
type: 'object',
|
|
22
|
+
required: ['id', 'where', 'what', 'steps', 'difficulty', 'status'],
|
|
23
|
+
properties: {
|
|
24
|
+
id: { type: 'string', description: '稳定 id,供里程碑引用,如 "t3"。' },
|
|
25
|
+
where: { type: 'string', description: '文件与符号位置,如 "internal/store/task.go SaveTask"。' },
|
|
26
|
+
what: { type: 'string', description: '一句话说明这个方法的目的。' },
|
|
27
|
+
steps: { type: 'array', items: { type: 'string' }, description: '分步实现提示。' },
|
|
28
|
+
hint: { type: 'string', description: '可选的坑、设计理由与语言特性。' },
|
|
29
|
+
difficulty: { type: 'integer', enum: [1, 2, 3, 4], description: '1(读数据模型)到 4(分布式协同)。' },
|
|
30
|
+
status: { type: 'string', enum: [...TODO_STATUS] },
|
|
31
|
+
},
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
};
|
|
34
|
+
const QUESTION_SCHEMA = {
|
|
35
|
+
type: 'object',
|
|
36
|
+
required: ['ask', 'expected', 'hints', 'status', 'hintLevel'],
|
|
37
|
+
properties: {
|
|
38
|
+
ask: { type: 'string', description: '要向学生提出的追问。' },
|
|
39
|
+
expected: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: { type: 'string' },
|
|
42
|
+
description: '作为评分标准的期望答案要点。绝不逐字公布给学生。',
|
|
43
|
+
},
|
|
44
|
+
hints: { type: 'array', items: { type: 'string' }, description: '逐步升级的提示,在公布答案之前一次给一条。' },
|
|
45
|
+
status: { type: 'string', enum: [...QUESTION_STATUS] },
|
|
46
|
+
hintLevel: { type: 'integer', description: '已经给过多少条提示。' },
|
|
47
|
+
note: { type: 'string', description: '评分定分时记录的备注(可选)。' },
|
|
48
|
+
},
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
};
|
|
51
|
+
const MILESTONE_SCHEMA = {
|
|
52
|
+
type: 'object',
|
|
53
|
+
required: ['id', 'title', 'todos', 'questions'],
|
|
54
|
+
properties: {
|
|
55
|
+
id: { type: 'string', description: '稳定 id,如 "m1"。' },
|
|
56
|
+
title: { type: 'string', description: '简短的人类标题,如 "etcd 里的任务 CRUD"。' },
|
|
57
|
+
todos: { type: 'array', items: { type: 'string' }, description: '本里程碑覆盖的 todo id。' },
|
|
58
|
+
gate: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
required: ['build'],
|
|
61
|
+
properties: {
|
|
62
|
+
build: { type: 'array', items: { type: 'string' }, description: '命令与参数;缺省用语言级验证门。' },
|
|
63
|
+
},
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
questions: { type: 'array', items: QUESTION_SCHEMA },
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
};
|
|
70
|
+
/** 组合了工具注册表时,注册这三个面向前模型的工具。 */
|
|
71
|
+
export function registerTools(ctx, engine) {
|
|
72
|
+
const tools = ctx.get('tools');
|
|
73
|
+
if (tools === undefined)
|
|
74
|
+
return;
|
|
75
|
+
tools.register(statusTool(engine));
|
|
76
|
+
tools.register(nextTool(engine));
|
|
77
|
+
tools.register(updateTool(ctx, engine));
|
|
78
|
+
}
|
|
79
|
+
/** 解析一次工具调用的会话 cwd;缺失或没有演练状态时直接拒绝。 */
|
|
80
|
+
async function toolCwd(engine, exec) {
|
|
81
|
+
const cwd = exec.agent?.session.header.cwd;
|
|
82
|
+
if (cwd === undefined) {
|
|
83
|
+
throw new Error('learn_* 工具需要会话 cwd,但本次调用没有绑定 agent');
|
|
84
|
+
}
|
|
85
|
+
const state = await engine.load(cwd);
|
|
86
|
+
if (state === undefined) {
|
|
87
|
+
throw new Error(`${cwd} 下没有代码演练状态;请先执行 "/thesis-defense new <原始项目路径>"`);
|
|
88
|
+
}
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
function text(text) {
|
|
92
|
+
return [{ type: 'text', text }];
|
|
93
|
+
}
|
|
94
|
+
function statusTool(engine) {
|
|
95
|
+
return {
|
|
96
|
+
name: 'defense_code_status',
|
|
97
|
+
description: '读取本会话 cwd 下代码演练的状态:阶段、里程碑、todo 进度、未完成的追问、编译验证门记录。' +
|
|
98
|
+
'动手推进演练之前先用它看清当前位置。答辩前把系统讲清楚就从这里开始。',
|
|
99
|
+
parameters: { type: 'object', properties: {}, required: [], additionalProperties: false },
|
|
100
|
+
output: { schema: { type: 'string' }, render: (_args, value) => text(String(value)) },
|
|
101
|
+
async execute(_args, exec) {
|
|
102
|
+
const state = await toolCwd(engine, exec);
|
|
103
|
+
return engine.describe(state);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function nextTool(engine) {
|
|
108
|
+
return {
|
|
109
|
+
name: 'defense_code_next',
|
|
110
|
+
description: '返回本会话 cwd 下一次该做什么:当前里程碑连同它的 todo、编译验证门与追问' +
|
|
111
|
+
'(含用于评分的期望答案要点)。',
|
|
112
|
+
parameters: { type: 'object', properties: {}, required: [], additionalProperties: false },
|
|
113
|
+
output: { schema: { type: 'object' }, render: (_args, value) => text(JSON.stringify(value, null, 2)) },
|
|
114
|
+
async execute(_args, exec) {
|
|
115
|
+
const state = await toolCwd(engine, exec);
|
|
116
|
+
return nextPayload(state);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function nextPayload(state) {
|
|
121
|
+
const current = state.milestones.find((milestone) => milestone.status === 'in_progress');
|
|
122
|
+
const gate = current === undefined ? undefined : (current.gate ?? state.gates[state.origin.language]);
|
|
123
|
+
return {
|
|
124
|
+
phase: state.phase,
|
|
125
|
+
current: current === undefined
|
|
126
|
+
? null
|
|
127
|
+
: {
|
|
128
|
+
id: current.id,
|
|
129
|
+
title: current.title,
|
|
130
|
+
status: current.status,
|
|
131
|
+
gate,
|
|
132
|
+
todos: current.todos
|
|
133
|
+
.map((id) => state.todos.find((todo) => todo.id === id))
|
|
134
|
+
.filter((todo) => todo !== undefined),
|
|
135
|
+
questions: current.questions,
|
|
136
|
+
},
|
|
137
|
+
nextMilestone: state.milestones.find((milestone) => milestone.status === 'pending')?.id ?? null,
|
|
138
|
+
guidance: state.phase === 'analyzing'
|
|
139
|
+
? '先分析原始项目,用 defense_code_update 把架构记成 todo 与里程碑,然后推进阶段。'
|
|
140
|
+
: state.phase === 'skeletonizing'
|
|
141
|
+
? '开始搭骨架:剥掉非核心功能,留下带注释的 TODO 桩,用 /thesis-defense check 跑通编译验证门。'
|
|
142
|
+
: state.phase === 'learning'
|
|
143
|
+
? '推进当前里程碑:带学生走完它的 todo,逐个提出追问,并对照期望要点评分。'
|
|
144
|
+
: '全部里程碑已验证:总结学生现在能讲清哪些设计。',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function updateTool(ctx, engine) {
|
|
148
|
+
return {
|
|
149
|
+
name: 'defense_code_update',
|
|
150
|
+
description: '对本会话 cwd 的代码演练状态施加一次「经校验的」改动:登记 todo 与里程碑、迁移 todo 状态、推进阶段、' +
|
|
151
|
+
'驱动追问与评分、重试里程碑。每一次改动都会立即落盘。',
|
|
152
|
+
parameters: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
required: ['action'],
|
|
155
|
+
properties: {
|
|
156
|
+
action: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
enum: [
|
|
159
|
+
'create',
|
|
160
|
+
'upsert_todo',
|
|
161
|
+
'upsert_milestone',
|
|
162
|
+
'set_todo_status',
|
|
163
|
+
'advance_phase',
|
|
164
|
+
'ask_question',
|
|
165
|
+
'give_hint',
|
|
166
|
+
'assess_answer',
|
|
167
|
+
'retry_milestone',
|
|
168
|
+
'check_gate',
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
originPath: { type: 'string', description: '原始项目的绝对路径,action=create 时必填。' },
|
|
172
|
+
language: { type: 'string', description: 'action=create 时的语言键,如 "go"。' },
|
|
173
|
+
level: { type: 'string', enum: ['beginner', 'intermediate', 'advanced'], description: 'action=create 时的学生水平。' },
|
|
174
|
+
module: { type: 'string', description: 'action=create 时可选:只聚焦某个子模块。' },
|
|
175
|
+
todo: TODO_SCHEMA,
|
|
176
|
+
milestone: MILESTONE_SCHEMA,
|
|
177
|
+
todoId: { type: 'string' },
|
|
178
|
+
status: { type: 'string', enum: [...TODO_STATUS] },
|
|
179
|
+
phase: { type: 'string', enum: [...PHASES] },
|
|
180
|
+
milestoneId: { type: 'string' },
|
|
181
|
+
questionIndex: { type: 'integer' },
|
|
182
|
+
passed: { type: 'boolean' },
|
|
183
|
+
note: { type: 'string', description: 'assess_answer 的评分备注。' },
|
|
184
|
+
},
|
|
185
|
+
additionalProperties: false,
|
|
186
|
+
},
|
|
187
|
+
output: { schema: { type: 'string' }, render: (_args, value) => text(String(value)) },
|
|
188
|
+
async execute(args, exec) {
|
|
189
|
+
const cwd = exec.agent?.session.header.cwd;
|
|
190
|
+
if (cwd === undefined) {
|
|
191
|
+
throw new Error('learn_* 工具需要会话 cwd,但本次调用没有绑定 agent');
|
|
192
|
+
}
|
|
193
|
+
const update = args;
|
|
194
|
+
if (update.action === 'create') {
|
|
195
|
+
const state = await engine.create(cwd, {
|
|
196
|
+
origin: {
|
|
197
|
+
path: requireFieldValue(update, 'originPath', 'create'),
|
|
198
|
+
language: update.language ?? 'go',
|
|
199
|
+
},
|
|
200
|
+
scope: {
|
|
201
|
+
level: update.level ?? 'beginner',
|
|
202
|
+
...(update.module !== undefined ? { module: update.module } : {}),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return `已为 ${state.origin.path} 创建代码演练(${state.origin.language},${state.scope.level})。\n${engine.describe(state)}`;
|
|
206
|
+
}
|
|
207
|
+
const state = await toolCwd(engine, exec);
|
|
208
|
+
if (update.action === 'check_gate') {
|
|
209
|
+
return await checkGate(ctx, engine, exec, cwd, state, update);
|
|
210
|
+
}
|
|
211
|
+
const result = applyUpdate(engine, state, update);
|
|
212
|
+
await engine.save(cwd, state);
|
|
213
|
+
return result;
|
|
214
|
+
},
|
|
215
|
+
timeoutMs: 60_000,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/** 从模型侧跑一次里程碑验证门,并渲染结论。 */
|
|
219
|
+
async function checkGate(ctx, engine, exec, cwd, state, args) {
|
|
220
|
+
const milestone = pickCheckMilestone(state.milestones, args.milestoneId);
|
|
221
|
+
if (milestone === undefined) {
|
|
222
|
+
throw new Error(args.milestoneId === undefined
|
|
223
|
+
? '当前没有 in_progress 或 failed 的里程碑,没有可检查的对象'
|
|
224
|
+
: `未知里程碑 "${args.milestoneId}"`);
|
|
225
|
+
}
|
|
226
|
+
const settled = await runMilestoneGate(ctx, engine, cwd, state, milestone, exec.signal, exec.agent?.session);
|
|
227
|
+
const label = `验证门 "${settled.command.join(' ')}"`;
|
|
228
|
+
if (settled.interrupted)
|
|
229
|
+
return `${label} 被中断;该里程碑已标记为 failed`;
|
|
230
|
+
if (settled.outcome.kind === 'failed') {
|
|
231
|
+
return `${label} 失败(退出码 ${settled.exitCode});该里程碑已标记为 failed。\nstderr 末尾:\n${settled.stderr}`;
|
|
232
|
+
}
|
|
233
|
+
if (settled.outcome.kind === 'pending-questions') {
|
|
234
|
+
return `${label} 通过,但仍有 ${milestone.questions.filter((question) => question.status !== 'passed').length} 个问题未通过:先提问并评分,再重跑 check_gate。`;
|
|
235
|
+
}
|
|
236
|
+
return `${label} 通过 —— 里程碑 "${milestone.id}" 已验证。${state.phase === 'complete' ? ' 全部里程碑已验证:本次代码演练完成。' : ''}`;
|
|
237
|
+
}
|
|
238
|
+
function pickCheckMilestone(milestones, requestedId) {
|
|
239
|
+
if (requestedId !== undefined)
|
|
240
|
+
return milestones.find((milestone) => milestone.id === requestedId);
|
|
241
|
+
return (milestones.find((milestone) => milestone.status === 'in_progress') ??
|
|
242
|
+
milestones.find((milestone) => milestone.status === 'failed'));
|
|
243
|
+
}
|
|
244
|
+
function applyUpdate(engine, state, args) {
|
|
245
|
+
switch (args.action) {
|
|
246
|
+
case 'upsert_todo': {
|
|
247
|
+
if (args.todo === undefined)
|
|
248
|
+
throw new Error('upsert_todo 需要 "todo"');
|
|
249
|
+
engine.upsertTodo(state, args.todo);
|
|
250
|
+
return `已登记 todo ${args.todo.id}(${args.todo.where})`;
|
|
251
|
+
}
|
|
252
|
+
case 'upsert_milestone': {
|
|
253
|
+
if (args.milestone === undefined)
|
|
254
|
+
throw new Error('upsert_milestone 需要 "milestone"');
|
|
255
|
+
engine.upsertMilestone(state, args.milestone);
|
|
256
|
+
return `已登记里程碑 ${args.milestone.id}(${args.milestone.title}):${args.milestone.todos.length} 个 todo、${args.milestone.questions.length} 个问题`;
|
|
257
|
+
}
|
|
258
|
+
case 'set_todo_status': {
|
|
259
|
+
requireField(args, 'todoId', 'set_todo_status');
|
|
260
|
+
requireField(args, 'status', 'set_todo_status');
|
|
261
|
+
engine.setTodoStatus(state, args.todoId, args.status);
|
|
262
|
+
return `todo ${args.todoId} → ${args.status}`;
|
|
263
|
+
}
|
|
264
|
+
case 'advance_phase': {
|
|
265
|
+
requireField(args, 'phase', 'advance_phase');
|
|
266
|
+
engine.advancePhase(state, args.phase);
|
|
267
|
+
return `阶段 → ${state.phase}${state.phase === 'learning' ? `;当前里程碑:${state.milestones.find((milestone) => milestone.status === 'in_progress')?.id ?? '无'}` : ''}`;
|
|
268
|
+
}
|
|
269
|
+
case 'ask_question': {
|
|
270
|
+
requireField(args, 'milestoneId', 'ask_question');
|
|
271
|
+
requireField(args, 'questionIndex', 'ask_question');
|
|
272
|
+
engine.askQuestion(state, args.milestoneId, args.questionIndex);
|
|
273
|
+
return `问题 ${args.milestoneId}[${args.questionIndex}] 已标记为已提出`;
|
|
274
|
+
}
|
|
275
|
+
case 'give_hint': {
|
|
276
|
+
requireField(args, 'milestoneId', 'give_hint');
|
|
277
|
+
requireField(args, 'questionIndex', 'give_hint');
|
|
278
|
+
const hint = engine.giveHint(state, args.milestoneId, args.questionIndex);
|
|
279
|
+
return hint === null
|
|
280
|
+
? `${args.milestoneId}[${args.questionIndex}] 的提示阶梯已用尽;公布答案并让学生复述,然后再评分`
|
|
281
|
+
: `提示 ${args.milestoneId}[${args.questionIndex}]:${hint}`;
|
|
282
|
+
}
|
|
283
|
+
case 'assess_answer': {
|
|
284
|
+
requireField(args, 'milestoneId', 'assess_answer');
|
|
285
|
+
requireField(args, 'questionIndex', 'assess_answer');
|
|
286
|
+
requireField(args, 'passed', 'assess_answer');
|
|
287
|
+
engine.assessAnswer(state, args.milestoneId, args.questionIndex, args.passed, args.note);
|
|
288
|
+
return `问题 ${args.milestoneId}[${args.questionIndex}] 评分:${args.passed ? '通过' : '未通过'}`;
|
|
289
|
+
}
|
|
290
|
+
case 'retry_milestone': {
|
|
291
|
+
requireField(args, 'milestoneId', 'retry_milestone');
|
|
292
|
+
engine.retryMilestone(state, args.milestoneId);
|
|
293
|
+
return `里程碑 ${args.milestoneId} 已重新打开,可以再试一次`;
|
|
294
|
+
}
|
|
295
|
+
default:
|
|
296
|
+
throw new Error(`未知的更新动作 "${args.action}"`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function requireField(args, key, action) {
|
|
300
|
+
if (args[key] === undefined)
|
|
301
|
+
throw new Error(`${action} 需要 "${key}"`);
|
|
302
|
+
}
|
|
303
|
+
/** 取一个必填字符串字段(收窄类型),否则抛错。 */
|
|
304
|
+
function requireFieldValue(args, key, action) {
|
|
305
|
+
const value = args[key];
|
|
306
|
+
if (typeof value !== 'string' || value === '')
|
|
307
|
+
throw new Error(`${action} 需要 "${key}"`);
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
/** pre-step 注入用的随机 id 生成器;导出以便复用。 */
|
|
311
|
+
export function newMessageId() {
|
|
312
|
+
return randomUUID();
|
|
313
|
+
}
|
|
314
|
+
export { isLearningError };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 演练状态 schema:持久化在 `<cwd>/.paper/state.json`。
|
|
3
|
+
*
|
|
4
|
+
* 一个文件随演练骨架仓库一起走,因此进度能跨过每一次会话边界:里程碑、todo
|
|
5
|
+
* 状态、已提出的问题、评分备注、验证门记录全都落在这里,而不是留在对话记忆里。
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-thesis/codewalk
|
|
8
|
+
*/
|
|
9
|
+
/** 难度:1(读数据模型)到 4(分布式协同)。 */
|
|
10
|
+
export type Difficulty = 1 | 2 | 3 | 4;
|
|
11
|
+
/** 一个带注释的骨架桩:这段代码做什么(WHAT)以及怎么重建(HOW)。 */
|
|
12
|
+
export interface Todo {
|
|
13
|
+
/** 供里程碑引用的稳定 id,如 "t3"。 */
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/** 文件与符号位置,如 "internal/store/task.go SaveTask"。 */
|
|
16
|
+
readonly where: string;
|
|
17
|
+
/** 一句话说明这个方法的目的。 */
|
|
18
|
+
readonly what: string;
|
|
19
|
+
/** 分步实现提示。 */
|
|
20
|
+
readonly steps: readonly string[];
|
|
21
|
+
/** 可选的坑、设计理由与语言/库特性。 */
|
|
22
|
+
readonly hint?: string;
|
|
23
|
+
/** 难度评级。 */
|
|
24
|
+
readonly difficulty: Difficulty;
|
|
25
|
+
status: TodoStatus;
|
|
26
|
+
}
|
|
27
|
+
export type TodoStatus = 'pending' | 'in_progress' | 'done';
|
|
28
|
+
/** 一个验证门:一条必须成功的可执行命令行。 */
|
|
29
|
+
export interface GateSpec {
|
|
30
|
+
/** 命令与参数,如 ["go", "build", "./..."]。 */
|
|
31
|
+
build: string[];
|
|
32
|
+
}
|
|
33
|
+
/** 一次实际执行的验证门:里程碑确实被验证过的证据。 */
|
|
34
|
+
export interface GateRecord {
|
|
35
|
+
/** 该验证门所属的里程碑 id。 */
|
|
36
|
+
readonly milestone: string;
|
|
37
|
+
/** 实际执行的命令行。 */
|
|
38
|
+
readonly command: readonly string[];
|
|
39
|
+
/** 进程退出码。 */
|
|
40
|
+
readonly exitCode: number;
|
|
41
|
+
/** 捕获的 stdout,受 `maxCapturedOutput` 截断。 */
|
|
42
|
+
readonly stdout: string;
|
|
43
|
+
/** 捕获的 stderr,受 `maxCapturedOutput` 截断。 */
|
|
44
|
+
readonly stderr: string;
|
|
45
|
+
/** 墙钟耗时(毫秒)。 */
|
|
46
|
+
readonly durationMs: number;
|
|
47
|
+
/** 执行时刻(epoch 毫秒)。 */
|
|
48
|
+
readonly at: number;
|
|
49
|
+
}
|
|
50
|
+
/** 一个追问:含评分标准与升级提示。 */
|
|
51
|
+
export interface Question {
|
|
52
|
+
/** AI 应该向学生提出的问题。 */
|
|
53
|
+
readonly ask: string;
|
|
54
|
+
/**
|
|
55
|
+
* 作为评分标准的期望答案要点。AI 用它与学生的回答比对,而不是凭感觉打分。
|
|
56
|
+
* 绝不直接展示给学生;只能通过提示阶梯逐步接近。
|
|
57
|
+
*/
|
|
58
|
+
readonly expected: readonly string[];
|
|
59
|
+
/** 逐步升级的提示,在公布答案之前一次给一条。 */
|
|
60
|
+
readonly hints: readonly string[];
|
|
61
|
+
status: QuestionStatus;
|
|
62
|
+
/** 已经给过多少条提示(0..hints.length)。 */
|
|
63
|
+
hintLevel: number;
|
|
64
|
+
/** 评分定分时记录的备注。 */
|
|
65
|
+
note?: string;
|
|
66
|
+
}
|
|
67
|
+
export type QuestionStatus = 'unasked' | 'asked' | 'passed' | 'failed';
|
|
68
|
+
/** 一组相关 todo 加上它的验证门与问题:一个演练步骤。 */
|
|
69
|
+
export interface Milestone {
|
|
70
|
+
/** 稳定 id,如 "m1"。 */
|
|
71
|
+
readonly id: string;
|
|
72
|
+
/** 简短的人类标题,如 "etcd 里的任务 CRUD"。 */
|
|
73
|
+
readonly title: string;
|
|
74
|
+
/** 本里程碑覆盖的 todo id。 */
|
|
75
|
+
readonly todos: readonly string[];
|
|
76
|
+
/** 里程碑级验证门;缺省时回退到语言级验证门。 */
|
|
77
|
+
readonly gate?: GateSpec;
|
|
78
|
+
/** 本里程碑的追问。 */
|
|
79
|
+
readonly questions: readonly Question[];
|
|
80
|
+
status: MilestoneStatus;
|
|
81
|
+
}
|
|
82
|
+
export type MilestoneStatus = 'pending' | 'in_progress' | 'verified' | 'failed';
|
|
83
|
+
/** 工作流阶段;严格单向前进。 */
|
|
84
|
+
export type Phase = 'analyzing' | 'skeletonizing' | 'learning' | 'complete';
|
|
85
|
+
/** `create` 时选定的学生水平。 */
|
|
86
|
+
export type LearnerLevel = 'beginner' | 'intermediate' | 'advanced';
|
|
87
|
+
/** 整个持久化演练状态。 */
|
|
88
|
+
export interface LearningState {
|
|
89
|
+
readonly schemaVersion: 1;
|
|
90
|
+
phase: Phase;
|
|
91
|
+
/** 原始项目在哪里、用什么语言。 */
|
|
92
|
+
readonly origin: {
|
|
93
|
+
/** 学生要研究的原始项目绝对路径。 */
|
|
94
|
+
readonly path: string;
|
|
95
|
+
/** 选择验证门的语言键,如 "go"。 */
|
|
96
|
+
readonly language: string;
|
|
97
|
+
};
|
|
98
|
+
/** create 时捕获的范围配置。 */
|
|
99
|
+
readonly scope: {
|
|
100
|
+
/** 可选:只聚焦某个子模块,而不是整个项目。 */
|
|
101
|
+
readonly module?: string;
|
|
102
|
+
/** 学生水平校准。 */
|
|
103
|
+
readonly level: LearnerLevel;
|
|
104
|
+
};
|
|
105
|
+
/** 按演练顺序排列的里程碑。 */
|
|
106
|
+
milestones: Milestone[];
|
|
107
|
+
/** 全部 todo,隐式按 id 索引。 */
|
|
108
|
+
todos: Todo[];
|
|
109
|
+
/** create 时快照下来的语言验证门。 */
|
|
110
|
+
readonly gates: Readonly<Record<string, GateSpec>>;
|
|
111
|
+
/** 每一次执行的验证门,最新的在最后。 */
|
|
112
|
+
records: GateRecord[];
|
|
113
|
+
/** 最后一次保存的 epoch 毫秒。 */
|
|
114
|
+
updatedAt: number;
|
|
115
|
+
}
|
|
116
|
+
/** `AiLearningEngine.create` 的新状态输入。 */
|
|
117
|
+
export interface CreateStateInput {
|
|
118
|
+
readonly origin: LearningState['origin'];
|
|
119
|
+
readonly scope: LearningState['scope'];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 代码演练的部署配置(与 `src/config.ts` 的 `CodeWalkthroughOptions` 结构一致)。
|
|
123
|
+
*
|
|
124
|
+
* `src/config.ts` 的 `LearnOptions` 要求字段必填,可直接传给引擎;此处的可选
|
|
125
|
+
* 版本便于单测里只覆盖关心的字段,缺省值由引擎统一回退。
|
|
126
|
+
*/
|
|
127
|
+
export interface LearnOptions {
|
|
128
|
+
/** 会话 cwd 下存放状态文件的目录。 */
|
|
129
|
+
stateDir?: string;
|
|
130
|
+
/** 按语言名索引的验证门,如 "go"。 */
|
|
131
|
+
gates?: Record<string, GateSpec>;
|
|
132
|
+
/** 每条验证门输出流保留的最大字符数。 */
|
|
133
|
+
maxCapturedOutput?: number;
|
|
134
|
+
}
|
|
135
|
+
/** `AiLearningEngine.recordGate` 的结论。 */
|
|
136
|
+
export type GateOutcome = {
|
|
137
|
+
readonly kind: 'verified';
|
|
138
|
+
} | {
|
|
139
|
+
readonly kind: 'failed';
|
|
140
|
+
} | {
|
|
141
|
+
readonly kind: 'pending-questions';
|
|
142
|
+
};
|
|
143
|
+
export interface EngineStateSnapshot {
|
|
144
|
+
readonly state: LearningState;
|
|
145
|
+
readonly phase: Phase;
|
|
146
|
+
readonly current: Milestone | undefined;
|
|
147
|
+
readonly next: Milestone | undefined;
|
|
148
|
+
readonly gateCount: number;
|
|
149
|
+
readonly questionCount: number;
|
|
150
|
+
readonly doneQuestionCount: number;
|
|
151
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 斜杠命令的聚合层。
|
|
3
|
+
*
|
|
4
|
+
* `/thesis-defense` 同时覆盖**答辩幻灯**与**代码演练**两块能力,因此它的注册
|
|
5
|
+
* 放在装配层:`ppt/` 与 `codewalk/` 各自导出处理器,由本文件按子命令调度,
|
|
6
|
+
* 两个模块之间没有反向依赖(见 DESIGN.md 的分层规则)。
|
|
7
|
+
*
|
|
8
|
+
* 命令只做薄封装:解析输入 → 交给对应模块的纯逻辑入口,因此命令行为与工具完全一致。
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-thesis/commands
|
|
11
|
+
*/
|
|
12
|
+
import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands';
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
import type { PptOptions } from './config.ts';
|
|
15
|
+
import type { PptDeps } from './ppt/actions.ts';
|
|
16
|
+
/** 答辩命令两侧的子命令集合(仅用于错误提示)。 */
|
|
17
|
+
export declare const DEFENSE_COMMAND_HINT = "prepare | convert | check | guide [\u9875\u6570] [undergrad|master] [force] | new <\u539F\u59CB\u9879\u76EE\u8DEF\u5F84> [--lang go] [--level beginner] [--module pkg] | status | check [\u91CC\u7A0B\u7891]";
|
|
18
|
+
/**
|
|
19
|
+
* 注册 `/thesis-defense`:答辩幻灯 + 代码演练。
|
|
20
|
+
*
|
|
21
|
+
* 幻灯侧子命令:`prepare`(= outline)/ `convert` / `check` / `guide`;
|
|
22
|
+
* 代码演练侧子命令:`new` / `status` / `check`。`check` 两侧同名,按是否给出
|
|
23
|
+
* 里程碑参数区分——幻灯侧的 check 不接受位置参数。
|
|
24
|
+
*
|
|
25
|
+
* headless/ACP 组合没有命令面时静默跳过。
|
|
26
|
+
*/
|
|
27
|
+
export declare function registerDefenseCommand(ctx: Context, options: {
|
|
28
|
+
ppt: PptOptions;
|
|
29
|
+
}, deps?: PptDeps): void;
|
|
30
|
+
/** 按子命令把一次 `/thesis-defense` 调用分派到幻灯侧或代码演练侧。 */
|
|
31
|
+
export declare function dispatchDefenseCommand(ctx: Context, options: {
|
|
32
|
+
ppt: PptOptions;
|
|
33
|
+
}, invocation: CommandInvocation, deps?: PptDeps): Promise<CommandResult>;
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 斜杠命令的聚合层。
|
|
3
|
+
*
|
|
4
|
+
* `/thesis-defense` 同时覆盖**答辩幻灯**与**代码演练**两块能力,因此它的注册
|
|
5
|
+
* 放在装配层:`ppt/` 与 `codewalk/` 各自导出处理器,由本文件按子命令调度,
|
|
6
|
+
* 两个模块之间没有反向依赖(见 DESIGN.md 的分层规则)。
|
|
7
|
+
*
|
|
8
|
+
* 命令只做薄封装:解析输入 → 交给对应模块的纯逻辑入口,因此命令行为与工具完全一致。
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-thesis/commands
|
|
11
|
+
*/
|
|
12
|
+
import { CODEWALK_COMMAND_HINT, handleCodewalkCommand } from "./codewalk/commands.js";
|
|
13
|
+
import { handlePptCommand, PPT_COMMAND_DESCRIPTION, PPT_COMMAND_HINT, PPT_SUBCOMMANDS } from "./ppt/commands.js";
|
|
14
|
+
/** 答辩命令两侧的子命令集合(仅用于错误提示)。 */
|
|
15
|
+
export const DEFENSE_COMMAND_HINT = `${PPT_COMMAND_HINT} | ${CODEWALK_COMMAND_HINT}`;
|
|
16
|
+
/**
|
|
17
|
+
* 注册 `/thesis-defense`:答辩幻灯 + 代码演练。
|
|
18
|
+
*
|
|
19
|
+
* 幻灯侧子命令:`prepare`(= outline)/ `convert` / `check` / `guide`;
|
|
20
|
+
* 代码演练侧子命令:`new` / `status` / `check`。`check` 两侧同名,按是否给出
|
|
21
|
+
* 里程碑参数区分——幻灯侧的 check 不接受位置参数。
|
|
22
|
+
*
|
|
23
|
+
* headless/ACP 组合没有命令面时静默跳过。
|
|
24
|
+
*/
|
|
25
|
+
export function registerDefenseCommand(ctx, options, deps = {}) {
|
|
26
|
+
const commands = ctx.get('commands');
|
|
27
|
+
if (commands === undefined)
|
|
28
|
+
return;
|
|
29
|
+
commands.register({
|
|
30
|
+
name: 'thesis-defense',
|
|
31
|
+
description: `答辩准备:幻灯与演练。${PPT_COMMAND_DESCRIPTION};代码演练:new <原始项目路径> | status | check [里程碑]`,
|
|
32
|
+
input: { hint: DEFENSE_COMMAND_HINT },
|
|
33
|
+
handler: async (invocation) => {
|
|
34
|
+
return await dispatchDefenseCommand(ctx, options, invocation, deps);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** 按子命令把一次 `/thesis-defense` 调用分派到幻灯侧或代码演练侧。 */
|
|
39
|
+
export async function dispatchDefenseCommand(ctx, options, invocation, deps = {}) {
|
|
40
|
+
const tokens = invocation.rawInput.trim().split(/\s+/).filter(token => token !== '');
|
|
41
|
+
const sub = (tokens[0] ?? 'prepare').toLowerCase();
|
|
42
|
+
if (PPT_SUBCOMMANDS.includes(sub)) {
|
|
43
|
+
return await handlePptCommand(ctx.fs, options, invocation, deps);
|
|
44
|
+
}
|
|
45
|
+
if (sub === 'new' || sub === 'status' || (sub === 'check' && tokens.length > 1)) {
|
|
46
|
+
const engine = ctx.get('paperCodeWalkthrough');
|
|
47
|
+
if (engine === undefined) {
|
|
48
|
+
return { kind: 'error', text: '代码演练引擎未装配:请确认插件配置里 codeWalkthrough 已启用。' };
|
|
49
|
+
}
|
|
50
|
+
return await handleCodewalkCommand(ctx, engine, invocation);
|
|
51
|
+
}
|
|
52
|
+
if (sub === 'check') {
|
|
53
|
+
// 无里程碑参数的 check 属于幻灯侧质量检查。
|
|
54
|
+
return await handlePptCommand(ctx.fs, options, invocation, deps);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
kind: 'error',
|
|
58
|
+
text: `未知子命令「${sub}」。可用:${DEFENSE_COMMAND_HINT}`,
|
|
59
|
+
};
|
|
60
|
+
}
|