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,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marp Markdown 渲染(`thesis_slides action=outline` 的产出格式)。
|
|
3
|
+
*
|
|
4
|
+
* 输出目标:**标准 Marp 幻灯片 Markdown**,保证 `marp PPT.md --pptx` /
|
|
5
|
+
* `npx @marp-team/marp-cli PPT.md --pptx` / `pandoc PPT.md -o PPT.pptx`
|
|
6
|
+
* 可以直接消费。因此:
|
|
7
|
+
* - frontmatter 只写 `marp/theme/paginate/size/header/footer` 六个键,值全部
|
|
8
|
+
* 经过清洗(去引号、去换行、去 `---`),不可能破坏 YAML 头;
|
|
9
|
+
* - 页间分隔符是独占一行的 `---`;正文里任何以 `---` 开头的行都会被转义;
|
|
10
|
+
* - 讲稿写成 HTML 注释 `<!-- ... -->`(Marp 的演示者备注兼容形态)。
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-thesis/ppt
|
|
13
|
+
*/
|
|
14
|
+
import { type PlanBullet, type PlannedSlide, type SlidePlan } from './outline.ts';
|
|
15
|
+
/** 推荐主题名(Marp 内置主题 + 常见自定义主题)。 */
|
|
16
|
+
export declare const MARP_THEMES: readonly string[];
|
|
17
|
+
/** {@link renderMarp} 选项。 */
|
|
18
|
+
export interface MarpOptions {
|
|
19
|
+
/** frontmatter 的 `theme:`;缺省 `default`。 */
|
|
20
|
+
readonly theme?: string;
|
|
21
|
+
/** 页脚文字;缺省 `dsh-thesis`。 */
|
|
22
|
+
readonly footer?: string;
|
|
23
|
+
/** 页眉文字;缺省用课题标题。 */
|
|
24
|
+
readonly header?: string;
|
|
25
|
+
/** 是否写 `paginate: true`,缺省 true。 */
|
|
26
|
+
readonly paginate?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 清洗任意文本,使其可安全放进 Markdown 正文/HTML 注释:
|
|
30
|
+
* 去掉换行与 HTML 注释定界符,行首 `---`/`#` 会被打散,避免误触发分页或标题。
|
|
31
|
+
*/
|
|
32
|
+
export declare function sanitizeInline(text: string): string;
|
|
33
|
+
/** 清洗 YAML 标量:单行、去引号、去 `---`,保证 frontmatter 不被破坏。 */
|
|
34
|
+
export declare function sanitizeYamlScalar(text: string): string;
|
|
35
|
+
/** 主题名只允许 `[A-Za-z0-9._-]`,其余替换为 `-`。 */
|
|
36
|
+
export declare function sanitizeTheme(theme: string | undefined): string;
|
|
37
|
+
/** 一条要点渲染为一行 Markdown 列表项(超长截断的要点带 `␣` 标记说明)。 */
|
|
38
|
+
export declare function renderBullet(bullet: PlanBullet): string;
|
|
39
|
+
/** 一页渲染为:标题 + 要点 + 讲稿注释。 */
|
|
40
|
+
export declare function renderSlide(slide: PlannedSlide): string;
|
|
41
|
+
/**
|
|
42
|
+
* 渲染整份 Marp Markdown。
|
|
43
|
+
*
|
|
44
|
+
* 返回文本以 frontmatter 开头、以换行结尾;页间以 `---` 分隔,
|
|
45
|
+
* 因此 `---` 独占行的出现次数 = 页数 - 1(frontmatter 的两条定界行不计入)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderMarp(plan: SlidePlan, options?: MarpOptions): string;
|
|
48
|
+
/** 从 Marp Markdown 里解析出的单页(用于质量检查)。 */
|
|
49
|
+
export interface ParsedSlide {
|
|
50
|
+
readonly index: number;
|
|
51
|
+
readonly title: string;
|
|
52
|
+
readonly bullets: readonly {
|
|
53
|
+
readonly text: string;
|
|
54
|
+
readonly anchor?: string;
|
|
55
|
+
readonly truncated: boolean;
|
|
56
|
+
}[];
|
|
57
|
+
readonly note?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Marp Markdown 的解析结果。 */
|
|
60
|
+
export interface ParsedDeck {
|
|
61
|
+
readonly title: string;
|
|
62
|
+
readonly theme: string;
|
|
63
|
+
readonly paginate: boolean;
|
|
64
|
+
readonly slides: readonly ParsedSlide[];
|
|
65
|
+
}
|
|
66
|
+
/** 提取 frontmatter 键值(只支持 `key: value` 单行标量)。 */
|
|
67
|
+
export declare function parseFrontmatter(markdown: string): {
|
|
68
|
+
readonly fields: Record<string, string>;
|
|
69
|
+
readonly body: string;
|
|
70
|
+
};
|
|
71
|
+
/** 解析 Marp Markdown(含 frontmatter)为可检查的结构。 */
|
|
72
|
+
export declare function parseMarp(markdown: string): ParsedDeck;
|
|
73
|
+
/** 检查 Marp Markdown 里是否会破坏 frontmatter/分页的字符。 */
|
|
74
|
+
export declare function findUnsafeMarkdown(markdown: string): string[];
|
package/lib/ppt/marp.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marp Markdown 渲染(`thesis_slides action=outline` 的产出格式)。
|
|
3
|
+
*
|
|
4
|
+
* 输出目标:**标准 Marp 幻灯片 Markdown**,保证 `marp PPT.md --pptx` /
|
|
5
|
+
* `npx @marp-team/marp-cli PPT.md --pptx` / `pandoc PPT.md -o PPT.pptx`
|
|
6
|
+
* 可以直接消费。因此:
|
|
7
|
+
* - frontmatter 只写 `marp/theme/paginate/size/header/footer` 六个键,值全部
|
|
8
|
+
* 经过清洗(去引号、去换行、去 `---`),不可能破坏 YAML 头;
|
|
9
|
+
* - 页间分隔符是独占一行的 `---`;正文里任何以 `---` 开头的行都会被转义;
|
|
10
|
+
* - 讲稿写成 HTML 注释 `<!-- ... -->`(Marp 的演示者备注兼容形态)。
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-thesis/ppt
|
|
13
|
+
*/
|
|
14
|
+
import { MAX_BULLETS_PER_SLIDE } from "./outline.js";
|
|
15
|
+
/** 推荐主题名(Marp 内置主题 + 常见自定义主题)。 */
|
|
16
|
+
export const MARP_THEMES = ['default', 'gaia', 'uncover', 'academic', 'dsh-thesis'];
|
|
17
|
+
/**
|
|
18
|
+
* 清洗任意文本,使其可安全放进 Markdown 正文/HTML 注释:
|
|
19
|
+
* 去掉换行与 HTML 注释定界符,行首 `---`/`#` 会被打散,避免误触发分页或标题。
|
|
20
|
+
*/
|
|
21
|
+
export function sanitizeInline(text) {
|
|
22
|
+
const flat = text.replace(/[\r\n]+/g, ' ').replace(/<!--|-->/g, ' ').replace(/\s+/g, ' ').trim();
|
|
23
|
+
return flat.replace(/^---+/, '- —').replace(/^#{1,6}\s*/, '');
|
|
24
|
+
}
|
|
25
|
+
/** 清洗 YAML 标量:单行、去引号、去 `---`,保证 frontmatter 不被破坏。 */
|
|
26
|
+
export function sanitizeYamlScalar(text) {
|
|
27
|
+
const flat = sanitizeInline(text).replace(/["'`]/g, '').replace(/:/g, ':');
|
|
28
|
+
return flat === '' ? 'untitled' : flat;
|
|
29
|
+
}
|
|
30
|
+
/** 主题名只允许 `[A-Za-z0-9._-]`,其余替换为 `-`。 */
|
|
31
|
+
export function sanitizeTheme(theme) {
|
|
32
|
+
const raw = (theme ?? 'default').trim();
|
|
33
|
+
const safe = raw.replace(/[^A-Za-z0-9._-]/g, '-');
|
|
34
|
+
return safe === '' ? 'default' : safe;
|
|
35
|
+
}
|
|
36
|
+
function frontmatter(plan, options) {
|
|
37
|
+
return [
|
|
38
|
+
'---',
|
|
39
|
+
'marp: true',
|
|
40
|
+
`theme: ${sanitizeTheme(options.theme)}`,
|
|
41
|
+
`paginate: ${options.paginate === false ? 'false' : 'true'}`,
|
|
42
|
+
'size: 16:9',
|
|
43
|
+
`header: ${sanitizeYamlScalar(options.header ?? plan.title)}`,
|
|
44
|
+
`footer: ${sanitizeYamlScalar(options.footer ?? 'dsh-thesis')}`,
|
|
45
|
+
'---',
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
/** 一条要点渲染为一行 Markdown 列表项(超长截断的要点带 `␣` 标记说明)。 */
|
|
49
|
+
export function renderBullet(bullet) {
|
|
50
|
+
const text = sanitizeInline(bullet.text);
|
|
51
|
+
const anchor = bullet.anchor !== undefined ? sanitizeInline(bullet.anchor) : '';
|
|
52
|
+
const suffix = anchor !== '' ? ` <!-- 锚点: ${anchor} -->` : '';
|
|
53
|
+
const mark = bullet.truncated ? '(已截断)' : '';
|
|
54
|
+
return `- ${text}${mark}${suffix}`;
|
|
55
|
+
}
|
|
56
|
+
/** 一页渲染为:标题 + 要点 + 讲稿注释。 */
|
|
57
|
+
export function renderSlide(slide) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
lines.push(`## ${sanitizeInline(slide.title)}`);
|
|
60
|
+
lines.push('');
|
|
61
|
+
for (const bullet of slide.bullets.slice(0, MAX_BULLETS_PER_SLIDE))
|
|
62
|
+
lines.push(renderBullet(bullet));
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(`<!-- 讲稿:${sanitizeInline(slide.note)} -->`);
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 渲染整份 Marp Markdown。
|
|
69
|
+
*
|
|
70
|
+
* 返回文本以 frontmatter 开头、以换行结尾;页间以 `---` 分隔,
|
|
71
|
+
* 因此 `---` 独占行的出现次数 = 页数 - 1(frontmatter 的两条定界行不计入)。
|
|
72
|
+
*/
|
|
73
|
+
export function renderMarp(plan, options = {}) {
|
|
74
|
+
const blocks = [];
|
|
75
|
+
blocks.push(...frontmatter(plan, options));
|
|
76
|
+
blocks.push('');
|
|
77
|
+
const body = plan.slides.map(renderSlide).join('\n\n---\n\n');
|
|
78
|
+
blocks.push(body);
|
|
79
|
+
blocks.push('');
|
|
80
|
+
return blocks.join('\n');
|
|
81
|
+
}
|
|
82
|
+
/** 提取 frontmatter 键值(只支持 `key: value` 单行标量)。 */
|
|
83
|
+
export function parseFrontmatter(markdown) {
|
|
84
|
+
const lines = markdown.split(/\r?\n/);
|
|
85
|
+
if (lines[0]?.trim() !== '---')
|
|
86
|
+
return { fields: {}, body: markdown };
|
|
87
|
+
let end = -1;
|
|
88
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
89
|
+
if (lines[i]?.trim() === '---') {
|
|
90
|
+
end = i;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (end === -1)
|
|
95
|
+
return { fields: {}, body: markdown };
|
|
96
|
+
const fields = {};
|
|
97
|
+
for (const raw of lines.slice(1, end)) {
|
|
98
|
+
const matched = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw.trim());
|
|
99
|
+
if (matched?.[1] !== undefined)
|
|
100
|
+
fields[matched[1]] = (matched[2] ?? '').trim();
|
|
101
|
+
}
|
|
102
|
+
return { fields, body: lines.slice(end + 1).join('\n') };
|
|
103
|
+
}
|
|
104
|
+
/** 解析 Marp Markdown(含 frontmatter)为可检查的结构。 */
|
|
105
|
+
export function parseMarp(markdown) {
|
|
106
|
+
const { fields, body } = parseFrontmatter(markdown);
|
|
107
|
+
const blocks = [[]];
|
|
108
|
+
for (const line of body.split(/\r?\n/)) {
|
|
109
|
+
if (line.trim() === '---') {
|
|
110
|
+
blocks.push([]);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
blocks[blocks.length - 1].push(line);
|
|
114
|
+
}
|
|
115
|
+
const slides = [];
|
|
116
|
+
for (const block of blocks) {
|
|
117
|
+
const title = block.map(l => /^##\s+(.+)$/.exec(l.trim())?.[1]).find(v => v !== undefined);
|
|
118
|
+
if (title === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
const bullets = [];
|
|
121
|
+
let note;
|
|
122
|
+
for (const line of block) {
|
|
123
|
+
const trimmed = line.trim();
|
|
124
|
+
const noteMatched = /^<!--\s*讲稿[::]\s*(.*?)\s*-->$/.exec(trimmed);
|
|
125
|
+
if (noteMatched?.[1] !== undefined) {
|
|
126
|
+
note = noteMatched[1];
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!trimmed.startsWith('- '))
|
|
130
|
+
continue;
|
|
131
|
+
const anchorMatched = /<!--\s*锚点[::]\s*(.*?)\s*-->/.exec(trimmed);
|
|
132
|
+
const withoutAnchor = trimmed.replace(/<!--.*?-->/g, '').trim();
|
|
133
|
+
const truncated = /(已截断)$/.test(withoutAnchor);
|
|
134
|
+
const text = withoutAnchor.replace(/^-\s+/, '').replace(/(已截断)$/, '').trim();
|
|
135
|
+
bullets.push({ text, ...(anchorMatched?.[1] !== undefined ? { anchor: anchorMatched[1] } : {}), truncated });
|
|
136
|
+
}
|
|
137
|
+
slides.push({
|
|
138
|
+
index: slides.length + 1,
|
|
139
|
+
title,
|
|
140
|
+
bullets,
|
|
141
|
+
...(note !== undefined ? { note } : {}),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
title: fields.title ?? '',
|
|
146
|
+
theme: fields.theme ?? '',
|
|
147
|
+
paginate: (fields.paginate ?? 'true') !== 'false',
|
|
148
|
+
slides,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** 检查 Marp Markdown 里是否会破坏 frontmatter/分页的字符。 */
|
|
152
|
+
export function findUnsafeMarkdown(markdown) {
|
|
153
|
+
const problems = [];
|
|
154
|
+
const { body } = parseFrontmatter(markdown);
|
|
155
|
+
const lines = body.split(/\r?\n/);
|
|
156
|
+
for (const [i, line] of lines.entries()) {
|
|
157
|
+
if (/^\s*---/.test(line) && line.trim() !== '---')
|
|
158
|
+
problems.push(`第 ${i + 1} 行:以 --- 开头的正文行会被误判为分页`);
|
|
159
|
+
if (line.includes('<!-- 讲稿') && !/^<!--\s*讲稿[::].*-->$/.test(line.trim()))
|
|
160
|
+
problems.push(`第 ${i + 1} 行:讲稿注释形态不合法`);
|
|
161
|
+
}
|
|
162
|
+
if (!/^---\r?\nmarp:\s*true\s*$/m.test(markdown))
|
|
163
|
+
problems.push('frontmatter 首行必须是 marp: true');
|
|
164
|
+
return problems;
|
|
165
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 答辩幻灯计划(`thesis_slides action=outline` 的核心)。
|
|
3
|
+
*
|
|
4
|
+
* 产品决策:**不自研 pptx 生成器**。本模块把真实素材变成结构化的《幻灯片计划》
|
|
5
|
+
* (SlidePlan),由 `marp.ts` 渲染为标准 Marp Markdown,再交给外部转换器
|
|
6
|
+
* (marp-cli / pandoc)成稿。计划里的每一条要点都带**证据锚点**,指向工作区里
|
|
7
|
+
* 真实存在的文件/章节/数据——这是"零编造"红线在答辩环节的落地。
|
|
8
|
+
*
|
|
9
|
+
* 两条输入路径:
|
|
10
|
+
* 1. `07-答辩/答辩素材.md`(由 `thesis_defense` 生成)——优先;
|
|
11
|
+
* 2. 素材不存在时回退到 `06-论文/章节/*.md` 的标题与要点,并在结果里标注
|
|
12
|
+
* "素材未生成,建议先跑 thesis_defense",**不编造系统细节**。
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-thesis/ppt
|
|
15
|
+
*/
|
|
16
|
+
import type { FileSystem } from '@deepseek-ai/dsh-fs';
|
|
17
|
+
/** 幻灯页的语义类型(渲染与检查共用)。 */
|
|
18
|
+
export type SlideKind = 'cover' | 'toc' | 'background' | 'status' | 'content' | 'route' | 'design' | 'impl' | 'test' | 'conclusion' | 'innovation' | 'thanks';
|
|
19
|
+
/** 听众档位:本科(默认)/ 硕士。影响详略与时长估算。 */
|
|
20
|
+
export type Audience = 'undergrad' | 'master';
|
|
21
|
+
/** 单条要点的硬上限(字符):超出自动截断并标记。 */
|
|
22
|
+
export declare const MAX_BULLET_CHARS = 30;
|
|
23
|
+
/** 单页标题的硬上限(字符)。 */
|
|
24
|
+
export declare const MAX_TITLE_CHARS = 20;
|
|
25
|
+
/** 单页要点条数区间。 */
|
|
26
|
+
export declare const MIN_BULLETS_PER_SLIDE = 3;
|
|
27
|
+
export declare const MAX_BULLETS_PER_SLIDE = 6;
|
|
28
|
+
/** 每页预计讲述秒数区间(本科)。 */
|
|
29
|
+
export declare const SECONDS_PER_SLIDE_MIN = 40;
|
|
30
|
+
export declare const SECONDS_PER_SLIDE_MAX = 60;
|
|
31
|
+
/** 系统实现页的证据锚点根目录(真实代码位置)。 */
|
|
32
|
+
export declare const CODE_ROOT = "04-\u5B9E\u73B0";
|
|
33
|
+
/** 实验结果的证据锚点根目录。 */
|
|
34
|
+
export declare const RESULT_ROOT = "05-\u5B9E\u9A8C\u6D4B\u8BD5/\u7ED3\u679C";
|
|
35
|
+
/** 要点模板:正文 + 证据锚点。 */
|
|
36
|
+
export interface BulletSpec {
|
|
37
|
+
readonly text: string;
|
|
38
|
+
readonly anchor?: string;
|
|
39
|
+
}
|
|
40
|
+
/** 页面模板(解析素材后的呈现结构)。 */
|
|
41
|
+
export interface SlideSpec {
|
|
42
|
+
readonly kind: SlideKind;
|
|
43
|
+
readonly title: string;
|
|
44
|
+
readonly bullets: readonly BulletSpec[];
|
|
45
|
+
/** 讲稿要点(渲染为 HTML 注释,Marp 演示者备注兼容)。 */
|
|
46
|
+
readonly note: string;
|
|
47
|
+
}
|
|
48
|
+
/** 解析后的单条要点。 */
|
|
49
|
+
export interface PlanBullet {
|
|
50
|
+
readonly text: string;
|
|
51
|
+
/** 超长被自动截断(原文另见 `original`)。 */
|
|
52
|
+
readonly truncated: boolean;
|
|
53
|
+
/** 被截断前的原文(仅在 truncated=true 时出现)。 */
|
|
54
|
+
readonly original?: string;
|
|
55
|
+
/** 证据锚点:相对工作区根的文件/章节/数据位置。 */
|
|
56
|
+
readonly anchor?: string;
|
|
57
|
+
}
|
|
58
|
+
/** 计划中的一页。 */
|
|
59
|
+
export interface PlannedSlide {
|
|
60
|
+
readonly index: number;
|
|
61
|
+
readonly kind: SlideKind;
|
|
62
|
+
readonly title: string;
|
|
63
|
+
readonly bullets: readonly PlanBullet[];
|
|
64
|
+
readonly note: string;
|
|
65
|
+
}
|
|
66
|
+
/** 素材来源模式。 */
|
|
67
|
+
export type MaterialSource = 'defense-materials' | 'chapters';
|
|
68
|
+
/** 章节解析结果。 */
|
|
69
|
+
export interface ChapterDigest {
|
|
70
|
+
readonly file: string;
|
|
71
|
+
readonly title: string;
|
|
72
|
+
readonly headings: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
/** 技术选型条目。 */
|
|
75
|
+
export interface TechChoiceEntry {
|
|
76
|
+
readonly item: string;
|
|
77
|
+
readonly choice: string;
|
|
78
|
+
readonly reason: string;
|
|
79
|
+
readonly alternative: string;
|
|
80
|
+
}
|
|
81
|
+
/** 从答辩素材里解析出的结构化事实。 */
|
|
82
|
+
export interface DefenseMaterials {
|
|
83
|
+
readonly title: string;
|
|
84
|
+
readonly chapters: readonly ChapterDigest[];
|
|
85
|
+
readonly techChoices: readonly TechChoiceEntry[];
|
|
86
|
+
readonly decisions: readonly string[];
|
|
87
|
+
readonly testCases: number;
|
|
88
|
+
readonly resultFiles: readonly string[];
|
|
89
|
+
readonly gitCommits?: number;
|
|
90
|
+
readonly bibCount?: number;
|
|
91
|
+
}
|
|
92
|
+
/** 时长估算。 */
|
|
93
|
+
export interface PlanEstimate {
|
|
94
|
+
/** 页数下限(每页秒数上限换算成分钟)。 */
|
|
95
|
+
readonly minMinutes: number;
|
|
96
|
+
/** 页数上限(每页秒数下限换算成分钟)。 */
|
|
97
|
+
readonly maxMinutes: number;
|
|
98
|
+
readonly secondsPerSlideMin: number;
|
|
99
|
+
readonly secondsPerSlideMax: number;
|
|
100
|
+
readonly advice: string;
|
|
101
|
+
}
|
|
102
|
+
/** 幻灯片计划(纯函数产物,可直接序列化)。 */
|
|
103
|
+
export interface SlidePlan {
|
|
104
|
+
readonly title: string;
|
|
105
|
+
readonly audience: Audience;
|
|
106
|
+
readonly pages: number;
|
|
107
|
+
readonly slides: readonly PlannedSlide[];
|
|
108
|
+
readonly estimate: PlanEstimate;
|
|
109
|
+
/** 入参页码被夹到骨架允许的区间时给出说明。 */
|
|
110
|
+
readonly pageAdjustment?: string;
|
|
111
|
+
/** 素材来源模式。 */
|
|
112
|
+
readonly source: MaterialSource;
|
|
113
|
+
/** 素材缺失标注(无则为 undefined)。 */
|
|
114
|
+
readonly note?: string;
|
|
115
|
+
/** 其它需要用户注意的事项。 */
|
|
116
|
+
readonly warnings: readonly string[];
|
|
117
|
+
}
|
|
118
|
+
/** 去掉 Markdown 修饰(粗体、行内代码、列表符号),得到纯文本。 */
|
|
119
|
+
export declare function stripMarkdownInline(text: string): string;
|
|
120
|
+
/** 中文字符数(用于判断素材章节是否"已撰写")。 */
|
|
121
|
+
export declare function cjkCount(text: string): number;
|
|
122
|
+
/**
|
|
123
|
+
* 截断单条要点:`limit` 字以内原样返回;超出则截到 `limit - 1` 并补省略号,
|
|
124
|
+
* 保证总长不超过 limit,并把 `truncated` 标记为 true(原文保留在 `original`)。
|
|
125
|
+
*/
|
|
126
|
+
export declare function truncateBullet(text: string, limit?: number): {
|
|
127
|
+
text: string;
|
|
128
|
+
truncated: boolean;
|
|
129
|
+
original?: string;
|
|
130
|
+
};
|
|
131
|
+
/** 标题超长同样截断(20 字硬上限)。 */
|
|
132
|
+
export declare function truncateTitle(title: string, limit?: number): string;
|
|
133
|
+
/**
|
|
134
|
+
* 解析 `07-答辩/答辩素材.md`(thesis_defense 的产物)。
|
|
135
|
+
*
|
|
136
|
+
* 容错原则:任何一段缺失都只是"该段没有事实",绝不用默认值伪造内容。
|
|
137
|
+
*/
|
|
138
|
+
export declare function parseDefenseMaterials(markdown: string): DefenseMaterials;
|
|
139
|
+
/**
|
|
140
|
+
* 回退解析:从 `06-论文/章节/*.md` 提取章节标题与该章二三级小节标题。
|
|
141
|
+
*
|
|
142
|
+
* 这是"素材未生成"时的降级路径——只搬运论文章节里**已经写好**的标题,
|
|
143
|
+
* 不推断、不补全任何系统细节。
|
|
144
|
+
*/
|
|
145
|
+
export declare function parseChapterFallback(files: readonly {
|
|
146
|
+
readonly name: string;
|
|
147
|
+
readonly text: string;
|
|
148
|
+
}[]): ChapterDigest[];
|
|
149
|
+
/** 展开顺序的候选段落(含页面模板与素材填充规则)。 */
|
|
150
|
+
type SegmentId = 'background' | 'status' | 'route' | 'design' | 'impl' | 'test' | 'conclusion';
|
|
151
|
+
/** 段落页数分配结果。 */
|
|
152
|
+
export interface Composition {
|
|
153
|
+
readonly ids: readonly SegmentId[];
|
|
154
|
+
readonly parts: Readonly<Record<SegmentId, number>>;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 按目标页数分配各段落页数:固定头(封面、目录)与固定尾(创新点、致谢)各 1 页,
|
|
158
|
+
* 中间的 7 个段落至少各 1 页,多出来的页数按可展开顺序补 2 页以上的段落。
|
|
159
|
+
*/
|
|
160
|
+
export declare function composeSlides(targetPages: number): Composition;
|
|
161
|
+
/** 听众档位对应的时长与页数参数。 */
|
|
162
|
+
export declare function audienceProfile(audience: Audience): {
|
|
163
|
+
readonly label: string;
|
|
164
|
+
readonly secondsMin: number;
|
|
165
|
+
readonly secondsMax: number;
|
|
166
|
+
readonly pages: readonly [number, number];
|
|
167
|
+
readonly defaultPages: number;
|
|
168
|
+
readonly advice: string;
|
|
169
|
+
};
|
|
170
|
+
/** {@link buildSlidePlan} 的输入。 */
|
|
171
|
+
export interface BuildSlidePlanInput {
|
|
172
|
+
/** 课题标题(通常来自进度台账)。 */
|
|
173
|
+
readonly title: string;
|
|
174
|
+
readonly audience: Audience;
|
|
175
|
+
/** 目标页数(会被夹到骨架允许的区间,并在结果里报告)。 */
|
|
176
|
+
readonly targetPages: number;
|
|
177
|
+
/** `07-答辩/答辩素材.md` 原文;缺失时传 undefined。 */
|
|
178
|
+
readonly defenseMaterials?: string;
|
|
179
|
+
/** 素材缺失时的回退章节(真实文件内容)。 */
|
|
180
|
+
readonly chapterFiles?: readonly {
|
|
181
|
+
readonly name: string;
|
|
182
|
+
readonly text: string;
|
|
183
|
+
}[];
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 生成幻灯片计划(纯函数;不读磁盘、不编造素材)。
|
|
187
|
+
*
|
|
188
|
+
* 步骤:解析素材 → 取骨架 → 按目标页数补齐段落 → 填素材 → 截断要点 → 估时。
|
|
189
|
+
*/
|
|
190
|
+
export declare function buildSlidePlan(input: BuildSlidePlanInput): SlidePlan;
|
|
191
|
+
/** 读盘结果:台账标题、素材原文、回退章节。 */
|
|
192
|
+
export interface OutlineInputs {
|
|
193
|
+
readonly ledgerTitle: string;
|
|
194
|
+
readonly defenseMaterials?: string;
|
|
195
|
+
readonly chapterFiles: readonly {
|
|
196
|
+
readonly name: string;
|
|
197
|
+
readonly text: string;
|
|
198
|
+
}[];
|
|
199
|
+
}
|
|
200
|
+
/** 从进度台账提取课题标题(与 thesis_build/thesis_defense 的口径一致)。 */
|
|
201
|
+
export declare function titleFromLedger(ledger: string | undefined): string;
|
|
202
|
+
/** 装载生成幻灯计划所需的全部输入(素材 → 回退章节 → 台账标题)。 */
|
|
203
|
+
export declare function loadOutlineInputs(fs: FileSystem, root: string, signal?: AbortSignal): Promise<OutlineInputs>;
|
|
204
|
+
/** 计划的人类可读摘要(工具返回值用,有界)。 */
|
|
205
|
+
export declare function summarizePlan(plan: SlidePlan): string[];
|
|
206
|
+
export {};
|