dsh-maoying 0.1.11 → 0.1.13
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/README.en.md +169 -0
- package/README.md +17 -2
- package/bin/maoying.mjs +95 -50
- package/docs/creator-panel.md +33 -7
- package/docs/design.md +10 -7
- package/docs/spec.md +7 -4
- package/index.mjs +242 -88
- package/lib/base.css +156 -25
- package/lib/client.js +688 -232
- package/lib/epub.mjs +37 -6
- package/lib/novel-parse.mjs +171 -46
- package/lib/panel-core.mjs +116 -10
- package/lib/version.mjs +122 -36
- package/lib/yaml-loader.mjs +10 -2
- package/package.json +7 -2
- package/presets/maoying/agent.cordis.v2.yml +6 -1
- package/presets/maoying/agent.cordis.yml +5 -2
- package/presets/maoying/restrict.mjs +5 -6
- package/skills/maoying-review-checklist/SKILL.md +1 -1
- package/templates/novel/README.md +3 -3
- package/templates/novel/foreshadows.yml +1 -0
- package/templates/novel/timeline.yml +1 -1
package/README.en.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# dsh-maoying (Maoying)
|
|
2
|
+
|
|
3
|
+
[中文](README.md) | English
|
|
4
|
+
|
|
5
|
+
**Maoying** literally means "the brush" — in *Maoying Zhuan* (毛颖传, "The Biography of the Brush"), Han Yu wrote a writing brush as a courtier with a fief and a title. dsh-maoying fits that brush into DSH (DeepSeek Harness): **a brush that manages a crew of helpers and guards a stack of manuscripts** — a web-novel writing assistant plugin, fully original, released under MIT.
|
|
6
|
+
|
|
7
|
+
Once installed, the main agent becomes the **editor-in-chief of your novel**: it runs a seven-phase workflow with a six-step writing method, delegating outlining, drafting, review, lore-keeping and research to five helpers who each carry only their own tools. The whole novel is a structured markdown/YAML `novel/` directory in the workspace (character cards, foreshadowing state machine, timeline, clue board, narrative memory) — agents read and write files directly, **no database, no API layer**. "Show all unresolved foreshadowing" is one line of grep.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Editor-in-chief + five helpers**: the main agent plans, connects, reviews and advances states; the outliner (outline), writer (write), reviewer (review), lore keeper (lore) and researcher (research) each carry their own tools. Trivial errands like finding a file or checking a foreshadow status, the chief handles itself with glob/grep.
|
|
12
|
+
- **novel/ as a file database**: character cards, worldbuilding & science settings with three-color tags, relations graph, foreshadow ledger, timeline, narrative memory, clue board — all grep-friendly flat-key/top-level-array YAML, human-editable, git-versioned, no migrations, no ops.
|
|
13
|
+
- **Seven-phase workflow**: idea → setting → outline → writing → review → consistency → export. The phase lives in the `phase` key of project.yml; the chief advances it at each milestone and reports to you.
|
|
14
|
+
- **Writing six-step method**: continuity check → outline → draft → review → revision → finalization, looping per chapter; paired with the Collab four-step co-writing flow (setting co-creation → volume planning → golden three chapters → handoff loop) it takes a new book from zero to rolling updates.
|
|
15
|
+
- **Foreshadowing state machine**: planted → progressing → resolved / abandoned, with planting and payoff chapter numbers, expected-payoff scheduling and priority tiers — a long serial never drops a thread; past 15 active foreshadowing entries the chief proactively suggests cleanup.
|
|
16
|
+
- **Context on demand**: the chief reads index.yml for the big picture, then pulls only what the chapter at hand needs (previous chapter's ending ≈1500 chars + its outline + on-stage character cards + active foreshadow excerpts) — never the whole book.
|
|
17
|
+
- **Narrow tool surface**: the chief sees only "role delegation + internal retrieval + coordination"; helpers carry their line's tools (reviewer read-only, researcher web+notes only) — less per-request tool overhead and context weight.
|
|
18
|
+
- **Research**: the research helper searches the web for historical/geographic/domain material, files notes under `novel/research/`, separating fact from common lore from legend, with sources — a dividend of the DSH architecture.
|
|
19
|
+
- **Creator panel**: a full-page web panel from the DSH sidebar — Overview / Chapters / Characters / Foreshadowing / Relations / Timeline / Worldbuilding / Index Board / Novel Settings; full-page chapter reading (outline side-by-side + five-dimension fields + status flow), form-based project.yml editing, UI language (中文/English), six font-size steps, and automatic new-version detection.
|
|
20
|
+
- **CLI fallback**: `maoying init / stats / index / export / brief / check` — six zero-dependency commands for scaffolding, stats, index rebuild, export, per-chapter writing cards and static checks.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
Prerequisites: **Node.js ≥ 18** and an installed DSH host (**0.1.7+**, see "Compatibility").
|
|
25
|
+
|
|
26
|
+
The official plugin channel — one command, then restart:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
dsh plugin --profile web add dsh-maoying
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Restart DSH (`dsh web`)**: at startup the plugin installs the preset into `~/.dsh/.agent-presets/maoying/` and writes the managed declaration block into `~/.dsh/cordis.patch.yml` — no buttons to press. Pick "**毛颖创作 (Maoying)**" in the preset selector of a new session; to make it default, select it in Settings → Agent Presets.
|
|
33
|
+
|
|
34
|
+
For a development checkout (you want to modify the plugin or preset sources), link the repo directly; changes take effect after a restart:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/lifangjin/dsh-maoying.git
|
|
38
|
+
dsh plugin --profile web add link:"$PWD/dsh-maoying"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Only want the command-line tool, without the plugin? The zero-dependency CLI works standalone via `npx`, or as a global command (both npm channels, independent of the plugin):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx maoying init # run without installing
|
|
45
|
+
npm install -g dsh-maoying # or install a global maoying command
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## How to Use
|
|
49
|
+
|
|
50
|
+
With the preset selected, just talk to the chief; it routes by the workflow itself:
|
|
51
|
+
|
|
52
|
+
**Start a new book (Collab four-step co-writing)**:
|
|
53
|
+
|
|
54
|
+
> I want to write an Eastern fantasy where the protagonist's golden finger is hearing objects speak and appraising treasures to level up.
|
|
55
|
+
|
|
56
|
+
The chief aligns genre and tone with you, delegates core character cards and worldbuilding to the lore keeper, volume planning to the outliner, then has the golden three chapters (world hook, protagonist motivation, core conflict) written and delivered.
|
|
57
|
+
|
|
58
|
+
**Per-chapter writing (Writing six-step method)**:
|
|
59
|
+
|
|
60
|
+
> Can we start chapter 3?
|
|
61
|
+
|
|
62
|
+
The chief runs the continuity check, has the outliner produce the chapter outline (with the five narrative dimensions), and after your sign-off sends it to the writer, then the reviewer (checklist + four-dimension consistency scan); issues go back to the writer; once accepted, the lore keeper does finalization upkeep — you only call the shots at decision points.
|
|
63
|
+
|
|
64
|
+
**Consistency pass**:
|
|
65
|
+
|
|
66
|
+
> Run a consistency check over the first 20 chapters.
|
|
67
|
+
|
|
68
|
+
The chief advances the phase to consistency; the reviewer scans four dimensions — conflicts / timeline / foreshadowing / settings — and returns issues ranked by severity.
|
|
69
|
+
|
|
70
|
+
A reply of "ok / sure / go on" counts as confirmation; the chief advances directly without echoing it back.
|
|
71
|
+
|
|
72
|
+
## Creator Panel
|
|
73
|
+
|
|
74
|
+
Open "**毛颖创作台**" at the bottom of the DSH sidebar — a full-page cockpit for the human eye, beyond what agents do:
|
|
75
|
+
|
|
76
|
+
- **Chapters**: status filters + the writing queue; click a chapter for full-page reading — back button, outline side-by-side (Markdown rendered), read-only manuscript, five narrative-dimension fields, one-click status flow.
|
|
77
|
+
- **Novel Settings**: form-based editing of project.yml — title / author / genre / planned length / language / workflow phase / co-writing mode / synopsis; saving preserves the file's existing comments.
|
|
78
|
+
- **Top bar**: current version always visible; a "new version ↗" badge links to the release page when npm has a newer one; the gear opens Settings — UI language (中文/English), six font-size steps (100%–150%), all applied immediately and remembered.
|
|
79
|
+
|
|
80
|
+
The panel only writes state (chapter status, project.yml, index.yml rebuilds), never prose — prose belongs to the author and the chief.
|
|
81
|
+
|
|
82
|
+
## Co-writing Mode (Author Writes)
|
|
83
|
+
|
|
84
|
+
Not everyone wants AI ghostwriting. The `mode` key in project.yml decides who writes the prose — three modes in one line each: `ai-draft` (default) AI drafts via the Writing six-step method; `co-write` assigns a writer per chapter, and the chief asks who writes before touching the pen; `author-draft` the author writes the whole book, and Maoying only preps, reviews and keeps the books — never ghostwrites.
|
|
85
|
+
|
|
86
|
+
The author-draft loop — you just write, hand it over, and the chores are taken care of:
|
|
87
|
+
|
|
88
|
+
> Chapter 5 is done, here it is. (paste the text, or just say chapter X is done and point at a file)
|
|
89
|
+
|
|
90
|
+
After receiving a manuscript you get three things: a **review checklist** (issues only, no rewriting — applying them is up to you), a **ledger backfill** (foreshadowing / timeline / relations / memory / clue board recorded from what your text actually does, even off-plan changes, tagged `backfill`), and the **next chapter outline** (`novel/outlines/vXX-cXXX.md`, ready to write from).
|
|
91
|
+
|
|
92
|
+
Companion commands: `maoying brief <dir> <chapter>` packs a per-chapter writing card (outline + previous ending + character cards + active foreshadowing — carry it to any editor or AI), and `maoying check <dir>` runs a zero-model static check (frontmatter completeness, foreshadow chapter references, timeline ordering — no model calls; LLM review goes through the chief).
|
|
93
|
+
|
|
94
|
+
## Methodology Skills
|
|
95
|
+
|
|
96
|
+
The plugin ships 4 web-novel methodology skills. At startup the installer drops four `maoying-*` directories under `~/.dsh/skills/` (i.e. `<home>/skills/`) where the host's skill panel lists them; the outliner, writer, reviewer and lore keeper load them by scenario — no manual steps:
|
|
97
|
+
|
|
98
|
+
| Skill | One-liner |
|
|
99
|
+
|---|---|
|
|
100
|
+
| maoying-golden-opening | Golden three chapters: role split of the first three chapters (world hook → motivation → conflict detonation), the "something happens in the first 300 words" rule, information-release discipline and a self-check list |
|
|
101
|
+
| maoying-beat-pacing | Payoff pacing: a payoff-type library, suppression-release ratios, chapter/arc/book pacing curves and chapter-end hook selection |
|
|
102
|
+
| maoying-review-checklist | Review checklist: six layers (language / pacing / characterization / logic / foreshadowing / spoilers) with P0/P1/P2 severity |
|
|
103
|
+
| maoying-foreshadow-discipline | Foreshadow management: register on planting, pacing & scheduling, the "surprising yet reasonable" payoff bar, and ledger cleanup thresholds |
|
|
104
|
+
|
|
105
|
+
The installer only touches `maoying-*` prefixed directories; your own skills under `~/.dsh/skills/` are untouched, byte for byte. After uninstalling the plugin, remove the skill directories manually (all four together):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
dsh plugin --profile web remove dsh-maoying
|
|
109
|
+
rm -rf "${DSH_HOME:-$HOME/.dsh}/skills/"maoying-*
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## The novel/ Data Spec
|
|
113
|
+
|
|
114
|
+
The `novel/` directory is the plugin's entire database: project.yml (metadata + seven-phase state), volumes.yml, chapters/ (frontmatter five narrative dimensions + prose), characters/ (character cards), world/, science/ (three-color settings), relations.yml, foreshadows.yml (the state machine), timeline.yml, memories.yml (with appearance records and the promise ledger), clue-board.yml, research/, exports/. Full formats, examples and design rationale:
|
|
115
|
+
|
|
116
|
+
- Quick reference: `novel/README.md` after project init (template source: [templates/novel/README.md](templates/novel/README.md))
|
|
117
|
+
- Full spec: [docs/spec.md](docs/spec.md) (why not SQLite, five-dimension field definitions, foreshadow state-machine semantics, appearance-record conventions)
|
|
118
|
+
|
|
119
|
+
## CLI
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
maoying init [dir] # scaffold novel/ (default ./novel; existing files are always skipped, never overwritten)
|
|
123
|
+
maoying stats [dir] # per-chapter CJK counts, status breakdown, current phase
|
|
124
|
+
maoying index [dir] # rebuild index.yml from volumes.yml + chapters/ (derived file)
|
|
125
|
+
maoying export [dir] # export the book: --format md|txt|epub (default md), --out <dir> to change destination
|
|
126
|
+
maoying brief [dir] <chapter> # pack a per-chapter writing card (outline + previous ending + character cards + active foreshadowing); --out to write to disk, stdout by default
|
|
127
|
+
maoying check [dir] # static check (no model calls): frontmatter / enums / chapter numbers / foreshadow refs / timeline years / relation-card refs
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Export examples:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
maoying export novel # Markdown, into novel/exports/
|
|
134
|
+
maoying export novel --format txt # plain text
|
|
135
|
+
maoying export novel --format epub # EPUB 3 (zero-dependency packaging, importable into readers)
|
|
136
|
+
maoying export novel --format epub --out ~/Downloads # different destination
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The output directory defaults to `<dir>/exports/`, file name `<title>-<YYYYMMDD>.<ext>` (title from project.yml); chapter parsing shares the same logic as stats/index, so frontmatter never leaks into prose.
|
|
140
|
+
|
|
141
|
+
Zero dependencies (node:fs/path/url/crypto), runs from any cwd. Install one of three ways: `npm install -g dsh-maoying`, on-demand `npx maoying <command>`, or `npm link` on a dev machine (repo checkout, edits take effect immediately). With none of these, `node <plugin dir>/bin/maoying.mjs` is equivalent (a dsh plugin install also drops the package into the profile, so you can point there directly). In day-to-day writing the lore keeper rebuilds index.yml directly; the CLI is mainly for humans.
|
|
142
|
+
|
|
143
|
+
## Uninstall
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
dsh plugin --profile web remove dsh-maoying
|
|
147
|
+
rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets/maoying"
|
|
148
|
+
rm -rf "${DSH_HOME:-$HOME/.dsh}/skills/"maoying-*
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
On DSH 0.1.7+ also delete the dsh-maoying managed block in `~/.dsh/cordis.patch.yml` (the whole section between `# --- dsh-maoying preset managed (auto-generated; do not edit) ---` and `# --- end dsh-maoying preset managed ---`, markers included), so no declaration lines point at a deleted directory. The `novel/` directory is your manuscript — uninstalling never touches it.
|
|
152
|
+
|
|
153
|
+
## Compatibility
|
|
154
|
+
|
|
155
|
+
At startup the plugin reads the host version (`<home>/node_modules/@deepseek-ai/dsh/package.json`) and picks the delegation-line shape automatically — **after a host upgrade, a restart is all it takes; no reinstall**:
|
|
156
|
+
|
|
157
|
+
- **DSH 0.1.7 – 0.2.1-rc.1 → v1 inline shape**: the 5 role delegation lines inline persona and toolFilter; the managed block is 1 declaration line.
|
|
158
|
+
- **DSH 0.2.1-rc.2+ → v2 AgentPreset shape**: from that version dsh-tool-subagent rejects inline persona/toolFilter, so the plugin switches to [presets/maoying/agent.cordis.v2.yml](presets/maoying/agent.cordis.v2.yml) — the managed block writes 6 declaration lines (chief-in-chief preset + 5 role presets: outliner / writer / reviewer / lore keeper / researcher); the chief's delegation line points at the role presets via `agentPreset`, and each role's tool surface is carried by the preset's entity tool lines (reviewer read-only, researcher with web search).
|
|
159
|
+
- **Role presets appear in the preset selector** (the host has no hiding mechanism for presets); their names carry "(内部)" to mark them: in daily writing they are picked automatically via chief delegation, but you can also select a writer/researcher directly for a session without the chief.
|
|
160
|
+
- Both shapes share persona text byte-for-byte from one source ([test/composition.test.mjs](test/composition.test.mjs) guards it); if the host version can't be read, the plugin falls back to v1 conservatively.
|
|
161
|
+
- The ≤0.1.6 directory-scan track: not supported (the host dropped it).
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
[MIT](LICENSE) © 2026 lifangjin
|
|
166
|
+
|
|
167
|
+
## Acknowledgments
|
|
168
|
+
|
|
169
|
+
- "Maoying" comes from Han Yu's *Maoying Zhuan* (毛颖传): a writing brush personified, with a fief and a title.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-maoying(毛颖)
|
|
2
2
|
|
|
3
|
+
中文 | [English](README.en.md)
|
|
4
|
+
|
|
3
5
|
毛颖者,笔也——韩愈作《毛颖传》,把一支毛笔写成有封地有爵位的臣子。dsh-maoying 把这支笔装进 DSH(DeepSeek Harness):**一支管着一班帮手、守着一摞书稿的毛笔**——网文创作辅助插件,全部实现原创,MIT 发布。
|
|
4
6
|
|
|
5
7
|
装上之后,主 agent 变身**网文主编(创作总监)**:带七阶段工作流与六步写作法,把大纲、正文、审校、设定维护、素材考据分别委派给五个只带本行工具的帮手;整部小说就是工作区里的 `novel/` 结构化 markdown/YAML 目录(角色卡、伏笔状态机、时间线、线索板、叙事记忆)——agent 直接读写文件,**没有数据库,没有 API 层**。查「所有未回收伏笔」就是 grep 一行。
|
|
@@ -14,7 +16,8 @@
|
|
|
14
16
|
- **上下文按需取**:主编先读 index.yml 概览,写到哪章读哪章(前章结尾约 1500 字 + 该章大纲 + 出场角色卡 + 活跃伏笔节选),不把整本书塞进上下文。
|
|
15
17
|
- **工具面收窄**:主编只见「角色委派 + 内部检索 + 协调」白名单,帮手各带本行工具(审校只读、考据只带联网+落笔记);每次请求的工具开销与上下文占用随之下降。
|
|
16
18
|
- **素材考据**:research 帮手联网检索历史/地理/专业领域素材,笔记落 `novel/research/`,区分史实/通说/演义并附来源——DSH 架构的红利。
|
|
17
|
-
-
|
|
19
|
+
- **创作台面板**:DSH 侧栏一键打开的全页可视化面板——概览/章节/角色/伏笔/关系图/时间线/设定/索引板/小说设置九个 tab;章节整页阅读(章纲对照 + 五维 + 状态流转)、project.yml 表单化编辑、顶栏字号五档缩放与自动新版检测。
|
|
20
|
+
- **CLI 兜底**:`maoying init / stats / index / export / brief / check` 六条零依赖命令管脚手架、统计、索引重建、导出、写作卡打包与静态体检。
|
|
18
21
|
|
|
19
22
|
## 快速开始
|
|
20
23
|
|
|
@@ -66,6 +69,16 @@ npm install -g dsh-maoying # 或装成全局 maoying 命令
|
|
|
66
69
|
|
|
67
70
|
用户回「ok / 可以 / 继续」即视为确认,主编直接推进,不复述确认。
|
|
68
71
|
|
|
72
|
+
## 创作台面板
|
|
73
|
+
|
|
74
|
+
DSH 左侧栏底部点「**毛颖创作台**」打开全页面板(改文件、跑流程之外,给肉眼看的驾驶舱):
|
|
75
|
+
|
|
76
|
+
- **章节**:状态筛选 + 按单写作清单;点章节进整页阅读——返回按钮、章纲对照(Markdown 渲染)、只读正文、五维叙事字段、一键状态流转。
|
|
77
|
+
- **小说设置**:project.yml 的书名/作者/题材/篇幅/语言/工作流阶段/共创模式/简介表单化编辑,保存保留文件里原有注释。
|
|
78
|
+
- **顶栏**:当前版本号常驻;npm 上有新版时亮「有新版 ↗」徽章直连发布页;齿轮打开系统设置,界面语言中文/English 可切、字号六档(100%–150%)整体缩放,均立即生效并记忆。
|
|
79
|
+
|
|
80
|
+
面板只写状态(章节状态、project.yml、index.yml 重建),不改正文——正文永远归作者与主编。
|
|
81
|
+
|
|
69
82
|
## 共创模式(作者亲笔)
|
|
70
83
|
|
|
71
84
|
不是谁都想让 AI 代笔。project.yml 的 `mode` 键决定正文归谁写,三种模式一句话:`ai-draft`(默认)AI 代笔,走 Writing 六步法;`co-write` 逐章指派谁写,主编动笔前先问清本章归谁;`author-draft` 全书作者亲笔,毛颖只备料、审稿、记账,永不代写。
|
|
@@ -110,6 +123,8 @@ maoying init [dir] # 生成 novel/ 脚手架(默认 ./novel;已存在文
|
|
|
110
123
|
maoying stats [dir] # 逐章 CJK 字数、状态分布、当前 phase
|
|
111
124
|
maoying index [dir] # 从 volumes.yml + chapters/ 重建 index.yml(派生文件)
|
|
112
125
|
maoying export [dir] # 导出全书:--format md|txt|epub(默认 md),--out <目录> 改输出位置
|
|
126
|
+
maoying brief [dir] <chapter> # 组装单章写作卡(章纲+前章结尾+角色卡+活跃伏笔);--out 落盘,缺省 stdout
|
|
127
|
+
maoying check [dir] # 静态体检(不调模型):frontmatter/枚举/章号/伏笔引用/时间线年份/关系卡引用
|
|
113
128
|
```
|
|
114
129
|
|
|
115
130
|
export 示例:
|
|
@@ -137,7 +152,7 @@ rm -rf "${DSH_HOME:-$HOME/.dsh}/skills/"maoying-*
|
|
|
137
152
|
|
|
138
153
|
## 兼容面
|
|
139
154
|
|
|
140
|
-
|
|
155
|
+
插件启动时读宿主版本,按版本自动选择委派行形状——**宿主升级后重启即切换,无需重装插件**。版本探测是五级探针(argv[1] → 全 argv 扫描 → app.asar / desktop-runtime → Electron API → home 全局兜底),前四级锚定当前进程的真实宿主,`<home>/node_modules/@deepseek-ai/dsh/package.json` 那份 home 全局安装只做最后兜底(老 CLI 残留可能陈旧几个大版本);每级读到了什么可查创作台面板的 `host-debug` 端点(路径已脱敏):
|
|
141
156
|
|
|
142
157
|
- **DSH 0.1.7 – 0.2.1-rc.1 → v1 内联形状**:5 个角色委派行直接内联 persona 与 toolFilter,托管块 1 条声明行。
|
|
143
158
|
- **DSH 0.2.1-rc.2+ → v2 AgentPreset 形状**:该版本起 dsh-tool-subagent 拒绝内联 persona/toolFilter,插件改用 [presets/maoying/agent.cordis.v2.yml](presets/maoying/agent.cordis.v2.yml)——托管块写 6 条声明行(主编 preset + 大纲师/写手/审校/设定管理员/素材调研员 5 张角色 preset),主编委派行以 `agentPreset` 指向角色 preset,角色的工具面由 preset 里的实体工具行承载(审校只读、考据带联网检索)。
|
package/bin/maoying.mjs
CHANGED
|
@@ -17,15 +17,25 @@
|
|
|
17
17
|
* 子命令出错 exit 1,中文报错。
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync,
|
|
20
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
|
|
21
21
|
import { randomUUID } from 'node:crypto'
|
|
22
22
|
import path from 'node:path'
|
|
23
23
|
import { fileURLToPath } from 'node:url'
|
|
24
24
|
|
|
25
25
|
import { buildEpub } from '../lib/epub.mjs'
|
|
26
26
|
// 解析层与面板共用(lib/novel-parse.mjs);parseFlatYamlArray 沿用旧名
|
|
27
|
-
// parseSimpleYaml,保持本文件其余代码原样不动。
|
|
28
|
-
|
|
27
|
+
// parseSimpleYaml,保持本文件其余代码原样不动。STATUSES / splitParagraphs
|
|
28
|
+
// 也改从 lib 取——状态机与分段逻辑只留 lib 一份,bin 不再各养一副拷贝。
|
|
29
|
+
import {
|
|
30
|
+
buildBrief,
|
|
31
|
+
buildIndexYaml,
|
|
32
|
+
CHAPTER_STATUSES as STATUSES, // 章节状态机(原 bin 本地 STATUSES,展示用中文标签随 lib 走)
|
|
33
|
+
checkProject,
|
|
34
|
+
collectChapters,
|
|
35
|
+
parseFlatYamlArray as parseSimpleYaml,
|
|
36
|
+
readTextOrNull, // 读文本文件读不到返回 null(原 bin 本地拷贝,lib 导出后改 import)
|
|
37
|
+
splitParagraphs, // 正文分段(原 bin 本地 bodyParagraphs 的同款实现)
|
|
38
|
+
} from '../lib/novel-parse.mjs'
|
|
29
39
|
|
|
30
40
|
const PLUGIN_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
31
41
|
const TEMPLATE_DIR = path.join(PLUGIN_ROOT, 'templates', 'novel')
|
|
@@ -41,14 +51,6 @@ const PHASES = {
|
|
|
41
51
|
export: '导出',
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
/** 章节状态机(frontmatter status 枚举;带中文标签,展示用)。 */
|
|
45
|
-
const STATUSES = [
|
|
46
|
-
['pending', '待写'],
|
|
47
|
-
['writing', '写作中'],
|
|
48
|
-
['review', '审阅中'],
|
|
49
|
-
['accepted', '已定稿'],
|
|
50
|
-
]
|
|
51
|
-
|
|
52
54
|
// ── 基础工具 ────────────────────────────────────────────────────────────────
|
|
53
55
|
|
|
54
56
|
/** 中文报错 + exit 1。 */
|
|
@@ -57,12 +59,19 @@ function fail(message) {
|
|
|
57
59
|
process.exit(1)
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
/**
|
|
63
|
+
* 同名输出不静默覆盖:目标文件已存在时在扩展名前加序号 -2、-3……直到找到
|
|
64
|
+
* 未占用的名字。返回 { path, renamed };renamed 为 true 时调用方向 stderr
|
|
65
|
+
* 提示一句改写去向——绝不覆盖用户已有文件。
|
|
66
|
+
*/
|
|
67
|
+
function dedupeWritePath(target) {
|
|
68
|
+
if (!existsSync(target)) return { path: target, renamed: false }
|
|
69
|
+
const dir = path.dirname(target)
|
|
70
|
+
const ext = path.extname(target)
|
|
71
|
+
const stem = path.basename(target, ext)
|
|
72
|
+
for (let n = 2; ; n++) {
|
|
73
|
+
const candidate = path.join(dir, `${stem}-${n}${ext}`)
|
|
74
|
+
if (!existsSync(candidate)) return { path: candidate, renamed: true }
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
@@ -94,7 +103,14 @@ function copyTemplateTree(srcDir, destDir, relBase = '') {
|
|
|
94
103
|
function cmdInit(targetArg) {
|
|
95
104
|
if (!existsSync(TEMPLATE_DIR)) fail(`模板目录缺失:${TEMPLATE_DIR}(插件包不完整?)`)
|
|
96
105
|
const dest = path.resolve(targetArg ?? 'novel')
|
|
97
|
-
|
|
106
|
+
// 落盘点兜底:目录建不出来 / 模板拷不动(权限、只读盘等)报中文错,不甩英文栈
|
|
107
|
+
let counts
|
|
108
|
+
try {
|
|
109
|
+
counts = copyTemplateTree(TEMPLATE_DIR, dest)
|
|
110
|
+
} catch (error) {
|
|
111
|
+
fail(`写入失败:${dest}(${error.message})`)
|
|
112
|
+
}
|
|
113
|
+
const { created, skipped } = counts
|
|
98
114
|
console.log(`毛颖脚手架 → ${dest}`)
|
|
99
115
|
for (const rel of created) console.log(` 创建 ${rel}`)
|
|
100
116
|
for (const rel of skipped) console.log(` 跳过 ${rel}(已存在,未覆盖)`)
|
|
@@ -152,7 +168,12 @@ function cmdIndex(targetArg) {
|
|
|
152
168
|
const volumesRaw = parseSimpleYaml(readTextOrNull(path.join(root, 'volumes.yml')))
|
|
153
169
|
const { text, volumesCount, chaptersCount, orphansCount, totalWords } = buildIndexYaml(chapters, volumesRaw)
|
|
154
170
|
const indexPath = path.join(root, 'index.yml')
|
|
155
|
-
|
|
171
|
+
// 落盘点兜底:写不进(权限/只读盘等)报中文错,不甩英文栈
|
|
172
|
+
try {
|
|
173
|
+
writeFileSync(indexPath, text, 'utf8')
|
|
174
|
+
} catch (error) {
|
|
175
|
+
fail(`写入失败:${indexPath}(${error.message})`)
|
|
176
|
+
}
|
|
156
177
|
console.log(`已重建 ${indexPath}:${volumesCount} 卷、${chaptersCount} 章(总字数 ${totalWords})、未归卷 ${orphansCount} 章。`)
|
|
157
178
|
}
|
|
158
179
|
|
|
@@ -190,26 +211,6 @@ function volumeNumber(volume) {
|
|
|
190
211
|
return m === null ? null : Number(m[1])
|
|
191
212
|
}
|
|
192
213
|
|
|
193
|
-
/**
|
|
194
|
-
* 正文分段:空行为段界;段内多行去首尾空白后直接拼接(中文行间不加空格)。
|
|
195
|
-
* md/txt 的正文与 epub 的 <p> 共用这一套。
|
|
196
|
-
*/
|
|
197
|
-
function bodyParagraphs(body) {
|
|
198
|
-
const paragraphs = []
|
|
199
|
-
let current = []
|
|
200
|
-
for (const rawLine of String(body ?? '').split(/\r?\n/)) {
|
|
201
|
-
const line = rawLine.trim()
|
|
202
|
-
if (line === '') {
|
|
203
|
-
if (current.length > 0) paragraphs.push(current.join(''))
|
|
204
|
-
current = []
|
|
205
|
-
} else {
|
|
206
|
-
current.push(line)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (current.length > 0) paragraphs.push(current.join(''))
|
|
210
|
-
return paragraphs
|
|
211
|
-
}
|
|
212
|
-
|
|
213
214
|
/**
|
|
214
215
|
* 章节展示信息(md/txt/epub 三格式共用):卷层、章标题、分段正文。
|
|
215
216
|
* 章标题形如「卷X-章Y 章节标题」;卷 id 不合规范或章节无卷信息时退为
|
|
@@ -223,7 +224,7 @@ function chapterDisplay(ch, ordinal, volTitles) {
|
|
|
223
224
|
volume: volNum !== null ? { num: volNum, title: volTitles.get(ch.volume) ?? '' } : null,
|
|
224
225
|
heading: `${volPart} ${ch.title}`.trim(),
|
|
225
226
|
body: ch.body,
|
|
226
|
-
paragraphs:
|
|
227
|
+
paragraphs: splitParagraphs(ch.body),
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
230
|
|
|
@@ -296,6 +297,8 @@ function cmdExport(args) {
|
|
|
296
297
|
}
|
|
297
298
|
}
|
|
298
299
|
if (!EXPORT_FORMATS.includes(format)) fail(`未知导出格式「${format}」——支持 ${EXPORT_FORMATS.join(' / ')}`)
|
|
300
|
+
// 显式拒绝空串:`--out=` 经 path.resolve 会静默落成 cwd,属意外行为
|
|
301
|
+
if (outArg !== null && outArg.trim() === '') fail('选项 --out 不能是空串——--out 取导出目录路径')
|
|
299
302
|
|
|
300
303
|
const root = path.resolve(dirArg ?? 'novel')
|
|
301
304
|
const chapters = collectChapters(root)
|
|
@@ -316,6 +319,9 @@ function cmdExport(args) {
|
|
|
316
319
|
const stamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`
|
|
317
320
|
const outDir = path.resolve(outArg ?? path.join(root, 'exports'))
|
|
318
321
|
const outPath = path.join(outDir, `${sanitizeTitleForFile(rawTitle)}-${stamp}.${format}`)
|
|
322
|
+
// 同名输出不静默覆盖:目标已存在时自动加序号(书名-20250101-2.epub)并提示
|
|
323
|
+
const target = dedupeWritePath(outPath)
|
|
324
|
+
if (target.renamed) console.error(`提示:${outPath} 已存在,本次导出改写到 ${target.path}`)
|
|
319
325
|
|
|
320
326
|
let content
|
|
321
327
|
if (format === 'md') {
|
|
@@ -331,10 +337,15 @@ function cmdExport(args) {
|
|
|
331
337
|
})
|
|
332
338
|
}
|
|
333
339
|
|
|
334
|
-
|
|
335
|
-
|
|
340
|
+
// 落盘点兜底:目录建不出 / 文件写不进(权限、只读盘等)报中文错,不甩英文栈
|
|
341
|
+
try {
|
|
342
|
+
mkdirSync(outDir, { recursive: true })
|
|
343
|
+
writeFileSync(target.path, content)
|
|
344
|
+
} catch (error) {
|
|
345
|
+
fail(`写入失败:${target.path}(${error.message})`)
|
|
346
|
+
}
|
|
336
347
|
const totalWords = chapters.reduce((sum, ch) => sum + ch.words, 0)
|
|
337
|
-
console.log(`导出完成:${path.resolve(
|
|
348
|
+
console.log(`导出完成:${path.resolve(target.path)}`)
|
|
338
349
|
console.log(`格式 ${format};${chapters.length} 章,总字数 ${totalWords}(CJK 计数)。`)
|
|
339
350
|
}
|
|
340
351
|
|
|
@@ -373,6 +384,8 @@ function cmdBrief(args) {
|
|
|
373
384
|
if (dirArg === null && chapterArg === null) {
|
|
374
385
|
fail('用法:maoying brief [dir] <chapter> [--out <文件>]——chapter 可写文件名(v01-c002.md)、stem(v01-c002)、「卷-章」(1-2)或裸章号(2)')
|
|
375
386
|
}
|
|
387
|
+
// 显式拒绝空串:`--out=` 经 path.resolve 会静默落成 cwd,属意外行为
|
|
388
|
+
if (outArg !== null && outArg.trim() === '') fail('选项 --out 不能是空串——--out 取输出文件路径')
|
|
376
389
|
// 只给一个位置参数时把它当章号用(dir 走默认 ./novel),这是常用路径
|
|
377
390
|
const root = path.resolve(chapterArg === null ? 'novel' : dirArg)
|
|
378
391
|
const brief = buildBrief(root, chapterArg ?? dirArg)
|
|
@@ -380,9 +393,17 @@ function cmdBrief(args) {
|
|
|
380
393
|
for (const warning of brief.warnings) console.error(`提醒:${warning}`)
|
|
381
394
|
if (outArg !== null) {
|
|
382
395
|
const outPath = path.resolve(outArg)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
console.
|
|
396
|
+
// 同名输出不静默覆盖:目标已存在时自动加序号 -2、-3……改写到新路径
|
|
397
|
+
const target = dedupeWritePath(outPath)
|
|
398
|
+
if (target.renamed) console.error(`提示:${outPath} 已存在,本次写作卡改写到 ${target.path}`)
|
|
399
|
+
// 落盘点兜底:目录建不出 / 文件写不进(权限、只读盘等)报中文错,不甩英文栈
|
|
400
|
+
try {
|
|
401
|
+
mkdirSync(path.dirname(target.path), { recursive: true })
|
|
402
|
+
writeFileSync(target.path, brief.text, 'utf8')
|
|
403
|
+
} catch (error) {
|
|
404
|
+
fail(`写入失败:${target.path}(${error.message})`)
|
|
405
|
+
}
|
|
406
|
+
console.log(`写作卡已写入:${target.path}`)
|
|
386
407
|
console.log('拿去写,写完丢回给主编收稿。')
|
|
387
408
|
} else {
|
|
388
409
|
process.stdout.write(brief.text)
|
|
@@ -434,18 +455,42 @@ const HELP_TEXT = `毛颖(maoying)—— 网文创作项目 CLI
|
|
|
434
455
|
check 有 error 退出码 1,只有 warn 退出码 0。
|
|
435
456
|
所有命令零依赖,任意 cwd 可用;出错时退出码 1。`
|
|
436
457
|
|
|
458
|
+
/** 只接受一个可选目录参数的子命令(目录参数不以 `-` 开头;--help/-h 由 main 先拦)。 */
|
|
459
|
+
const SINGLE_DIR_COMMANDS = ['init', 'stats', 'index', 'check']
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* 单参命令的目录参数防线:至多一个位置参数,且不以 `-` 开头——以前
|
|
463
|
+
* `maoying init --help` 把 --help 当目录名,静默建出 ./--help/。--help/-h 由
|
|
464
|
+
* main 在派发前拦截显示帮助;其余 `-` 开头参数报未知选项,多余位置参数报错
|
|
465
|
+
* (对照 export 的同款防线)。
|
|
466
|
+
*/
|
|
467
|
+
function singleDirTarget(command, rest) {
|
|
468
|
+
let target = null
|
|
469
|
+
for (const arg of rest) {
|
|
470
|
+
if (arg.startsWith('-')) fail(`未知选项「${arg}」——${command} 只接受一个可选目录参数`)
|
|
471
|
+
if (target !== null) fail(`多余的位置参数「${arg}」——${command} 只接受一个目录参数`)
|
|
472
|
+
target = arg
|
|
473
|
+
}
|
|
474
|
+
return target
|
|
475
|
+
}
|
|
476
|
+
|
|
437
477
|
function main(argv) {
|
|
438
478
|
const [command, ...rest] = argv
|
|
439
479
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
440
480
|
console.log(HELP_TEXT)
|
|
441
481
|
return
|
|
442
482
|
}
|
|
443
|
-
|
|
444
|
-
if (command === '
|
|
445
|
-
|
|
483
|
+
// 单参子命令自身也认 --help/-h(防 init --help 被当目录名),显示帮助即止
|
|
484
|
+
if (SINGLE_DIR_COMMANDS.includes(command) && rest.some((a) => a === '--help' || a === '-h')) {
|
|
485
|
+
console.log(HELP_TEXT)
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
if (command === 'init') return cmdInit(singleDirTarget('init', rest))
|
|
489
|
+
if (command === 'stats') return cmdStats(singleDirTarget('stats', rest))
|
|
490
|
+
if (command === 'index') return cmdIndex(singleDirTarget('index', rest))
|
|
446
491
|
if (command === 'export') return cmdExport(rest)
|
|
447
492
|
if (command === 'brief') return cmdBrief(rest)
|
|
448
|
-
if (command === 'check') return cmdCheck(rest
|
|
493
|
+
if (command === 'check') return cmdCheck(singleDirTarget('check', rest))
|
|
449
494
|
if (command === undefined) {
|
|
450
495
|
console.error('maoying: 缺少子命令(init / stats / index / export / brief / check,--help 看用法)')
|
|
451
496
|
process.exit(1)
|
package/docs/creator-panel.md
CHANGED
|
@@ -37,16 +37,31 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
|
|
|
37
37
|
|
|
38
38
|
| 路由 | 方法 | 入参 | 出参 |
|
|
39
39
|
|---|---|---|---|
|
|
40
|
-
| `client.css` | GET | — | text/css(lib/base.css
|
|
40
|
+
| `client.css` | GET | — | text/css(lib/base.css 现读,no-cache) |
|
|
41
|
+
| `update-check` | GET | — | `{ ok, current, latest, updateAvailable, releaseUrl, checkedAt, error, source }`——新版检测(npm registry → npmmirror → GitHub 三级源,1h 缓存 + 7 天 stale-if-error,全败静默降级为 latest=null / error 非空) |
|
|
42
|
+
| `host-debug` | GET | — | `{ ok, hostVersion, trace, shape, registry, runtime }`——现场诊断(见下节) |
|
|
41
43
|
| `workspaces` | GET | — | `{ ok, workspaces: [{ path, name, hasNovel }] }`——workspaceRegistry.list() 逐根探测 `<root>/novel/project.yml` |
|
|
42
44
|
| `state` | GET | `path=<root>` | 全量快照(见 §3 契约) |
|
|
43
45
|
| `chapter` | GET | `path`, `file`(chapters/ 下文件名) | `{ ok, file, frontmatter, body, outline }`——正文全文 + 对应章纲(`{ file, words, body }`,outlines/ 无对应文件时 null),章节抽屉用 |
|
|
44
46
|
| `chapter-status` | POST | `{ path, file, status }` | `{ ok }`——frontmatter status 行原地替换;status 必须 ∈ pending/writing/review/accepted |
|
|
45
47
|
| `foreshadow-status` | POST | `{ path, id, status }` | `{ ok }`——foreshadows.yml 对应条目 status 行替换;∈ planted/progressing/resolved/abandoned |
|
|
46
48
|
| `phase` | POST | `{ path, phase }` | `{ ok }`——project.yml phase 行替换;七态枚举 |
|
|
49
|
+
| `project-save` | POST | `{ path, values }`(8 平键,title/author/genre/length/language/phase/mode/description) | `{ ok }`——mutateProject 逐键改写保行尾注释、缺键按模板补齐;phase/mode 越枚举与空书名 422 |
|
|
47
50
|
| `rebuild-index` | POST | `{ path }` | `{ ok, chapters }`——照 CLI index 同一逻辑重建 index.yml |
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
共 11 条。写操作全部「行级原地替换」:只在 frontmatter/目标 yml 的对应行上做字符串手术,写前读、写后校验(替换计数 ≠1 即 409 拒写并报错),不做整文件重排——把对 git diff 的扰动压到最小,也与 agent 的写法兼容。
|
|
53
|
+
|
|
54
|
+
### host-debug 端点(现场诊断)
|
|
55
|
+
|
|
56
|
+
用途:面板「有新版不显示 / 形态判定可疑」时现场定位宿主版本读到的是哪一份。响应:
|
|
57
|
+
|
|
58
|
+
- `hostVersion`:五级探针(argv[1] → 全 argv 扫描 → app.asar / desktop-runtime → Electron API → home 全局兜底)命中的版本;全落空为 null。
|
|
59
|
+
- `trace`:每级探针一条轨迹 `{ probe, hit, via?, note? }`,能看到每级看到了什么、为什么落空。
|
|
60
|
+
- `shape`:托管块形态判定(v1/v2,读不到宿主版本保守 v1)。
|
|
61
|
+
- `registry`:预设注册表内窥(逐 preset 的 broken 激活诊断,尽力而为)。
|
|
62
|
+
- `runtime`:argv / execPath / cwd / electron / node。
|
|
63
|
+
|
|
64
|
+
围栏与脱敏:与所有路由同一信任围栏(回环 + 可信 Host);出口统一 sanitizeDeep——trace.via、runtime.argv / execPath / cwd 等本机绝对路径一律换 `~`,诊断信息照给、路径不出门。
|
|
50
65
|
|
|
51
66
|
## 3. state 快照契约(前后端共同遵守,字段名冻结)
|
|
52
67
|
|
|
@@ -56,7 +71,7 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
|
|
|
56
71
|
"root": "<workspace root>",
|
|
57
72
|
"novelDir": "<root>/novel",
|
|
58
73
|
"found": true, // false 时其余字段缺省,面板显示「未初始化」引导
|
|
59
|
-
"project": { "title","author","genre","length","language","phase","description" },
|
|
74
|
+
"project": { "title","author","genre","length","language","phase","mode","description" }, // mode: ai-draft|co-write|author-draft(五期)
|
|
60
75
|
"chapters": [{ "file","volume","num","title","status","words","summary",
|
|
61
76
|
"cognitive_frame","emotional_anchor","world_texture",
|
|
62
77
|
"suspense","interpersonal_tension" }], // 按 volume,num 升序;words 为 CJK 计数
|
|
@@ -88,12 +103,12 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
|
|
|
88
103
|
- 会话中栏全页 overlay:`html[data-dsh-maoying-active]` 驱动,`dsh-panel-activate` 事件与 mnemon/taskboard/ssh 等全页面板互斥,点侧栏会话行自动关闭;
|
|
89
104
|
- 顶部工作区选择器(workspaces 路由取数,hasNovel 的排前;无 novel 工作区显示引导卡:一句 `maoying init` 提示 + 复制按钮)。
|
|
90
105
|
|
|
91
|
-
|
|
106
|
+
九个 tab(顺序即默认排布,第 9 个为后续迭代新增):
|
|
92
107
|
|
|
93
108
|
1. **概览**:project 卡(书名大字 + 类型/篇幅/语言 pills + 阶段徽章 + 简介);统计卡(总字数/章节数/状态分布条);最近更新(chapters 按 mtime 前 5);warnings 黄条。
|
|
94
|
-
2. **章节**:表格(卷-章/标题/状态徽章/字数/梗概),状态筛选 pills;表格上方「按单写作」区块——有章纲且 status=pending 的章按序列出(章纲节选 +
|
|
109
|
+
2. **章节**:表格(卷-章/标题/状态徽章/字数/梗概),状态筛选 pills;表格上方「按单写作」区块——有章纲且 status=pending 的章按序列出(章纲节选 + 状态徽章,共创模式的主工作清单);行点击开整页阅读视图(0.1.12 起替代原右侧抽屉——fixed 侧弹层会被 DSH 侧边栏遮挡):返回按钮 + GET chapter 正文(只读,等宽滚动)+ 章纲对照栏(左右分栏或折叠段)+ 复制文件路径按钮 + 五维字段卡 + 状态流转按钮(当前态 → 下一态一键,pending→writing→review→accepted)。
|
|
95
110
|
3. **角色**:卡墙(姓名/role 徽章四色/年龄性别/性格一句/动机一行),点击看全卡(含 arc/markers/background)。
|
|
96
|
-
4. **伏笔**:四列看板(埋设/推进/已回收/弃用),卡片含 priority 色点(high 红/normal 灰/low 淡)、`source: backfill` 的「回填」小徽章(账本回填来的条目,区别于写前规划)、埋设章→预期回收章、notes
|
|
111
|
+
4. **伏笔**:四列看板(埋设/推进/已回收/弃用),卡片含 priority 色点(high 红/normal 灰/low 淡)、`source: backfill` 的「回填」小徽章(账本回填来的条目,区别于写前规划)、埋设章→预期回收章、notes 全文直显(0.1.10 去掉两行截断);卡片按钮:推进到下一状态(planted→progressing→resolved;abandoned 只能从看板拖不动、按钮给「弃用」)。列头计数。
|
|
97
112
|
5. **关系图**:canvas 力导向图——节点=角色(radius 随出场无关、固定 18px,名字 label 下方),边=relations(intensity 1-5 → 线宽 1-4px,type 标在边中点 hover 显示 description);拖拽节点、空白处滚轮缩放、双击复位;intensity 缺省 3。角色多于 30 个时只画与 relations 相连的节点,其余进「未入图」列表。
|
|
98
113
|
6. **时间线**:垂直轴,importance 1-5 → 点大小/颜色深浅,year 左列右卡片。
|
|
99
114
|
7. **设定**:world 按 category 分组列表 + science 三色标签(known 绿/extrapolation 黄/hypothesis 紫,标签下 references 链接列表)。
|
|
@@ -101,8 +116,19 @@ webServer.register({ kind: 'prefix', path: '/api/maoying', handler: async (req,
|
|
|
101
116
|
|
|
102
117
|
状态徽章色(全局统一):pending 灰 / writing 蓝 / review 橙 / accepted 绿;phase 七态沿用七色 pill。文案全中文、禁 emoji 图标(用 ui-primitives 图标或纯 CSS)。
|
|
103
118
|
|
|
119
|
+
## 4.1 后续迭代记录(0.1.10 – 0.1.13)
|
|
120
|
+
|
|
121
|
+
- **MaskPortal**:抽屉/弹层统一 portal 到 `document.body`——面板容器挂在宿主会话中栏,中栏 transform/overflow 会把 `position: fixed` 劫持成相对自身并连同裁剪(桌面端「点了没反应」实测病灶);无 createPortal 的老宿主退回原地渲染。
|
|
122
|
+
- **顶栏版本区**:当前版本号常驻;`update-check` 路由(模仿 dsh-paoding:npm registry → npmmirror → GitHub 三级源,1h 缓存 + 7 天 stale-if-error,全败静默)发现新版时亮「有新版 ↗」徽章。
|
|
123
|
+
- **系统设置**:齿轮弹窗,字号六档(100/110/120/130/140/150%)——面板根与 portal 节点读同一 `--myt-zoom` 变量整体 zoom,localStorage 记忆。
|
|
124
|
+
- **界面语言**:系统设置可选中文/English——全部 UI 文案走 `I18N` 词典(zh/en 各 209 条,`tt`/`tf` 取词 + `{x}` 插值,zh 值与历史硬编码逐字一致保证中文界面零回归),切换立即生效整树重渲并 localStorage 记忆;语言自称(中文/日本語)与侧栏入口品牌名不翻译。
|
|
125
|
+
- **Markdown 渲染**:零依赖渲染器(先整体转义再变换),章纲对照与世界观/科学设定描述不再裸显 md 源码;正文手稿仍走 pre。
|
|
126
|
+
- **章节整页阅读**:替代右侧抽屉(侧边栏遮挡问题),返回按钮 + 章纲对照 + 五维 + 状态流转原位保留;阅读态隐藏 tab 条。
|
|
127
|
+
- **小说设置 tab**(第 9 个):project.yml 平键表单化编辑,`POST project-save` → `mutateProject` 逐键改写保行尾注释、缺键按模板补齐、phase/mode 越枚举与空书名 422。
|
|
128
|
+
- **字号六档改百分比**(0.1.13):档位从中文名(小/标准/大…)改为 100/110/120/130/140/150% 直读,旧 localStorage 档位值回落 100%。
|
|
129
|
+
|
|
104
130
|
## 5. 验收基线
|
|
105
131
|
|
|
106
132
|
- Node 侧:临时 workspace(/tmp)+ 假 workspaceRegistry 不行——路由从 ctx.workspaceRegistry 取;单测直接 import lib/panel-core.mjs 的函数(parseStateSnapshot / mutateChapterStatus / mutateForeshadowStatus / mutatePhase / rebuildIndex)跑断言;路由层用 node 起一个最小 ctx 假件(webServer.register 收集 handler,workspaceRegistry 返回临时 root)模拟请求验证围栏 403 / 状态流转 / 防穿越。
|
|
107
|
-
- 浏览器侧:`node --check` 不适用(browser bundle 用 var/require factory
|
|
133
|
+
- 浏览器侧:`node --check` 不适用(browser bundle 用 var/require factory 语法)——冒烟脚本 `npm run smoke`(scripts/smoke-client.mjs,零依赖)已落地:new Function 包裹整份源码做编译检查 → stub window/document 求值截获 `__ModuleLoader__.load` → factory 传 react/UI 原语 stub 求值,断言导出面 `{ inject: ["slots"], apply }` 与 slots 注入 / disposer 折返语义。CI(Node 18/20/22 矩阵)与 `npm test` 同跑;真浏览器验收仍由用户重启后做。
|
|
108
134
|
- 不写死任何真实路径;测试全在 /tmp;真实 ~/.dsh 不碰。
|