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.
Files changed (188) hide show
  1. package/DESIGN.md +390 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +10 -0
  4. package/README.md +253 -0
  5. package/cordis.patch.yml +17 -0
  6. package/docs/host-api.md +640 -0
  7. package/lib/codewalk/commands.d.ts +23 -0
  8. package/lib/codewalk/commands.js +168 -0
  9. package/lib/codewalk/engine.d.ts +108 -0
  10. package/lib/codewalk/engine.js +449 -0
  11. package/lib/codewalk/errors.d.ts +31 -0
  12. package/lib/codewalk/errors.js +20 -0
  13. package/lib/codewalk/fs-types.d.ts +34 -0
  14. package/lib/codewalk/fs-types.js +10 -0
  15. package/lib/codewalk/gate.d.ts +30 -0
  16. package/lib/codewalk/gate.js +60 -0
  17. package/lib/codewalk/host-types.d.ts +198 -0
  18. package/lib/codewalk/host-types.js +11 -0
  19. package/lib/codewalk/prestep.d.ts +17 -0
  20. package/lib/codewalk/prestep.js +62 -0
  21. package/lib/codewalk/register.d.ts +23 -0
  22. package/lib/codewalk/register.js +31 -0
  23. package/lib/codewalk/skill.d.ts +15 -0
  24. package/lib/codewalk/skill.js +57 -0
  25. package/lib/codewalk/tools.d.ts +18 -0
  26. package/lib/codewalk/tools.js +314 -0
  27. package/lib/codewalk/types.d.ts +151 -0
  28. package/lib/codewalk/types.js +9 -0
  29. package/lib/commands.d.ts +33 -0
  30. package/lib/commands.js +60 -0
  31. package/lib/config.d.ts +125 -0
  32. package/lib/config.js +139 -0
  33. package/lib/dedup/commands.d.ts +21 -0
  34. package/lib/dedup/commands.js +93 -0
  35. package/lib/dedup/index.d.ts +75 -0
  36. package/lib/dedup/index.js +331 -0
  37. package/lib/dedup/normalize.d.ts +125 -0
  38. package/lib/dedup/normalize.js +329 -0
  39. package/lib/dedup/report.d.ts +114 -0
  40. package/lib/dedup/report.js +309 -0
  41. package/lib/dedup/rewrite.d.ts +71 -0
  42. package/lib/dedup/rewrite.js +228 -0
  43. package/lib/dedup/similarity.d.ts +169 -0
  44. package/lib/dedup/similarity.js +323 -0
  45. package/lib/gates/dangerous.d.ts +22 -0
  46. package/lib/gates/dangerous.js +47 -0
  47. package/lib/gates/detect.d.ts +54 -0
  48. package/lib/gates/detect.js +106 -0
  49. package/lib/gates/messages.d.ts +39 -0
  50. package/lib/gates/messages.js +74 -0
  51. package/lib/gates/prompt.d.ts +21 -0
  52. package/lib/gates/prompt.js +56 -0
  53. package/lib/gates/writing-intent.d.ts +49 -0
  54. package/lib/gates/writing-intent.js +68 -0
  55. package/lib/index.d.ts +36 -0
  56. package/lib/index.js +76 -0
  57. package/lib/ingest/code.d.ts +71 -0
  58. package/lib/ingest/code.js +356 -0
  59. package/lib/ingest/commands.d.ts +23 -0
  60. package/lib/ingest/commands.js +74 -0
  61. package/lib/ingest/docx.d.ts +29 -0
  62. package/lib/ingest/docx.js +92 -0
  63. package/lib/ingest/index.d.ts +179 -0
  64. package/lib/ingest/index.js +640 -0
  65. package/lib/ingest/ooxml.d.ts +58 -0
  66. package/lib/ingest/ooxml.js +393 -0
  67. package/lib/ingest/pdf.d.ts +86 -0
  68. package/lib/ingest/pdf.js +1016 -0
  69. package/lib/ingest/pptx.d.ts +32 -0
  70. package/lib/ingest/pptx.js +116 -0
  71. package/lib/ingest/text.d.ts +25 -0
  72. package/lib/ingest/text.js +180 -0
  73. package/lib/ingest/types.d.ts +48 -0
  74. package/lib/ingest/types.js +47 -0
  75. package/lib/ingest/xlsx.d.ts +72 -0
  76. package/lib/ingest/xlsx.js +411 -0
  77. package/lib/ingest/zip.d.ts +76 -0
  78. package/lib/ingest/zip.js +393 -0
  79. package/lib/intake/definitions.d.ts +65 -0
  80. package/lib/intake/definitions.js +42 -0
  81. package/lib/intake/gate-rules.d.ts +57 -0
  82. package/lib/intake/gate-rules.js +118 -0
  83. package/lib/intake/gate.d.ts +24 -0
  84. package/lib/intake/gate.js +86 -0
  85. package/lib/intake/index.d.ts +76 -0
  86. package/lib/intake/index.js +468 -0
  87. package/lib/intake/questions.d.ts +169 -0
  88. package/lib/intake/questions.js +720 -0
  89. package/lib/intake/spec.d.ts +66 -0
  90. package/lib/intake/spec.js +221 -0
  91. package/lib/intake/state.d.ts +192 -0
  92. package/lib/intake/state.js +420 -0
  93. package/lib/memory/recall.d.ts +69 -0
  94. package/lib/memory/recall.js +177 -0
  95. package/lib/memory/register.d.ts +37 -0
  96. package/lib/memory/register.js +52 -0
  97. package/lib/memory/service.d.ts +39 -0
  98. package/lib/memory/service.js +39 -0
  99. package/lib/memory/store.d.ts +45 -0
  100. package/lib/memory/store.js +123 -0
  101. package/lib/memory/tools.d.ts +19 -0
  102. package/lib/memory/tools.js +195 -0
  103. package/lib/memory/types.d.ts +55 -0
  104. package/lib/memory/types.js +6 -0
  105. package/lib/paper/commands.d.ts +13 -0
  106. package/lib/paper/commands.js +84 -0
  107. package/lib/paper/lib/aicheck.d.ts +38 -0
  108. package/lib/paper/lib/aicheck.js +241 -0
  109. package/lib/paper/lib/bibtex.d.ts +21 -0
  110. package/lib/paper/lib/bibtex.js +114 -0
  111. package/lib/paper/lib/check.d.ts +32 -0
  112. package/lib/paper/lib/check.js +215 -0
  113. package/lib/paper/lib/defense.d.ts +48 -0
  114. package/lib/paper/lib/defense.js +220 -0
  115. package/lib/paper/lib/docx.d.ts +35 -0
  116. package/lib/paper/lib/docx.js +213 -0
  117. package/lib/paper/lib/layout.d.ts +41 -0
  118. package/lib/paper/lib/layout.js +572 -0
  119. package/lib/paper/lib/ledger.d.ts +112 -0
  120. package/lib/paper/lib/ledger.js +349 -0
  121. package/lib/paper/lib/lit-api.d.ts +59 -0
  122. package/lib/paper/lib/lit-api.js +296 -0
  123. package/lib/paper/lib/lit-cache.d.ts +32 -0
  124. package/lib/paper/lib/lit-cache.js +91 -0
  125. package/lib/paper/lib/project.d.ts +29 -0
  126. package/lib/paper/lib/project.js +93 -0
  127. package/lib/paper/lib/review.d.ts +37 -0
  128. package/lib/paper/lib/review.js +194 -0
  129. package/lib/paper/lib/zip.d.ts +14 -0
  130. package/lib/paper/lib/zip.js +124 -0
  131. package/lib/paper/register.d.ts +20 -0
  132. package/lib/paper/register.js +183 -0
  133. package/lib/paper/tools/aicheck.d.ts +6 -0
  134. package/lib/paper/tools/aicheck.js +38 -0
  135. package/lib/paper/tools/build.d.ts +27 -0
  136. package/lib/paper/tools/build.js +166 -0
  137. package/lib/paper/tools/check.d.ts +7 -0
  138. package/lib/paper/tools/check.js +79 -0
  139. package/lib/paper/tools/decision.d.ts +13 -0
  140. package/lib/paper/tools/decision.js +53 -0
  141. package/lib/paper/tools/defense.d.ts +11 -0
  142. package/lib/paper/tools/defense.js +74 -0
  143. package/lib/paper/tools/init.d.ts +23 -0
  144. package/lib/paper/tools/init.js +99 -0
  145. package/lib/paper/tools/lit.d.ts +27 -0
  146. package/lib/paper/tools/lit.js +236 -0
  147. package/lib/paper/tools/progress.d.ts +18 -0
  148. package/lib/paper/tools/progress.js +77 -0
  149. package/lib/paper/tools/review.d.ts +11 -0
  150. package/lib/paper/tools/review.js +41 -0
  151. package/lib/ppt/actions.d.ts +97 -0
  152. package/lib/ppt/actions.js +263 -0
  153. package/lib/ppt/check.d.ts +64 -0
  154. package/lib/ppt/check.js +185 -0
  155. package/lib/ppt/commands.d.ts +28 -0
  156. package/lib/ppt/commands.js +71 -0
  157. package/lib/ppt/convert.d.ts +146 -0
  158. package/lib/ppt/convert.js +423 -0
  159. package/lib/ppt/index.d.ts +45 -0
  160. package/lib/ppt/index.js +57 -0
  161. package/lib/ppt/marp.d.ts +74 -0
  162. package/lib/ppt/marp.js +165 -0
  163. package/lib/ppt/outline.d.ts +206 -0
  164. package/lib/ppt/outline.js +695 -0
  165. package/lib/shared/define-tool.d.ts +77 -0
  166. package/lib/shared/define-tool.js +235 -0
  167. package/lib/shared/fs-errors.d.ts +50 -0
  168. package/lib/shared/fs-errors.js +90 -0
  169. package/lib/shared/index.d.ts +31 -0
  170. package/lib/shared/index.js +32 -0
  171. package/lib/shared/spawn.d.ts +38 -0
  172. package/lib/shared/spawn.js +50 -0
  173. package/lib/shared/text.d.ts +14 -0
  174. package/lib/shared/text.js +26 -0
  175. package/package.json +82 -0
  176. package/skills/code-walkthrough/SKILL.md +94 -0
  177. package/skills/code-walkthrough/references/examples.md +129 -0
  178. package/skills/paper-dedup/SKILL.md +58 -0
  179. package/skills/paper-intake/SKILL.md +64 -0
  180. package/skills/thesis-citation/SKILL.md +67 -0
  181. package/skills/thesis-defense/SKILL.md +87 -0
  182. package/skills/thesis-eng-design/SKILL.md +97 -0
  183. package/skills/thesis-inquiry/SKILL.md +55 -0
  184. package/skills/thesis-inquiry/references/intent-spec-template.md +39 -0
  185. package/skills/thesis-literature/SKILL.md +56 -0
  186. package/skills/thesis-opener/SKILL.md +77 -0
  187. package/skills/thesis-pipeline/SKILL.md +121 -0
  188. package/skills/thesis-writing/SKILL.md +86 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * dsh-thesis 的统一配置 schema。
3
+ *
4
+ * 单一插件行 = 单一 Config:论文工作区、材料摄取、追问、原创性自查、答辩幻灯、
5
+ * 代码演练、跨会话事实、闸门,全部在这里。`dsh plugin` 的一行 `config:` 即可覆盖。
6
+ *
7
+ * 三条原则:
8
+ * 1. **默认值的单一来源**:schema 的 `.default()` 与直接 `apply()` 的代码回退
9
+ * 共用 {@link PAPER_DEFAULTS},绝不让「缺配置」退化成「静默关闸门」。
10
+ * 2. **嵌套块默认值在 resolveConfig 里补齐**:schema 只校验用户写下的值,
11
+ * 代码侧负责补全——两者不各写一份。
12
+ * 3. **配置面只说论文的话**:字段名描述学生能理解的行为,不引入别的概念体系,也不靠继承拼接形状。
13
+ *
14
+ * @module dsh-thesis/config
15
+ */
16
+ import z from '@deepseek-ai/schemastery';
17
+ import { type DestructiveRule, type GateOptions } from './gates/writing-intent.ts';
18
+ import { type RecallOptions } from './memory/recall.ts';
19
+ export type { DestructiveRule };
20
+ /** 原创性自查 / 相似度的本地度量参数。 */
21
+ export interface SimilarityOptions {
22
+ /** 指纹 shingle 的窗口(中文字符数;英文按 4 字符近似词)。 */
23
+ shingle: number;
24
+ /** 判定为高风险的包含率阈值(0..1):越高越宽松。 */
25
+ threshold: number;
26
+ /** 参与比对的最短段落长度(字符);更短的段落不判重(标题、公式行)。 */
27
+ minChars: number;
28
+ }
29
+ /** 答辩幻灯产出链路(Markdown 为准,转换交给外部工具)。 */
30
+ export interface PptOptions {
31
+ /** auto = 探测 marp/pandoc,探测不到就只产出 Markdown。 */
32
+ engine: 'auto' | 'marp' | 'pandoc' | 'none';
33
+ /** marp 主题名(写入 frontmatter 的 `theme:`)。 */
34
+ theme: string;
35
+ /** 单次外部转换的超时(毫秒)。 */
36
+ timeoutMs: number;
37
+ }
38
+ /** 材料到手后的逐题追问。 */
39
+ export interface IntakeOptions {
40
+ /** 单轮追问最多问几个问题(一次一问,但允许上限)。 */
41
+ maxQuestions: number;
42
+ /** 意图规格文档相对论文工作区根的位置。 */
43
+ specRel: string;
44
+ }
45
+ /** 文档摄取。 */
46
+ export interface IngestOptions {
47
+ /** 单个文件读取上限(字节),超过则拒绝并提示。 */
48
+ maxBytes: number;
49
+ /** 单次摄取写回模型的最大字符数(超出部分截断并标注)。 */
50
+ maxChars: number;
51
+ }
52
+ /** 写作风格自查(AI 味六条启发式)。 */
53
+ export interface StylecheckOptions {
54
+ /** 报告落盘位置(相对论文工作区根)。 */
55
+ reportRel: string;
56
+ /** 触发「长段落无锚点」判断的最短段落长度(字符)。 */
57
+ maxParagraphChars: number;
58
+ }
59
+ /** 答辩准备。 */
60
+ export interface DefenseOptions {
61
+ /** 答辩素材落盘位置。 */
62
+ materialsRel: string;
63
+ /** 预答辩问题库落盘位置。 */
64
+ questionsRel: string;
65
+ /** 生成幻灯骨架时的默认页数。 */
66
+ defaultPages: number;
67
+ }
68
+ /** 代码演练:把 AI 写的系统拆成能讲清的模块(答辩必问「你的系统怎么实现的」)。 */
69
+ export interface CodeWalkthroughOptions {
70
+ /** 状态目录(相对会话 cwd)。 */
71
+ stateDir: string;
72
+ /** 语言 → 编译验证门命令。 */
73
+ gates: Record<string, {
74
+ build: string[];
75
+ }>;
76
+ /** 每条门命令输出保留的最大字符数。 */
77
+ maxCapturedOutput: number;
78
+ }
79
+ /** 插件配置(全部可选,均有出厂默认值)。 */
80
+ export interface PaperConfig extends GateOptions {
81
+ /** 论文工作区根;缺省从会话 cwd 向上探测 `00-管理/进度台账.md`。 */
82
+ workspace?: string;
83
+ /** 跨会话事实库路径;缺省 `$DSH_HOME/dsh-thesis-memory.db`。 */
84
+ memoryPath?: string;
85
+ ingest?: Partial<IngestOptions>;
86
+ intake?: Partial<IntakeOptions>;
87
+ similarity?: Partial<SimilarityOptions>;
88
+ stylecheck?: Partial<StylecheckOptions>;
89
+ ppt?: Partial<PptOptions>;
90
+ defense?: Partial<DefenseOptions>;
91
+ /** 代码演练(答辩层)。 */
92
+ codeWalkthrough?: Partial<CodeWalkthroughOptions>;
93
+ /** 相关时把已确认的稳定事实放回上下文。 */
94
+ recall?: Partial<RecallOptions>;
95
+ }
96
+ /** 出厂默认值的单一来源。 */
97
+ export declare const PAPER_DEFAULTS: {
98
+ ingest: IngestOptions;
99
+ intake: IntakeOptions;
100
+ similarity: SimilarityOptions;
101
+ stylecheck: StylecheckOptions;
102
+ ppt: PptOptions;
103
+ defense: DefenseOptions;
104
+ codeWalkthrough: CodeWalkthroughOptions;
105
+ recall: RecallOptions;
106
+ };
107
+ /** 解析后的配置:嵌套块已补全,调用方不再处理 undefined。 */
108
+ export interface ResolvedPaperConfig {
109
+ readonly workspace: string | undefined;
110
+ readonly memoryPath: string | undefined;
111
+ readonly ingest: IngestOptions;
112
+ readonly intake: IntakeOptions;
113
+ readonly similarity: SimilarityOptions;
114
+ readonly stylecheck: StylecheckOptions;
115
+ readonly ppt: PptOptions;
116
+ readonly defense: DefenseOptions;
117
+ readonly codeWalkthrough: CodeWalkthroughOptions;
118
+ readonly recall: RecallOptions;
119
+ /** 闸门配置:两个开关可选(缺省即由闸门自身按「开」处理),列表均已补全。 */
120
+ readonly gates: GateOptions;
121
+ }
122
+ /** 补全嵌套块默认值。 */
123
+ export declare function resolveConfig(config?: PaperConfig): ResolvedPaperConfig;
124
+ /** 单一 Config schema(cordis 启动前用它校验插件行里的 config)。 */
125
+ export declare const Config: z<PaperConfig>;
package/lib/config.js ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * dsh-thesis 的统一配置 schema。
3
+ *
4
+ * 单一插件行 = 单一 Config:论文工作区、材料摄取、追问、原创性自查、答辩幻灯、
5
+ * 代码演练、跨会话事实、闸门,全部在这里。`dsh plugin` 的一行 `config:` 即可覆盖。
6
+ *
7
+ * 三条原则:
8
+ * 1. **默认值的单一来源**:schema 的 `.default()` 与直接 `apply()` 的代码回退
9
+ * 共用 {@link PAPER_DEFAULTS},绝不让「缺配置」退化成「静默关闸门」。
10
+ * 2. **嵌套块默认值在 resolveConfig 里补齐**:schema 只校验用户写下的值,
11
+ * 代码侧负责补全——两者不各写一份。
12
+ * 3. **配置面只说论文的话**:字段名描述学生能理解的行为,不引入别的概念体系,也不靠继承拼接形状。
13
+ *
14
+ * @module dsh-thesis/config
15
+ */
16
+ import z from '@deepseek-ai/schemastery';
17
+ import { GATE_DEFAULTS } from "./gates/writing-intent.js";
18
+ import { RECALL_DEFAULTS } from "./memory/recall.js";
19
+ /** 出厂默认值的单一来源。 */
20
+ export const PAPER_DEFAULTS = {
21
+ ingest: { maxBytes: 32 * 1024 * 1024, maxChars: 60_000 },
22
+ intake: { maxQuestions: 12, specRel: '00-管理/意图规格.md' },
23
+ similarity: { shingle: 4, threshold: 0.3, minChars: 30 },
24
+ stylecheck: { reportRel: '08-合规/AI味自查报告.md', maxParagraphChars: 260 },
25
+ ppt: { engine: 'auto', theme: 'default', timeoutMs: 120_000 },
26
+ defense: {
27
+ materialsRel: '07-答辩/答辩素材.md',
28
+ questionsRel: '07-答辩/预答辩问题库.md',
29
+ defaultPages: 11,
30
+ },
31
+ codeWalkthrough: {
32
+ stateDir: '.paper',
33
+ gates: {
34
+ go: { build: ['go', 'build', './...'] },
35
+ ts: { build: ['npm', 'run', 'typecheck'] },
36
+ python: { build: ['python', '-m', 'compileall', '-q', '.'] },
37
+ rust: { build: ['cargo', 'check'] },
38
+ },
39
+ maxCapturedOutput: 8000,
40
+ },
41
+ recall: RECALL_DEFAULTS,
42
+ };
43
+ /** 补全嵌套块默认值。 */
44
+ 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
+ return {
47
+ workspace,
48
+ memoryPath,
49
+ ingest: { ...PAPER_DEFAULTS.ingest, ...ingest },
50
+ intake: { ...PAPER_DEFAULTS.intake, ...intake },
51
+ similarity: { ...PAPER_DEFAULTS.similarity, ...similarity },
52
+ stylecheck: { ...PAPER_DEFAULTS.stylecheck, ...stylecheck },
53
+ ppt: { ...PAPER_DEFAULTS.ppt, ...ppt },
54
+ defense: { ...PAPER_DEFAULTS.defense, ...defense },
55
+ codeWalkthrough: {
56
+ ...PAPER_DEFAULTS.codeWalkthrough,
57
+ ...codeWalkthrough,
58
+ gates: { ...PAPER_DEFAULTS.codeWalkthrough.gates, ...codeWalkthrough?.gates },
59
+ },
60
+ recall: { ...PAPER_DEFAULTS.recall, ...recall },
61
+ gates: {
62
+ ...(writingGate !== undefined ? { writingGate } : {}),
63
+ ...(destructiveGate !== undefined ? { destructiveGate } : {}),
64
+ writingIntentPatterns: writingIntentPatterns ?? GATE_DEFAULTS.writingIntentPatterns,
65
+ criteriaMarkers: criteriaMarkers ?? GATE_DEFAULTS.criteriaMarkers,
66
+ bypassMarkers: bypassMarkers ?? GATE_DEFAULTS.bypassMarkers,
67
+ maxQuestions: maxQuestions ?? GATE_DEFAULTS.maxQuestions,
68
+ destructiveToolNames: destructiveToolNames ?? GATE_DEFAULTS.destructiveToolNames,
69
+ destructiveRules: destructiveRules ?? GATE_DEFAULTS.destructiveRules,
70
+ },
71
+ };
72
+ }
73
+ /** 单一 Config schema(cordis 启动前用它校验插件行里的 config)。 */
74
+ export const Config = z.object({
75
+ // ---- 论文工作区 ----
76
+ workspace: z.string(),
77
+ memoryPath: z.string(),
78
+ // ---- 材料与追问 ----
79
+ ingest: z.object({
80
+ maxBytes: z.natural().default(PAPER_DEFAULTS.ingest.maxBytes),
81
+ maxChars: z.natural().default(PAPER_DEFAULTS.ingest.maxChars),
82
+ }),
83
+ intake: z.object({
84
+ maxQuestions: z.natural().min(1).default(PAPER_DEFAULTS.intake.maxQuestions),
85
+ specRel: z.string().default(PAPER_DEFAULTS.intake.specRel),
86
+ }),
87
+ // ---- 原创性自查 ----
88
+ similarity: z.object({
89
+ shingle: z.natural().default(PAPER_DEFAULTS.similarity.shingle),
90
+ threshold: z.number().default(PAPER_DEFAULTS.similarity.threshold),
91
+ minChars: z.natural().default(PAPER_DEFAULTS.similarity.minChars),
92
+ }),
93
+ // ---- 写作风格自查 ----
94
+ stylecheck: z.object({
95
+ reportRel: z.string().default(PAPER_DEFAULTS.stylecheck.reportRel),
96
+ maxParagraphChars: z.natural().default(PAPER_DEFAULTS.stylecheck.maxParagraphChars),
97
+ }),
98
+ // ---- 答辩:幻灯 + 素材 + 代码演练 ----
99
+ ppt: z.object({
100
+ engine: z.union([
101
+ z.const('auto'),
102
+ z.const('marp'),
103
+ z.const('pandoc'),
104
+ z.const('none'),
105
+ ]).default(PAPER_DEFAULTS.ppt.engine),
106
+ theme: z.string().default(PAPER_DEFAULTS.ppt.theme),
107
+ timeoutMs: z.natural().default(PAPER_DEFAULTS.ppt.timeoutMs),
108
+ }),
109
+ defense: z.object({
110
+ materialsRel: z.string().default(PAPER_DEFAULTS.defense.materialsRel),
111
+ questionsRel: z.string().default(PAPER_DEFAULTS.defense.questionsRel),
112
+ defaultPages: z.natural().min(1).default(PAPER_DEFAULTS.defense.defaultPages),
113
+ }),
114
+ codeWalkthrough: z.object({
115
+ stateDir: z.string().default(PAPER_DEFAULTS.codeWalkthrough.stateDir),
116
+ gates: z.dict(z.object({
117
+ build: z.array(z.string()),
118
+ })).default(PAPER_DEFAULTS.codeWalkthrough.gates),
119
+ maxCapturedOutput: z.natural().default(PAPER_DEFAULTS.codeWalkthrough.maxCapturedOutput),
120
+ }),
121
+ // ---- 跨会话事实 ----
122
+ recall: z.object({
123
+ maxEntries: z.natural().default(PAPER_DEFAULTS.recall.maxEntries),
124
+ maxChars: z.natural().default(PAPER_DEFAULTS.recall.maxChars),
125
+ minKeywordLength: z.natural().min(1).default(PAPER_DEFAULTS.recall.minKeywordLength),
126
+ }),
127
+ // ---- 闸门 ----
128
+ writingGate: z.boolean().default(true),
129
+ writingIntentPatterns: z.array(z.string()).default(GATE_DEFAULTS.writingIntentPatterns),
130
+ criteriaMarkers: z.array(z.string()).default(GATE_DEFAULTS.criteriaMarkers),
131
+ bypassMarkers: z.array(z.string()).default(GATE_DEFAULTS.bypassMarkers),
132
+ maxQuestions: z.natural().min(1).default(GATE_DEFAULTS.maxQuestions),
133
+ destructiveGate: z.boolean().default(true),
134
+ destructiveToolNames: z.array(z.string()).default(GATE_DEFAULTS.destructiveToolNames),
135
+ destructiveRules: z.array(z.object({
136
+ label: z.string(),
137
+ pattern: z.string(),
138
+ })).default(GATE_DEFAULTS.destructiveRules),
139
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 斜杠命令:`/thesis-originality`。
3
+ *
4
+ * 命令名遵循 DSH 的 `[a-z][a-z0-9_-]*` 约束(kebab-case),与 `/thesis-check` 等同一套写法。
5
+ * 命令只是 `thesis_originality` 工具的薄封装:把命令行输入解析成 `DedupArgs` 后交给 `runDedup`,
6
+ * 因此行为与工具**完全一致**,不存在"命令和工具两条逻辑"的漂移。
7
+ *
8
+ * @module dsh-thesis/dedup
9
+ */
10
+ import type { FileSystem } from '@deepseek-ai/dsh-fs';
11
+ import type { SimilarityOptions } from '../config.ts';
12
+ import { type DedupArgs } from './index.ts';
13
+ /**
14
+ * 解析 `/thesis-originality` 的输入。
15
+ *
16
+ * 支持:`scan|verify|report`、位置参数(语料路径)、`--chapter <章节>`、
17
+ * `--baseline <报告路径>`、`--rate <重复率>`、`--system <检测系统名>`。
18
+ */
19
+ export declare function parseDedupCommandInput(rawInput: string): DedupArgs;
20
+ /** 构造 `/thesis-originality` 命令定义。 */
21
+ export declare function dedupCommand(fs: FileSystem, similarity: SimilarityOptions): unknown;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 斜杠命令:`/thesis-originality`。
3
+ *
4
+ * 命令名遵循 DSH 的 `[a-z][a-z0-9_-]*` 约束(kebab-case),与 `/thesis-check` 等同一套写法。
5
+ * 命令只是 `thesis_originality` 工具的薄封装:把命令行输入解析成 `DedupArgs` 后交给 `runDedup`,
6
+ * 因此行为与工具**完全一致**,不存在"命令和工具两条逻辑"的漂移。
7
+ *
8
+ * @module dsh-thesis/dedup
9
+ */
10
+ import { runDedup } from "./index.js";
11
+ const ACTIONS = ['scan', 'verify', 'report'];
12
+ async function safe(work) {
13
+ try {
14
+ return { kind: 'success', text: await work() };
15
+ }
16
+ catch (error) {
17
+ return { kind: 'error', text: error instanceof Error ? error.message : String(error) };
18
+ }
19
+ }
20
+ /**
21
+ * 解析 `/thesis-originality` 的输入。
22
+ *
23
+ * 支持:`scan|verify|report`、位置参数(语料路径)、`--chapter <章节>`、
24
+ * `--baseline <报告路径>`、`--rate <重复率>`、`--system <检测系统名>`。
25
+ */
26
+ export function parseDedupCommandInput(rawInput) {
27
+ const tokens = rawInput.trim().split(/\s+/).filter(token => token !== '');
28
+ let action = 'scan';
29
+ const paths = [];
30
+ let chapter;
31
+ let baseline;
32
+ let detectedRate;
33
+ let detectedSystem;
34
+ for (let index = 0; index < tokens.length; index += 1) {
35
+ const token = tokens[index];
36
+ const lowered = token.toLowerCase();
37
+ if (ACTIONS.includes(lowered)) {
38
+ action = lowered;
39
+ continue;
40
+ }
41
+ if (lowered === '--chapter' || lowered === '-c') {
42
+ chapter = tokens[++index];
43
+ if (chapter === undefined)
44
+ throw new Error('--chapter 后面要跟章节名,如 --chapter 01-绪论');
45
+ continue;
46
+ }
47
+ if (lowered === '--baseline' || lowered === '-b') {
48
+ baseline = tokens[++index];
49
+ if (baseline === undefined)
50
+ throw new Error('--baseline 后面要跟报告路径');
51
+ continue;
52
+ }
53
+ if (lowered === '--rate' || lowered === '-r') {
54
+ const raw = tokens[++index];
55
+ const rate = raw === undefined ? Number.NaN : Number.parseFloat(raw);
56
+ if (!Number.isFinite(rate))
57
+ throw new Error('--rate 后面要跟数字,如 --rate 12 或 --rate 0.12');
58
+ detectedRate = rate;
59
+ continue;
60
+ }
61
+ if (lowered === '--system' || lowered === '-s') {
62
+ detectedSystem = tokens[++index];
63
+ if (detectedSystem === undefined)
64
+ throw new Error('--system 后面要跟检测系统名,如 --system 知网');
65
+ continue;
66
+ }
67
+ if (token.startsWith('-'))
68
+ throw new Error(`无法识别的选项「${token}」`);
69
+ paths.push(token);
70
+ }
71
+ return {
72
+ action,
73
+ ...(paths.length > 0 ? { corpus: paths.join(',') } : {}),
74
+ ...(chapter !== undefined ? { chapter } : {}),
75
+ ...(baseline !== undefined ? { baseline } : {}),
76
+ ...(detectedRate !== undefined ? { detected_rate: detectedRate } : {}),
77
+ ...(detectedSystem !== undefined ? { detected_system: detectedSystem } : {}),
78
+ };
79
+ }
80
+ /** 构造 `/thesis-originality` 命令定义。 */
81
+ export function dedupCommand(fs, similarity) {
82
+ return {
83
+ name: 'thesis-originality',
84
+ description: '原创性自查:本地相似度扫描(scan)/ 复测降幅(verify)/ 汇总并回填学校检测结果(report);不接入收费查重系统',
85
+ input: { hint: 'scan | verify | report [语料路径] [--chapter 01-绪论] [--rate 12] [--system 知网]' },
86
+ async handler(invocation) {
87
+ return await safe(async () => {
88
+ const args = parseDedupCommandInput(invocation.rawInput);
89
+ return await runDedup(fs, invocation.agent.session.header.cwd, args, similarity, invocation.signal);
90
+ });
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * dsh-thesis 降重模块的门面与工具装配。
3
+ *
4
+ * 三层结构:
5
+ * - `normalize.ts` 文本归一化 + 段落切分(带原文偏移映射)
6
+ * - `similarity.ts` shingle 指纹、倒排索引、包含率/杰卡德/最长公共子串
7
+ * - `rewrite.ts` / `report.ts` 改写处方与报告渲染
8
+ *
9
+ * 本文件只做两件事:把正文段落与参考语料装进扫描器;注册 `thesis_originality` 工具
10
+ * 并把报告落到 `08-合规/降重报告.md`(verify 另写 `08-合规/降重报告-复测.md`)。
11
+ *
12
+ * **边界**:不做知网/维普等收费系统的接入(无授权,也不应接入)。
13
+ * 本地相似度只是字符串重合度估算,权威口径是学校检测结果,可回填到报告里。
14
+ *
15
+ * @module dsh-thesis/dedup
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ import type { FileSystem } from '@deepseek-ai/dsh-fs';
19
+ import type { SimilarityOptions } from '../config.ts';
20
+ import { segmentsFromText, type Segment } from './normalize.ts';
21
+ import { scan, type ScanResult } from './similarity.ts';
22
+ import { buildReport, renderReport, weightedRateOf, type DedupReport } from './report.ts';
23
+ export declare const REPORT_REL = "08-\u5408\u89C4/\u964D\u91CD\u62A5\u544A.md";
24
+ export declare const VERIFY_REPORT_REL = "08-\u5408\u89C4/\u964D\u91CD\u62A5\u544A-\u590D\u6D4B.md";
25
+ export declare const CHAPTERS_REL = "06-\u8BBA\u6587/\u7AE0\u8282";
26
+ /** 缺省参考语料目录(相对论文工作区根)。 */
27
+ export declare const DEFAULT_CORPUS_DIRS: readonly string[];
28
+ /** 扫描到的参考文件(`file` 是相对论文工作区根的 POSIX 路径)。 */
29
+ export interface CorpusFile {
30
+ readonly file: string;
31
+ readonly label: string;
32
+ readonly text: string;
33
+ }
34
+ /** thesis_originality 的参数。 */
35
+ export interface DedupArgs {
36
+ /** scan | verify | report。 */
37
+ readonly action: string;
38
+ /** 参考语料路径(文件或目录),可多个;缺省用 DEFAULT_CORPUS_DIRS。 */
39
+ readonly corpus?: string;
40
+ /** 只处理某一章(章节文件名,可带 .md)。 */
41
+ readonly chapter?: string;
42
+ /** verify 用:上一次 scan 落盘的报告路径。 */
43
+ readonly baseline?: string;
44
+ /** report 用:用户自行送检得到的重复率(0..1 或百分数)。 */
45
+ readonly detected_rate?: number;
46
+ /** report 用:检测系统名称。 */
47
+ readonly detected_system?: string;
48
+ }
49
+ /** `thesis_originality` 的注册参数:相似度配置来自 `src/config.ts` 的 `SimilarityOptions`。 */
50
+ export interface DedupOptions {
51
+ readonly similarity: SimilarityOptions;
52
+ }
53
+ /** 收集参考语料:显式路径优先,否则用缺省目录 + 各章正文(互查)。 */
54
+ export declare function collectCorpus(fs: FileSystem, root: string, corpusArg: string | undefined, excludeFiles: readonly string[], wholeChapters: boolean, signal?: AbortSignal): Promise<CorpusFile[]>;
55
+ /** 读取正文段落(按章切分;chapter 为空则全部已撰写章)。 */
56
+ export declare function loadChapterSegments(fs: FileSystem, root: string, chapter: string | undefined, minChars: number, signal?: AbortSignal): Promise<{
57
+ segments: Segment[];
58
+ missing: string[];
59
+ corpusCandidates: CorpusFile[];
60
+ }>;
61
+ /** 执行 thesis_originality 的全部动作;返回有界文本摘要。 */
62
+ export declare function runDedup(fs: FileSystem, cwd: string | undefined, args: DedupArgs, similarity: SimilarityOptions, signal?: AbortSignal): Promise<string>;
63
+ /**
64
+ * 注册 `thesis_originality`:本地相似度扫描 / 复测 / 报告回填。
65
+ *
66
+ * 参数:`action`(scan | verify | report)、`corpus`(参考语料路径,文件或目录)、
67
+ * `chapter`(只处理某一章)、`baseline`(verify 的上次报告路径)、
68
+ * `detected_rate`(report 回填的学校检测重复率)、`detected_system`。
69
+ * 落盘:`08-合规/降重报告.md`;verify 另写 `08-合规/降重报告-复测.md`。
70
+ */
71
+ export declare function registerDedup(ctx: Context, options: {
72
+ similarity: SimilarityOptions;
73
+ }): void;
74
+ export { segmentsFromText, scan, buildReport, renderReport, weightedRateOf };
75
+ export type { Segment, ScanResult, DedupReport, SimilarityOptions };