dsh-novel-write-engine 0.2.8 → 0.2.10

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.
@@ -95,6 +95,7 @@ export interface SummaryDto {
95
95
  };
96
96
  quality?: {
97
97
  averageScore: number;
98
+ last_ten_average?: number | null;
98
99
  scores: number[];
99
100
  trend: number | null;
100
101
  trend_basis?: 'last_five_vs_previous_five' | 'insufficient_samples' | 'insufficient_history';
@@ -137,7 +138,7 @@ export interface ChapterRowDto {
137
138
  num: number;
138
139
  title: string;
139
140
  wordCount: number;
140
- score: number;
141
+ score: number | null;
141
142
  stage: string;
142
143
  status: 'passed' | 'blocked' | 'draft';
143
144
  details?: string;
@@ -156,13 +157,6 @@ export interface ChaptersDto {
156
157
  panelId: string;
157
158
  rows: ChapterRowDto[];
158
159
  }
159
- export interface GatesDto {
160
- schemaVersion: number;
161
- panelId: string;
162
- stage: string;
163
- blocking: BlockingDto[];
164
- explanations: string[];
165
- }
166
160
  export interface ProjectOptionDto {
167
161
  root: string;
168
162
  title: string;
@@ -188,11 +182,20 @@ export interface PanelPayloadDto {
188
182
  selection: PanelSelectionDto;
189
183
  summary?: SummaryDto;
190
184
  chapters?: ChaptersDto;
191
- gates?: GatesDto;
192
185
  resume?: {
193
186
  text: string;
194
187
  };
195
188
  missing?: PanelMissingDto;
196
189
  error?: string;
197
190
  empty?: true;
191
+ /** Additive v7 section payload. */
192
+ payload_version?: 7;
193
+ section?: string;
194
+ items?: unknown;
195
+ total?: number;
196
+ returned?: number;
197
+ truncated?: boolean;
198
+ next_cursor?: string | null;
199
+ updated_at?: string;
200
+ etag?: string;
198
201
  }
@@ -1,10 +1,28 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import { buildView, type PanelView } from './viewmodel.js';
3
+ import type { PanelPayloadDto } from './dto.js';
4
+ export type PanelTab = 'overview' | 'chapters' | 'characters' | 'foreshadows' | 'worldbook' | 'quality' | 'health';
5
+ export interface PanelSectionState {
6
+ loading: boolean;
7
+ error: string | null;
8
+ payload: PanelPayloadDto | null;
9
+ }
10
+ export interface ChapterQuery {
11
+ search: string;
12
+ status: 'all' | 'archived' | 'draft';
13
+ scene: string;
14
+ sort: 'num_asc' | 'num_desc';
15
+ cursor: string;
16
+ }
17
+ export interface ChapterDetailState {
18
+ chapter: number;
19
+ section: PanelSectionState;
20
+ }
3
21
  /**
4
22
  * Pure view: given a view model, return the element tree. Keeping the hooks in
5
23
  * `NovelPanel` makes every visual state renderable without a browser.
6
24
  */
7
- export declare function panelElement(view: PanelView, loading: boolean, onRefresh: () => void, onSelectProject?: (root: string | null) => void): ReactElement;
25
+ export declare function panelElement(view: PanelView, loading: boolean, onRefresh: () => void, onSelectProject?: (root: string | null) => void, tab?: PanelTab, onTab?: (tab: PanelTab) => void, section?: PanelSectionState | null, chapterQuery?: ChapterQuery, onChapterQuery?: (patch: Partial<ChapterQuery>) => void, sectionSearch?: string, onSectionSearch?: (value: string) => void, chapterDetail?: ChapterDetailState | null, onChapterDetail?: (chapter: number | null) => void, qualityDetail?: ChapterDetailState | null, onQualityDetail?: (chapter: number | null) => void): ReactElement;
8
26
  /** Stateful wrapper: fetch once, refresh on demand and after returning to the tab. */
9
27
  export declare function NovelPanel(): ReactElement;
10
28
  /** Sidebar icon: a small book mark, inheriting the current text colour. */
@@ -14,8 +14,8 @@ export interface PanelChapter {
14
14
  words: string;
15
15
  wordCount: number;
16
16
  score: string;
17
- scoreValue: number;
18
- scorePercent: number;
17
+ scoreValue: number | null;
18
+ scorePercent: number | null;
19
19
  badge: string;
20
20
  tone: 'ok' | 'bad' | 'muted';
21
21
  details: string;
@@ -69,6 +69,8 @@ export interface PanelView {
69
69
  stageLabel: string;
70
70
  /** The whole book reached its configured target; stage `done` alone is only one chapter. */
71
71
  finished: boolean;
72
+ /** Active chapter number for lazy quality detail. */
73
+ chapter: number;
72
74
  revisionLabel: string;
73
75
  phases: PanelPhase[];
74
76
  rows: PanelChapter[];
@@ -119,7 +121,7 @@ export interface PanelView {
119
121
  };
120
122
  quality: {
121
123
  averageScore: number;
122
- lastTenAverage: number;
124
+ lastTenAverage: number | null;
123
125
  bookMedian: number;
124
126
  bookMin: number;
125
127
  bookMax: number;
@@ -127,6 +129,7 @@ export interface PanelView {
127
129
  id: string;
128
130
  average: number;
129
131
  samples: number;
132
+ max?: number;
130
133
  }>;
131
134
  scores: number[];
132
135
  trend: number | null;
@@ -171,6 +174,9 @@ export interface PanelView {
171
174
  nextDetail: string;
172
175
  nextTool: string;
173
176
  emptyCommand: string;
177
+ /** Last successful sync / last background failure; filled by NovelPanel. */
178
+ lastSync?: string | null;
179
+ syncError?: string | null;
174
180
  /** Compatibility fields used by older cards and tests. */
175
181
  revision: string;
176
182
  next: string;
@@ -183,7 +189,8 @@ export declare const PANEL_SCHEMA_VERSION = 6;
183
189
  */
184
190
  export declare function buildView(payload: PanelPayloadDto | null, error?: string | null): PanelView;
185
191
  /** Fetch the panel payload; failures become a plain message, never a throw. */
186
- export declare function fetchPanel(fetcher: typeof fetch, path: string, signal?: AbortSignal): Promise<{
192
+ export declare function fetchPanel(fetcher: typeof fetch, path: string, signal?: AbortSignal, etag?: string): Promise<{
187
193
  payload: PanelPayloadDto | null;
188
194
  error: string | null;
195
+ notModified?: boolean;
189
196
  }>;
@@ -5,6 +5,8 @@ export declare const PANEL_ID = "novel-workshop";
5
5
  export declare const PANEL_ROUTE = "/api/novel-write-engine/panel";
6
6
  export declare const SKILL_NAME = "novel-write-engine";
7
7
  export declare const REMOTE_VERSION = 6;
8
+ /** User-visible panel/tutorial version; keep in sync with package.json. */
9
+ export declare const PANEL_VERSION = "0.2.10";
8
10
  /** The panel is a recent-history view; the wire payload and UI use one cap. */
9
11
  export declare const PANEL_CHAPTER_LIMIT = 30;
10
12
  /** Project-relative locations. Every write goes through these constants. */
@@ -45,7 +45,7 @@ export interface PanelBookData {
45
45
  average_words: number;
46
46
  remaining_words: number | null;
47
47
  remaining_words_basis: 'actual_average' | 'minimum_target' | 'none';
48
- percent: number;
48
+ percent: number | null;
49
49
  };
50
50
  current: {
51
51
  card: PanelCardData | null;
@@ -93,7 +93,7 @@ export interface PanelBookData {
93
93
  median: number;
94
94
  min: number;
95
95
  max: number;
96
- last_ten_average: number;
96
+ last_ten_average: number | null;
97
97
  trend: number | null;
98
98
  trend_basis: BookReport['quality']['trend_basis'];
99
99
  samples: number;
@@ -217,6 +217,18 @@ export interface WordMetrics {
217
217
  paragraph_count: number;
218
218
  paragraph_max_words: number;
219
219
  }
220
+ /** Protected evidence copied from the archived outline card at archive time. */
221
+ export interface ArchivedCardSnapshot {
222
+ title: string;
223
+ scene_type: SceneType;
224
+ target_words: [number, number];
225
+ cast: string[];
226
+ goal: string;
227
+ conflict: string;
228
+ hook: string;
229
+ notes: string;
230
+ archived_at: string | null;
231
+ }
220
232
  /** Derived chapter row kept in `state/chapters.yaml`. */
221
233
  export interface ChapterRecord {
222
234
  num: number;
@@ -239,6 +251,10 @@ export interface ChapterRecord {
239
251
  assigned_ids?: Record<string, string>;
240
252
  /** Digest of the archive request that produced this row. */
241
253
  archive_digest?: string;
254
+ /** Immutable card fields written by the archive transaction, not by amend. */
255
+ archive_card?: ArchivedCardSnapshot;
256
+ /** Canonical digest of archive_card; v1-prefixed like archive_digest. */
257
+ archive_card_digest?: string;
242
258
  }
243
259
  /** Summary block the model supplies with a draft and the engine persists. */
244
260
  export interface ChapterSummary {
@@ -760,7 +776,7 @@ export interface BookReport {
760
776
  target: number | null;
761
777
  words: number;
762
778
  average_words: number;
763
- percent: number;
779
+ percent: number | null;
764
780
  remaining_words: number | null;
765
781
  remaining_words_min: number | null;
766
782
  remaining_words_average: number | null;
@@ -825,6 +841,7 @@ export interface BookReport {
825
841
  id: string;
826
842
  average: number;
827
843
  samples: number;
844
+ max: number;
828
845
  }>;
829
846
  };
830
847
  /** Per-chapter effort survives archive (pipeline rounds are reset). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-novel-write-engine",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Novel writing engine for DeepSeek Harness: state machine, gates, rulepacks and ledgers moved from prompts into code.",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -63,7 +63,8 @@
63
63
  ],
64
64
  "external": [
65
65
  "react",
66
- "react/jsx-runtime"
66
+ "react/jsx-runtime",
67
+ "react-dom"
67
68
  ]
68
69
  }
69
70
  },
@@ -98,7 +99,8 @@
98
99
  "@deepseek-ai/dsh-skill": "^0.2.0-rc.1 || ^0.2.1-rc.1",
99
100
  "@deepseek-ai/schemastery": "^3.18.5",
100
101
  "@deepseek-ai/dsh-tools": "^0.2.0-rc.1 || ^0.2.1-rc.1",
101
- "react": "^18.0.0 || ^19.0.0"
102
+ "react": "^18.0.0 || ^19.0.0",
103
+ "react-dom": "^18.0.0 || ^19.0.0"
102
104
  },
103
105
  "peerDependenciesMeta": {
104
106
  "@deepseek-ai/dsh-client-ui-layout": {
@@ -112,6 +114,9 @@
112
114
  },
113
115
  "react": {
114
116
  "optional": true
117
+ },
118
+ "react-dom": {
119
+ "optional": true
115
120
  }
116
121
  },
117
122
  "devDependencies": {
@@ -17,6 +17,10 @@
17
17
  - 正文只经 novel_write_draft,改稿只经 novel_revise;归档只走 novel_archive,且必须带 state_delta。没有别的写入口。
18
18
  - 字数、引号、格式、门控结论一律以引擎返回为准,不要自己估算,也不要复述旧数字。
19
19
  - 评审每一分都要指到具体扣分项;指不出来就不要扣。
20
+ - 参数是严格 JSON:只用英文双引号,不写注释、尾逗号和 Markdown 围栏;不要把对象再包成 JSON 字符串,不要重复编码。
21
+ - 写工具一次只提交一个:novel_write_draft、novel_review、novel_check、novel_archive 读完结果再走下一步。
22
+ - 参数解析失败时不要原样重发大对象;先 novel_brief 重取当前阶段字段,缩小对象并逐字段核对。
23
+ - 归档只带 state_delta,不带 summary;归档后下一章从 novel_brief P1 重新开始。
20
24
  - 设定、前章细节、伏笔状态拿不准就查 novel_state,不许编。
21
25
  - { id: time-context, name: '@deepseek-ai/dsh-time-context' }
22
26
  - { id: tool-skill, name: '@deepseek-ai/dsh-tool-skill' }
@@ -1,6 +1,6 @@
1
1
  schema_version: 1
2
2
  name: default
3
- version: "1.0.1"
3
+ version: "1.1.0"
4
4
 
5
5
  lexicon:
6
6
  zero_tolerance:
@@ -182,10 +182,16 @@ platform:
182
182
  jinjiang: { words_per_chapter: [2500, 4000], dialogue_ratio: [0.35, 0.55] }
183
183
 
184
184
  genres:
185
- 玄幻修仙: { note: "力量体系必须自洽;升级要有代价,境界不能靠形容词抬升。" }
186
- 都市现代: { note: "现实都市,冲突来自资源、关系与规则;爽点要落在可验证的行动上。" }
185
+ 玄幻修仙:
186
+ note: "力量体系必须自洽;升级要有代价,境界不能靠形容词抬升。"
187
+ profile: { water_words: 0.30, pleasure_density: 2 }
188
+ 都市现代:
189
+ note: "现实都市,冲突来自资源、关系与规则;爽点要落在可验证的行动上。"
190
+ profile: { water_words: 0.32, pleasure_density: 1 }
187
191
  西方奇幻: { note: "种族、语言与力量来源要建条目;奇观不能替代人物选择。" }
188
- 悬疑推理: { note: "线索先于解答出现;每个伏笔都要在归档台账中可核销。" }
192
+ 悬疑推理:
193
+ note: "线索先于解答出现;每个伏笔都要在归档台账中可核销。"
194
+ profile: { period_density: { min: 1.5, max: 12 }, water_words: 0.30 }
189
195
  科幻: { note: "技术设定只写对情节有用的部分;禁止用术语代替冲突。" }
190
196
  游戏系统: { note: "系统提示是世界观内文本;奖励与惩罚必须可追踪。" }
191
197
  历史架空: { note: "礼制、称谓与器物要经得起基本推敲;不写现代口号。" }
@@ -193,7 +199,9 @@ genres:
193
199
  轻小说: { note: "关系推进靠具体互动,不靠内心独白宣布;对话要能区分说话人。" }
194
200
  军事: { note: "战术细节服务于人物选择;不写武器参数清单。" }
195
201
  末世流: { note: "生存资源必须记账;恐惧来自规则崩塌,不靠突然的音效式形容词。" }
196
- 言情: { note: "亲密关系按阶梯推进;单次独白要短,情绪落在动作上。" }
202
+ 言情:
203
+ note: "亲密关系按阶梯推进;单次独白要短,情绪落在动作上。"
204
+ profile: { period_density: { min: 1.5, max: 12 }, pleasure_density: 2 }
197
205
 
198
206
  crosschapter:
199
207
  duplicate_similarity: 0.9
package/skills/entry.md CHANGED
@@ -77,7 +77,8 @@ description: 引擎版小说创作流程——状态、门控、字数与写盘
77
77
 
78
78
  ## 工具怎么用
79
79
 
80
- - 参数是 JSON 对象;`summary`、`dimensions`、`issues`、`proof` 是结构化对象,按任务书字段填。
80
+ - 参数是严格 JSON 对象:英文双引号、无注释/尾逗号/围栏;`summary`、`dimensions`、`issues`、`proof` 是结构化对象/数组,按任务书字段填,不要再包成 JSON 字符串。
81
+ - 写工具一次只提交一个;参数解析失败时先重取 `novel_brief`,缩小对象再试,不要原样重发。
81
82
  - `novel_check` 必须带五个语义项(`L2-SCENE-ANCHOR` / `L2-CHARACTER-STATE` / `L2-CAUSALITY` / `L2-VOICE` / `L2-POV`),每项都要有正文逐字引用,且来自不同句子;缺一项即校验不过。
82
83
  - 失败分三类:参数错(改参数)、门控没过(改正文)、系统故障(查目录/稍后重试)。错误消息里的 code 说明属于哪类。
83
84
  - 门控失败会一次列出**全部**阻塞项;一次改完再交,不要逐条试。