dsh-thesis 1.0.1 → 2.0.1

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 (66) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE.md +1 -1
  3. package/README.md +51 -86
  4. package/docs/autopilot.md +73 -0
  5. package/docs/config.md +113 -0
  6. package/{DESIGN.md → docs/design.md} +69 -74
  7. package/docs/host-api.md +40 -4
  8. package/docs/release.md +126 -0
  9. package/docs/tools.md +76 -0
  10. package/docs/troubleshooting.md +88 -0
  11. package/docs/usage.md +87 -0
  12. package/lib/autopilot/index.d.ts +38 -0
  13. package/lib/autopilot/index.js +159 -0
  14. package/lib/autopilot/intent.d.ts +40 -0
  15. package/lib/autopilot/intent.js +82 -0
  16. package/lib/autopilot/messages.d.ts +22 -0
  17. package/lib/autopilot/messages.js +35 -0
  18. package/lib/autopilot/types.d.ts +60 -0
  19. package/lib/autopilot/types.js +10 -0
  20. package/lib/codewalk/engine.d.ts +1 -1
  21. package/lib/codewalk/engine.js +1 -1
  22. package/lib/codewalk/gate.d.ts +1 -1
  23. package/lib/codewalk/gate.js +1 -1
  24. package/lib/codewalk/host-types.d.ts +0 -29
  25. package/lib/codewalk/prestep.js +1 -1
  26. package/lib/codewalk/tools.js +2 -2
  27. package/lib/config.d.ts +17 -0
  28. package/lib/config.js +9 -1
  29. package/lib/dedup/index.js +0 -6
  30. package/lib/index.d.ts +3 -1
  31. package/lib/index.js +10 -7
  32. package/lib/ingest/index.d.ts +1 -1
  33. package/lib/ingest/index.js +1 -3
  34. package/lib/intake/index.d.ts +0 -17
  35. package/lib/intake/index.js +0 -51
  36. package/lib/intake/questions.d.ts +1 -1
  37. package/lib/intake/questions.js +1 -1
  38. package/lib/paper/lib/layout.d.ts +1 -1
  39. package/lib/paper/lib/layout.js +2 -2
  40. package/lib/paper/lib/ledger.d.ts +1 -1
  41. package/lib/paper/lib/ledger.js +1 -1
  42. package/lib/paper/register.d.ts +2 -2
  43. package/lib/paper/register.js +2 -8
  44. package/lib/paper/tools/decision.js +1 -1
  45. package/lib/paper/tools/init.d.ts +1 -1
  46. package/lib/paper/tools/init.js +1 -1
  47. package/lib/ppt/index.d.ts +1 -2
  48. package/lib/ppt/index.js +1 -2
  49. package/lib/shared/define-tool.d.ts +1 -1
  50. package/lib/shared/define-tool.js +1 -1
  51. package/package.json +17 -17
  52. package/skills/code-walkthrough/SKILL.md +5 -3
  53. package/skills/thesis-defense/SKILL.md +1 -1
  54. package/skills/thesis-pipeline/SKILL.md +3 -3
  55. package/lib/codewalk/commands.d.ts +0 -23
  56. package/lib/codewalk/commands.js +0 -168
  57. package/lib/commands.d.ts +0 -33
  58. package/lib/commands.js +0 -60
  59. package/lib/dedup/commands.d.ts +0 -21
  60. package/lib/dedup/commands.js +0 -93
  61. package/lib/ingest/commands.d.ts +0 -23
  62. package/lib/ingest/commands.js +0 -74
  63. package/lib/paper/commands.d.ts +0 -13
  64. package/lib/paper/commands.js +0 -84
  65. package/lib/ppt/commands.d.ts +0 -28
  66. package/lib/ppt/commands.js +0 -71
package/lib/commands.js DELETED
@@ -1,60 +0,0 @@
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
- }
@@ -1,21 +0,0 @@
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;
@@ -1,93 +0,0 @@
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
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * 斜杠命令:`/thesis-ingest`。
3
- *
4
- * 命令名遵循 DSH 的 `[a-z][a-z0-9_-]*` 约束(kebab-case),与 `/thesis-check` 等同一套写法。
5
- * 命令只是 `thesis_ingest` 工具的薄封装:把命令行输入解析成 `IngestArgs` 后交给 `runIngest`,
6
- * 因此行为与工具**完全一致**。
7
- *
8
- * @module dsh-thesis/ingest
9
- */
10
- import type { FileSystem } from '@deepseek-ai/dsh-fs';
11
- import type { IngestOptions } from '../config.ts';
12
- import { type IngestArgs } from './index.ts';
13
- /**
14
- * 解析 `/thesis-ingest` 的输入。
15
- *
16
- * 用法:`/thesis-ingest <绝对路径> [--glob docx,pdf] [--dry]`
17
- * - `<绝对路径>`:文件或目录(目录递归;源码目录会产出代码结构摘要)
18
- * - `--glob`:后缀过滤,逗号分隔
19
- * - `--dry`:只读不落盘(不写材料清单与摘要文件)
20
- */
21
- export declare function parseIngestCommandInput(rawInput: string): IngestArgs;
22
- /** 构造 `/thesis-ingest` 命令定义。 */
23
- export declare function ingestCommand(fs: FileSystem, options: IngestOptions): unknown;
@@ -1,74 +0,0 @@
1
- /**
2
- * 斜杠命令:`/thesis-ingest`。
3
- *
4
- * 命令名遵循 DSH 的 `[a-z][a-z0-9_-]*` 约束(kebab-case),与 `/thesis-check` 等同一套写法。
5
- * 命令只是 `thesis_ingest` 工具的薄封装:把命令行输入解析成 `IngestArgs` 后交给 `runIngest`,
6
- * 因此行为与工具**完全一致**。
7
- *
8
- * @module dsh-thesis/ingest
9
- */
10
- import { diskIO, renderOutcome, runIngest } from "./index.js";
11
- async function safe(work) {
12
- try {
13
- return { kind: 'success', text: await work() };
14
- }
15
- catch (error) {
16
- return { kind: 'error', text: error instanceof Error ? error.message : String(error) };
17
- }
18
- }
19
- /**
20
- * 解析 `/thesis-ingest` 的输入。
21
- *
22
- * 用法:`/thesis-ingest <绝对路径> [--glob docx,pdf] [--dry]`
23
- * - `<绝对路径>`:文件或目录(目录递归;源码目录会产出代码结构摘要)
24
- * - `--glob`:后缀过滤,逗号分隔
25
- * - `--dry`:只读不落盘(不写材料清单与摘要文件)
26
- */
27
- export function parseIngestCommandInput(rawInput) {
28
- const tokens = rawInput.trim().split(/\s+/).filter(token => token !== '');
29
- let path;
30
- let glob;
31
- let write = true;
32
- for (let index = 0; index < tokens.length; index += 1) {
33
- const token = tokens[index];
34
- const lowered = token.toLowerCase();
35
- if (lowered === '--glob' || lowered === '-g') {
36
- glob = tokens[++index];
37
- if (glob === undefined)
38
- throw new Error('--glob 后面要跟后缀列表,如 --glob docx,pdf');
39
- continue;
40
- }
41
- if (lowered === '--dry' || lowered === '--no-write') {
42
- write = false;
43
- continue;
44
- }
45
- if (token.startsWith('-'))
46
- throw new Error(`无法识别的选项「${token}」`);
47
- if (path !== undefined)
48
- throw new Error(`只能指定一个路径(多余的是「${token}」)`);
49
- path = token;
50
- }
51
- if (path === undefined) {
52
- throw new Error('用法:/thesis-ingest <绝对路径> [--glob docx,pdf] [--dry]');
53
- }
54
- return {
55
- path,
56
- ...(glob !== undefined ? { glob } : {}),
57
- ...(write ? {} : { write: false }),
58
- };
59
- }
60
- /** 构造 `/thesis-ingest` 命令定义。 */
61
- export function ingestCommand(fs, options) {
62
- return {
63
- name: 'thesis-ingest',
64
- description: '摄取材料:docx/xlsx/pptx/pdf/md/csv/bib + 源码目录 → 文本与代码结构摘要,写 00-管理/材料清单.md',
65
- input: { hint: '<绝对路径> [--glob docx,pdf] [--dry]' },
66
- async handler(invocation) {
67
- return await safe(async () => {
68
- const args = parseIngestCommandInput(invocation.rawInput);
69
- const outcome = await runIngest(fs, diskIO, args, invocation.agent.session.header.cwd, options, invocation.signal);
70
- return renderOutcome(outcome, options.maxChars);
71
- });
72
- },
73
- };
74
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * 斜杠命令:/thesis-status 与 /thesis-decide。
3
- *
4
- * 注:DSH 命令名只允许 [a-z][a-z0-9_-]*,因此使用 kebab-case
5
- * (DESIGN.md 中的 /thesis:status 在此修正为 /thesis-status)。
6
- * 其余命令(/thesis-lit、/thesis-check、/thesis-build)随 M2/M4 工具一并注册。
7
- */
8
- import type { FileSystem } from '@deepseek-ai/dsh-fs';
9
- export declare function statusCommand(fs: FileSystem): unknown;
10
- export declare function decideCommand(fs: FileSystem): unknown;
11
- export declare function litCommand(fs: FileSystem): unknown;
12
- export declare function checkCommand(fs: FileSystem): unknown;
13
- export declare function buildCommand(fs: FileSystem): unknown;
@@ -1,84 +0,0 @@
1
- /**
2
- * 斜杠命令:/thesis-status 与 /thesis-decide。
3
- *
4
- * 注:DSH 命令名只允许 [a-z][a-z0-9_-]*,因此使用 kebab-case
5
- * (DESIGN.md 中的 /thesis:status 在此修正为 /thesis-status)。
6
- * 其余命令(/thesis-lit、/thesis-check、/thesis-build)随 M2/M4 工具一并注册。
7
- */
8
- import { writeFile } from 'node:fs/promises';
9
- import { runDecision } from "./tools/decision.js";
10
- import { runLitSearch } from "./tools/lit.js";
11
- import { runProgress } from "./tools/progress.js";
12
- import { buildSummary, runBuild } from "./tools/build.js";
13
- import { runCheck } from "./tools/check.js";
14
- function cwdOf(invocation) {
15
- return invocation.agent.session.header.cwd;
16
- }
17
- async function safe(work) {
18
- try {
19
- return { kind: 'success', text: await work() };
20
- }
21
- catch (error) {
22
- return { kind: 'error', text: error instanceof Error ? error.message : String(error) };
23
- }
24
- }
25
- export function statusCommand(fs) {
26
- return {
27
- name: 'thesis-status',
28
- description: '查看毕业设计进度台账:当前阶段、人工关卡、任务状态',
29
- async handler(invocation) {
30
- return await safe(async () => runProgress(fs, { action: 'report' }, cwdOf(invocation), invocation.signal));
31
- },
32
- };
33
- }
34
- export function decideCommand(fs) {
35
- return {
36
- name: 'thesis-decide',
37
- description: '记录一条关键决定(内容/理由/备选),写入决定日志',
38
- input: { hint: '决定内容' },
39
- async handler(invocation) {
40
- const content = invocation.rawInput.trim();
41
- if (content === '') {
42
- return { kind: 'error', text: '用法:/thesis-decide <决定内容>。内容不能为空。' };
43
- }
44
- return await safe(async () => runDecision(fs, { content }, cwdOf(invocation), invocation.signal));
45
- },
46
- };
47
- }
48
- export function litCommand(fs) {
49
- return {
50
- name: 'thesis-lit',
51
- description: '从真实学术数据库检索文献(结果写入检索缓存与审计记录)',
52
- input: { hint: '检索关键词' },
53
- async handler(invocation) {
54
- const query = invocation.rawInput.trim();
55
- if (query === '') {
56
- return { kind: 'error', text: '用法:/thesis-lit <关键词>。例如 /thesis-lit 目标检测 小目标' };
57
- }
58
- return await safe(async () => runLitSearch(fs, { query, limit: 5 }, cwdOf(invocation), invocation.signal, fetch));
59
- },
60
- };
61
- }
62
- export function checkCommand(fs) {
63
- return {
64
- name: 'thesis-check',
65
- description: '全文综合检查:引用/字数/图表/术语/模板,报告写入 08-合规/',
66
- async handler(invocation) {
67
- return await safe(async () => runCheck(fs, cwdOf(invocation), invocation.signal));
68
- },
69
- };
70
- }
71
- export function buildCommand(fs) {
72
- return {
73
- name: 'thesis-build',
74
- description: '构建论文.docx(优先 pandoc + 学校模板,否则内置过渡引擎)',
75
- async handler(invocation) {
76
- return await safe(async () => {
77
- const outcome = await runBuild(fs, {}, cwdOf(invocation), invocation.signal, async (absPath, data) => {
78
- await writeFile(absPath, data);
79
- });
80
- return buildSummary(outcome);
81
- });
82
- },
83
- };
84
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * 斜杠命令 `/thesis-defense` 的**幻灯侧**输入解析。
3
- *
4
- * 命令名遵循 DSH 的 `[a-z][a-z0-9_-]*` 约束(kebab-case)。命令注册本身在
5
- * 装配层(`src/index.ts`)完成:`/thesis-defense` 同时覆盖答辩幻灯与代码演练,
6
- * 由装配层聚合两侧的处理器——这样 `ppt/` 与 `codewalk/` 之间没有反向依赖。
7
- *
8
- * @module dsh-thesis/ppt
9
- */
10
- import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands';
11
- import type { FileSystem } from '@deepseek-ai/dsh-fs';
12
- import type { PptOptions } from '../config.ts';
13
- import { type PptArgs, type PptDeps } from './actions.ts';
14
- /** 幻灯侧的子命令(与代码演练侧区分)。 */
15
- export declare const PPT_SUBCOMMANDS: readonly string[];
16
- /** 把 `/thesis-defense <输入>` 的幻灯侧部分解析成工具参数。 */
17
- export declare function parsePptCommandInput(rawInput: string): PptArgs;
18
- /**
19
- * 幻灯侧的命令处理器(由装配层在 `/thesis-defense` 下调度)。
20
- *
21
- * `rawInput` 仍是命令名之后的全部输入;本函数只关心幻灯侧子命令。
22
- */
23
- export declare function handlePptCommand(fs: FileSystem, options: {
24
- ppt: PptOptions;
25
- }, invocation: CommandInvocation, deps?: PptDeps): Promise<CommandResult>;
26
- /** 命令的展示元信息(装配层注册时使用)。 */
27
- export declare const PPT_COMMAND_DESCRIPTION = "\u7B54\u8FA9\u5E7B\u706F\uFF1A\u751F\u6210 Marp Markdown\uFF08prepare/outline\uFF09/ \u6267\u884C\u5916\u90E8\u8F6C\u6362\uFF08convert\uFF09/ \u8D28\u91CF\u68C0\u67E5\uFF08check\uFF09/ \u8F6C\u6362\u6307\u5F15\uFF08guide\uFF09";
28
- export declare const PPT_COMMAND_HINT = "prepare | convert | check | guide [\u9875\u6570] [undergrad|master] [force]";
@@ -1,71 +0,0 @@
1
- /**
2
- * 斜杠命令 `/thesis-defense` 的**幻灯侧**输入解析。
3
- *
4
- * 命令名遵循 DSH 的 `[a-z][a-z0-9_-]*` 约束(kebab-case)。命令注册本身在
5
- * 装配层(`src/index.ts`)完成:`/thesis-defense` 同时覆盖答辩幻灯与代码演练,
6
- * 由装配层聚合两侧的处理器——这样 `ppt/` 与 `codewalk/` 之间没有反向依赖。
7
- *
8
- * @module dsh-thesis/ppt
9
- */
10
- import { runPptAction } from "./actions.js";
11
- async function safe(work) {
12
- try {
13
- return { kind: 'success', text: await work() };
14
- }
15
- catch (error) {
16
- return { kind: 'error', text: error instanceof Error ? error.message : String(error) };
17
- }
18
- }
19
- /** 幻灯侧的子命令(与代码演练侧区分)。 */
20
- export const PPT_SUBCOMMANDS = ['prepare', 'convert', 'check', 'guide'];
21
- /** 把 `/thesis-defense <输入>` 的幻灯侧部分解析成工具参数。 */
22
- export function parsePptCommandInput(rawInput) {
23
- const tokens = rawInput.trim().split(/\s+/).filter(t => t !== '');
24
- let action = 'outline';
25
- let pages;
26
- let audience;
27
- let force = false;
28
- for (const token of tokens) {
29
- const lowered = token.toLowerCase();
30
- if (lowered === 'prepare' || lowered === 'outline' || lowered === 'convert' || lowered === 'check' || lowered === 'guide') {
31
- action = lowered === 'prepare' ? 'outline' : lowered;
32
- continue;
33
- }
34
- if (lowered === 'undergrad' || lowered === 'master') {
35
- audience = lowered;
36
- continue;
37
- }
38
- if (lowered === 'force' || lowered === '--force' || lowered === '-f') {
39
- force = true;
40
- continue;
41
- }
42
- const number = Number.parseInt(token, 10);
43
- if (Number.isFinite(number)) {
44
- pages = number;
45
- continue;
46
- }
47
- throw new Error(`无法识别的参数「${token}」。用法:/thesis-defense prepare [页数] [undergrad|master] [force]`
48
- + ' | convert | check | guide | new <原始项目路径> | status | check [里程碑]');
49
- }
50
- return {
51
- action,
52
- ...(pages !== undefined ? { pages } : {}),
53
- ...(audience !== undefined ? { audience } : {}),
54
- ...(force ? { force: true } : {}),
55
- };
56
- }
57
- /**
58
- * 幻灯侧的命令处理器(由装配层在 `/thesis-defense` 下调度)。
59
- *
60
- * `rawInput` 仍是命令名之后的全部输入;本函数只关心幻灯侧子命令。
61
- */
62
- export async function handlePptCommand(fs, options, invocation, deps = {}) {
63
- return await safe(async () => {
64
- const args = parsePptCommandInput(invocation.rawInput);
65
- const outcome = await runPptAction(fs, invocation.agent.session.header.cwd, args, options, invocation.signal, deps);
66
- return outcome.text;
67
- });
68
- }
69
- /** 命令的展示元信息(装配层注册时使用)。 */
70
- export const PPT_COMMAND_DESCRIPTION = '答辩幻灯:生成 Marp Markdown(prepare/outline)/ 执行外部转换(convert)/ 质量检查(check)/ 转换指引(guide)';
71
- export const PPT_COMMAND_HINT = 'prepare | convert | check | guide [页数] [undergrad|master] [force]';