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,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 入口闸门的判定规则(纯函数,可独立单测)。
|
|
3
|
+
*
|
|
4
|
+
* 闸门要回答两个问题:
|
|
5
|
+
* 1. 用户这句话是不是「要开始写论文」?(而不是在聊天、问问题、改格式)
|
|
6
|
+
* 2. 论文工作区里的意图规格立住了没有?(缺失 / 仍有阻塞项 / 已就绪)
|
|
7
|
+
*
|
|
8
|
+
* 只有 1 为真且 2 为「未就绪」时才注入追问指令——宁漏勿错:闸门的误报代价是
|
|
9
|
+
* 多问一句「要不要先确认要求」,漏报代价是整章按错误要求重写。
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-thesis/intake/gate-rules
|
|
12
|
+
*/
|
|
13
|
+
/** 「要开始写正文/开题报告」的意图模式(中英文)。 */
|
|
14
|
+
const WRITING_INTENT_PATTERNS = [
|
|
15
|
+
// 中文:写/生成/起草 + 论文/正文/章节/开题/中期
|
|
16
|
+
/(写|生成|起草|撰写|接着写|继续写|开始写)[^。!?\n]{0,12}(论文|正文|章节|第[一二三四五六七八九十0-9]{1,3}章|开题报告|任务书|中期报告|文献综述|摘要)/,
|
|
17
|
+
// 中文:直接点名章节文件
|
|
18
|
+
/\b0[1-7]-[^\s,。]{2,12}\.md\b/,
|
|
19
|
+
// 英文:write/draft/generate + thesis/paper/chapter
|
|
20
|
+
/\b(write|draft|generate|compose)\b[^.\n]{0,24}\b(thesis|paper|chapter|abstract|proposal)\b/i,
|
|
21
|
+
];
|
|
22
|
+
/** 用户显式授权「不用问,直接写」的短语:命中即放行。 */
|
|
23
|
+
const BYPASS_PATTERNS = [
|
|
24
|
+
/直接(写|生成|做)/,
|
|
25
|
+
/不用问|别问了|不需要问|跳过追问|跳过提问|无需澄清/,
|
|
26
|
+
/just (write|do it)/i,
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* 意图规格里代表「还没定下来」的文本标记。
|
|
30
|
+
*
|
|
31
|
+
* 主判据是**结构**(见 {@link judgeSpec} 里对「阻塞项」一节的读取),这几个字面量
|
|
32
|
+
* 只作为回退:规格可能是用户手写的、或由旧版本渲染的,那时没有标准小节。
|
|
33
|
+
*/
|
|
34
|
+
const BLOCKING_MARKERS = [
|
|
35
|
+
'未知(需补)',
|
|
36
|
+
'- [ ]',
|
|
37
|
+
];
|
|
38
|
+
/** 「阻塞项」小节的标题行(由 `intake/spec.ts` 渲染;连标题整行一起匹配,避免把标题后半截当成内容行)。 */
|
|
39
|
+
const BLOCKER_HEADING = /^##[^\n]*阻塞项[^\n]*/m;
|
|
40
|
+
/** 阻塞项小节里的「无」声明。 */
|
|
41
|
+
const NO_BLOCKER_TEXT = /^无[。..]/;
|
|
42
|
+
/** 是否在要求「开始写」。 */
|
|
43
|
+
export function isWritingIntent(text) {
|
|
44
|
+
return WRITING_INTENT_PATTERNS.some(pattern => pattern.test(text));
|
|
45
|
+
}
|
|
46
|
+
/** 用户是否显式授权跳过追问。 */
|
|
47
|
+
export function isBypassed(text) {
|
|
48
|
+
return BYPASS_PATTERNS.some(pattern => pattern.test(text));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 判定意图规格的状态。
|
|
52
|
+
*
|
|
53
|
+
* 判据顺序(越靠前越权威):
|
|
54
|
+
* 1. **结构判据**:`## 阻塞项…` 小节的第一行内容——`无。…` 表示已收敛,
|
|
55
|
+
* 是表格/其它内容则表示仍有必答项未答或格式待修正。这条与 `intake/spec.ts`
|
|
56
|
+
* 的渲染契约绑定,并且由 `tests/intake-convergence.test.ts` 端到端守住。
|
|
57
|
+
* 2. **文本回退**:`未知(需补)` / 未勾选项 `- [ ]`(用户手写或旧版规格)。
|
|
58
|
+
*
|
|
59
|
+
* @param specText - 规格文件内容;`undefined` 表示文件不存在。
|
|
60
|
+
*/
|
|
61
|
+
export function judgeSpec(specText) {
|
|
62
|
+
if (specText === undefined || specText.trim() === '')
|
|
63
|
+
return { kind: 'missing' };
|
|
64
|
+
const headingMatch = BLOCKER_HEADING.exec(specText);
|
|
65
|
+
if (headingMatch !== null) {
|
|
66
|
+
const rest = specText.slice(headingMatch.index + headingMatch[0].length);
|
|
67
|
+
const firstLine = rest
|
|
68
|
+
.split('\n')
|
|
69
|
+
.map(line => line.trim())
|
|
70
|
+
.find(line => line !== '' && !line.startsWith('<!--'));
|
|
71
|
+
if (firstLine !== undefined && !NO_BLOCKER_TEXT.test(firstLine)) {
|
|
72
|
+
return { kind: 'blocked', reason: `规格的「阻塞项」一节非空:${firstLine.slice(0, 80)}` };
|
|
73
|
+
}
|
|
74
|
+
// 小节存在且为空/为「无」:继续走文本回退,防止同文档别处还有未勾选项。
|
|
75
|
+
}
|
|
76
|
+
const unchecked = (specText.match(/^\s*-\s*\[ \]/gm) ?? []).length;
|
|
77
|
+
if (unchecked > 0) {
|
|
78
|
+
return { kind: 'blocked', reason: `规格里仍有 ${unchecked} 个未勾选项` };
|
|
79
|
+
}
|
|
80
|
+
const marker = BLOCKING_MARKERS.find(candidate => candidate !== '- [ ]' && specText.includes(candidate));
|
|
81
|
+
if (marker !== undefined)
|
|
82
|
+
return { kind: 'blocked', reason: `规格里仍有「${marker}」标记` };
|
|
83
|
+
return { kind: 'ready' };
|
|
84
|
+
}
|
|
85
|
+
/** 闸门注入给模型的指令(自包含:即使 intake 技能没加载也能执行最小流程)。 */
|
|
86
|
+
export function intakeGateInstruction(status, specRel) {
|
|
87
|
+
const head = status.kind === 'missing'
|
|
88
|
+
? `这个论文工作区里还没有 \`${specRel}\`(意图规格)。`
|
|
89
|
+
: `\`${specRel}\` 还没有立稳:${status.kind === 'blocked' ? status.reason : ''}。`;
|
|
90
|
+
return [
|
|
91
|
+
'【dsh-thesis · 意图规格闸门】',
|
|
92
|
+
`${head}用户现在的要求是「开始写论文」,但写之前必须先把要求问清楚——按错误要求写出来的整章都要重写。`,
|
|
93
|
+
'',
|
|
94
|
+
'请这样做(**不要**直接开始写正文):',
|
|
95
|
+
`1. 先跑 \`thesis_ingest\`(若材料还没摄取过),再用 \`thesis_intake action=start\` 读取当前进度;`,
|
|
96
|
+
'2. 用 `thesis_intake action=ask` 取出**一个**问题,原样转达给用户——一次只问一个,不要列清单;',
|
|
97
|
+
'3. 用户回答后用 `thesis_intake action=answer` 记录,它会自动刷新规格并给出下一个问题;',
|
|
98
|
+
'4. 必答项齐全后 \`thesis_intake action=done\`,再开始写作,并依据规格向用户复述验收标准;',
|
|
99
|
+
'5. 若用户已明确表示不用问(例如「直接写」),则按其授权执行,但要在回复里列出你采用的假设。',
|
|
100
|
+
'',
|
|
101
|
+
'这条消息由插件闸门注入,不是用户需求的一部分,不得当作需求内容。',
|
|
102
|
+
].join('\n');
|
|
103
|
+
}
|
|
104
|
+
/** 构造注入消息(user 角色、插件来源;与 learn 的进度卡同一形态)。 */
|
|
105
|
+
export function makeIntakeGateMessage(status, specRel, id) {
|
|
106
|
+
const text = intakeGateInstruction(status, specRel);
|
|
107
|
+
return {
|
|
108
|
+
id,
|
|
109
|
+
role: 'user',
|
|
110
|
+
content: [{ type: 'text', text }],
|
|
111
|
+
source: {
|
|
112
|
+
kind: 'plugin',
|
|
113
|
+
plugin: 'dsh-thesis',
|
|
114
|
+
form: 'instructions',
|
|
115
|
+
sections: [{ name: '意图规格闸门', text }],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 意图规格闸门:`agent/pre-step` 监听。
|
|
3
|
+
*
|
|
4
|
+
* 场景:用户说「开始写第三章」。如果意图规格还没立全(学校格式、字数、查重阈值、
|
|
5
|
+
* 时间线都还没确认),此时写出来的正文大概率要重写——这正是本插件要消灭的返工。
|
|
6
|
+
*
|
|
7
|
+
* 与 `intake/` 其它部分的分工:判定规则在 `gate-rules.ts`(纯函数),这里只做
|
|
8
|
+
* 「读规格 → 判定 → 注入」的胶水。三条设计底线:
|
|
9
|
+
* 便宜(只在用户新消息命中写作意图时才读文件,且按 agent 缓存 60 秒)、
|
|
10
|
+
* 面向人(只提醒模型去问,绝不替用户作答)、有终止条件(规格就绪即静默,
|
|
11
|
+
* 用户说「直接写」即放行)。
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-thesis/intake/gate
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
/**
|
|
17
|
+
* 安装意图规格闸门。
|
|
18
|
+
*
|
|
19
|
+
* `status === undefined` 表示「不在论文工作区里」,此时不介入——插件在别人的仓库、
|
|
20
|
+
* 纯聊天场景里都必须安静。
|
|
21
|
+
*/
|
|
22
|
+
export declare function installIntakeGate(ctx: Context, options: {
|
|
23
|
+
specRel: string;
|
|
24
|
+
}): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 意图规格闸门:`agent/pre-step` 监听。
|
|
3
|
+
*
|
|
4
|
+
* 场景:用户说「开始写第三章」。如果意图规格还没立全(学校格式、字数、查重阈值、
|
|
5
|
+
* 时间线都还没确认),此时写出来的正文大概率要重写——这正是本插件要消灭的返工。
|
|
6
|
+
*
|
|
7
|
+
* 与 `intake/` 其它部分的分工:判定规则在 `gate-rules.ts`(纯函数),这里只做
|
|
8
|
+
* 「读规格 → 判定 → 注入」的胶水。三条设计底线:
|
|
9
|
+
* 便宜(只在用户新消息命中写作意图时才读文件,且按 agent 缓存 60 秒)、
|
|
10
|
+
* 面向人(只提醒模型去问,绝不替用户作答)、有终止条件(规格就绪即静默,
|
|
11
|
+
* 用户说「直接写」即放行)。
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-thesis/intake/gate
|
|
14
|
+
*/
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { findThesisRoot } from "../paper/lib/project.js";
|
|
17
|
+
import { isMissingError } from "../shared/fs-errors.js";
|
|
18
|
+
import { textOfBlocks } from "../shared/text.js";
|
|
19
|
+
import { isBypassed, isWritingIntent, judgeSpec, makeIntakeGateMessage } from "./gate-rules.js";
|
|
20
|
+
/** 每个 agent 的规格状态缓存时长(毫秒):避免每一步都读盘。 */
|
|
21
|
+
const CACHE_TTL_MS = 60_000;
|
|
22
|
+
/**
|
|
23
|
+
* 安装意图规格闸门。
|
|
24
|
+
*
|
|
25
|
+
* `status === undefined` 表示「不在论文工作区里」,此时不介入——插件在别人的仓库、
|
|
26
|
+
* 纯聊天场景里都必须安静。
|
|
27
|
+
*/
|
|
28
|
+
export function installIntakeGate(ctx, options) {
|
|
29
|
+
const cache = new WeakMap();
|
|
30
|
+
ctx.on('agent/pre-step', async (payload, next) => {
|
|
31
|
+
const downstream = await next();
|
|
32
|
+
if (downstream.kind !== 'enter')
|
|
33
|
+
return downstream;
|
|
34
|
+
const messages = payload.messages;
|
|
35
|
+
const last = messages[messages.length - 1];
|
|
36
|
+
// 只对「用户刚发出的新消息」反应:工具结果、模型消息、插件注入都不触发。
|
|
37
|
+
if (last === undefined || last.source?.kind !== 'user')
|
|
38
|
+
return downstream;
|
|
39
|
+
const text = textOfBlocks(last.content);
|
|
40
|
+
if (!isWritingIntent(text) || isBypassed(text))
|
|
41
|
+
return downstream;
|
|
42
|
+
const agent = payload.agent;
|
|
43
|
+
const cwd = payload.agent?.session?.header?.cwd;
|
|
44
|
+
if (agent === undefined || typeof cwd !== 'string' || cwd === '')
|
|
45
|
+
return downstream;
|
|
46
|
+
const cached = cache.get(agent);
|
|
47
|
+
let status;
|
|
48
|
+
if (cached !== undefined && Date.now() - cached.at < CACHE_TTL_MS) {
|
|
49
|
+
status = cached.status;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
status = await readSpecStatus(ctx, cwd, options.specRel, payload.signal);
|
|
53
|
+
cache.set(agent, { at: Date.now(), status });
|
|
54
|
+
}
|
|
55
|
+
if (status === undefined || status.kind === 'ready')
|
|
56
|
+
return downstream;
|
|
57
|
+
const injected = makeIntakeGateMessage(status, options.specRel, `paper-intake-gate-${Date.now()}`);
|
|
58
|
+
return { kind: 'enter', messages: [...downstream.messages, injected] };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 读取规格状态。工作区不存在时返回 `undefined`(不介入);规格文件不存在返回 `missing`。
|
|
63
|
+
* 读盘异常一律视为「不介入」——闸门永不把异常抛进 agent 循环。
|
|
64
|
+
*/
|
|
65
|
+
async function readSpecStatus(ctx, cwd, specRel, signal) {
|
|
66
|
+
try {
|
|
67
|
+
const root = await findThesisRoot(ctx.fs, cwd, signal);
|
|
68
|
+
if (root === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
let body;
|
|
71
|
+
try {
|
|
72
|
+
body = await ctx.fs.readText(await ctx.fs.resolve(join(root, specRel), { signal }), signal);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// 只有「规格文件确实不存在」才当作 missing(继续提醒去追问);
|
|
76
|
+
// 读失败(权限/IO/取消)时无话可说,选择不介入——闸门是增益功能,绝不误报。
|
|
77
|
+
if (!isMissingError(error))
|
|
78
|
+
return undefined;
|
|
79
|
+
body = undefined;
|
|
80
|
+
}
|
|
81
|
+
return judgeSpec(body);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `thesis_intake`:材料到手后的逐题追问(一次一问 → 可证伪意图规格)。
|
|
3
|
+
*
|
|
4
|
+
* 产品链路:`thesis_ingest` 把学校模板/论文要求/实验数据/代码/参考文献读进工作区 →
|
|
5
|
+
* 本工具**逐题追问**,把模糊要求变成可检查的条款 → 落盘 `00-管理/意图规格.md` →
|
|
6
|
+
* 后续写作/格式检查/降重/PPT/答辩**只依据该规格**。
|
|
7
|
+
*
|
|
8
|
+
* 三条硬要求(全部在代码里强制,不靠模型自觉):
|
|
9
|
+
* 1. **一次只问一个**:任何 action 的返回里最多出现一个问题;
|
|
10
|
+
* 2. **规格永远最新**:每次 `answer`/`skip` 之后立即重绘并原子落盘规格;
|
|
11
|
+
* 3. **必答项不齐不许 `done`**:`done` 会拒绝并列出缺口,绝不放水。
|
|
12
|
+
*
|
|
13
|
+
* 本文件是**执行层 + 装配入口**:执行层零 `@deepseek-ai/*` 运行时导入(只借用它们的类型),
|
|
14
|
+
* 因此测试可以直接用假 fs 驱动 {@link runIntake};`registerIntake` 只在装配时调用宿主能力。
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-thesis/intake
|
|
17
|
+
*/
|
|
18
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
19
|
+
import { type IntakeArgs, type IntakeSubOptions } from './definitions.ts';
|
|
20
|
+
import { type IntakeContext, type IntakeQuestion } from './questions.ts';
|
|
21
|
+
import { type FileSystemLike, type IntakeIo, type IntakeState } from './state.ts';
|
|
22
|
+
/** 找不到工作区的统一指引(不抛裸异常)。 */
|
|
23
|
+
export declare function requireThesisRoot(fs: FileSystemLike, cwd: string | undefined, signal?: AbortSignal): Promise<string | null>;
|
|
24
|
+
/** 载入追问上下文:材料清单 + 台账阶段(都读不到就退化为默认值)。 */
|
|
25
|
+
export declare function loadContext(io: IntakeIo, root: string): Promise<IntakeContext>;
|
|
26
|
+
/** 规格相对路径:配置为空时回退到默认位置。 */
|
|
27
|
+
export declare function specRelOf(intake: IntakeSubOptions): string;
|
|
28
|
+
/** 进度一句话:必答项已答数 + 未答 + 格式待修正。 */
|
|
29
|
+
export declare function progressLine(state: IntakeState): string;
|
|
30
|
+
/**
|
|
31
|
+
* 执行一次 `thesis_intake` 调用(纯 I/O,零 cordis 依赖)。
|
|
32
|
+
*
|
|
33
|
+
* @param sessionAsked 本次会话已经问过的问题数(由调用方累加,用于 `maxQuestions` 上限)。
|
|
34
|
+
*/
|
|
35
|
+
export declare function runIntake(fs: FileSystemLike, intake: IntakeSubOptions, args: IntakeArgs, cwd: string | undefined, signal?: AbortSignal, sessionAsked?: number): Promise<string>;
|
|
36
|
+
/** 供其它模块/测试复用:一次裁剪后的待问队列(受 maxQuestions 限制)。 */
|
|
37
|
+
export declare function pendingQueue(state: IntakeState, ctx: IntakeContext, maxQuestions: number): IntakeQuestion[];
|
|
38
|
+
/**
|
|
39
|
+
* 注册 `thesis_intake`(单个工具、7 个 action)。
|
|
40
|
+
*
|
|
41
|
+
* 工具定义走仓库统一的 `definePaperTool`(生产 = 宿主真实 `defineTool`,
|
|
42
|
+
* 离线 = `shared/define-tool.ts` 的等价实现),因此本模块在测试环境里可加载。
|
|
43
|
+
* 工作区根由 `findThesisRoot` 从会话 cwd 向上探测 `00-管理/进度台账.md`;
|
|
44
|
+
* 找不到时工具返回明确指引(先 `thesis_init`),不抛裸异常。
|
|
45
|
+
*/
|
|
46
|
+
export declare function registerIntake(ctx: Context, options: {
|
|
47
|
+
intake: IntakeSubOptions;
|
|
48
|
+
}): void;
|
|
49
|
+
/** 命令执行上下文的最小面(宿主 `CommandInvocation` 在结构上满足它)。 */
|
|
50
|
+
export interface IntakeExecLike {
|
|
51
|
+
readonly signal?: AbortSignal;
|
|
52
|
+
readonly agent?: {
|
|
53
|
+
readonly session: {
|
|
54
|
+
readonly header: {
|
|
55
|
+
readonly cwd?: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** 命令返回值(宿主 `CommandResult` 的形状)。 */
|
|
61
|
+
export interface IntakeCommandResult {
|
|
62
|
+
readonly kind: 'success' | 'error';
|
|
63
|
+
readonly text?: string;
|
|
64
|
+
}
|
|
65
|
+
/** 把 `/thesis-intake` 的一行输入翻译成追问动作。 */
|
|
66
|
+
export declare function parseCommandInput(raw: string): IntakeArgs;
|
|
67
|
+
/**
|
|
68
|
+
* 注册命令 `/thesis-intake`(命令名只允许 `[a-z][a-z0-9_-]*`)。
|
|
69
|
+
*
|
|
70
|
+
* 用法:`/thesis-intake` 取下一个问题;`/thesis-intake status|spec|done|start`;
|
|
71
|
+
* `/thesis-intake answer school.name XX大学`;`/thesis-intake skip ref.samples 暂时没有`。
|
|
72
|
+
* `ctx.commands` 不可用(未装配命令服务的宿主)时静默跳过,不影响工具能力。
|
|
73
|
+
*/
|
|
74
|
+
export declare function registerIntakeCommand(ctx: Context, options: {
|
|
75
|
+
intake: IntakeSubOptions;
|
|
76
|
+
}): boolean;
|