dsh-novel-write-engine 0.2.1 → 0.2.3
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/CHANGELOG.md +60 -1
- package/README.md +124 -57
- package/briefs/P5.md +22 -1
- package/briefs/P8.md +67 -1
- package/lib/client.js +591 -55
- package/lib/index.d.ts +393 -37
- package/lib/index.js +4039 -1997
- package/lib/types/client/dto.d.ts +45 -4
- package/lib/types/client/panel.d.ts +1 -1
- package/lib/types/client/viewmodel.d.ts +50 -3
- package/lib/types/constants.d.ts +4 -1
- package/lib/types/types.d.ts +37 -2
- package/package.json +1 -1
- package/rulepacks/default.yaml +4 -2
- package/skills/entry.md +7 -0
|
@@ -13,6 +13,8 @@ export interface BlockingDto {
|
|
|
13
13
|
export interface SummaryDto {
|
|
14
14
|
schemaVersion: number;
|
|
15
15
|
panelId: string;
|
|
16
|
+
/** Absolute project root, so a stale title cannot hide a wrong book. */
|
|
17
|
+
root: string;
|
|
16
18
|
title: string;
|
|
17
19
|
genre: string;
|
|
18
20
|
platform: string;
|
|
@@ -73,7 +75,10 @@ export interface SummaryDto {
|
|
|
73
75
|
quality?: {
|
|
74
76
|
averageScore: number;
|
|
75
77
|
scores: number[];
|
|
76
|
-
trend: number;
|
|
78
|
+
trend: number | null;
|
|
79
|
+
trend_basis?: 'last_five_vs_previous_five' | 'insufficient_samples' | 'insufficient_history';
|
|
80
|
+
samples?: number;
|
|
81
|
+
attempts?: number;
|
|
77
82
|
};
|
|
78
83
|
health?: {
|
|
79
84
|
ok: boolean;
|
|
@@ -86,6 +91,17 @@ export interface SummaryDto {
|
|
|
86
91
|
chapter: number;
|
|
87
92
|
status: string;
|
|
88
93
|
}>;
|
|
94
|
+
advisories?: Array<{
|
|
95
|
+
code: string;
|
|
96
|
+
detail: string;
|
|
97
|
+
hint?: string;
|
|
98
|
+
}>;
|
|
99
|
+
/** Advisory codes the author has already acknowledged in the current check. */
|
|
100
|
+
acknowledgedAdvisories?: number;
|
|
101
|
+
legacy?: Array<{
|
|
102
|
+
code: string;
|
|
103
|
+
detail: string;
|
|
104
|
+
}>;
|
|
89
105
|
}
|
|
90
106
|
export interface ChapterRowDto {
|
|
91
107
|
num: number;
|
|
@@ -108,11 +124,36 @@ export interface GatesDto {
|
|
|
108
124
|
blocking: BlockingDto[];
|
|
109
125
|
explanations: string[];
|
|
110
126
|
}
|
|
127
|
+
export interface ProjectOptionDto {
|
|
128
|
+
root: string;
|
|
129
|
+
title: string;
|
|
130
|
+
active: boolean;
|
|
131
|
+
registered: boolean;
|
|
132
|
+
state: 'ok' | 'damaged' | 'missing' | 'not-project' | 'unreadable';
|
|
133
|
+
identity_changed?: boolean;
|
|
134
|
+
}
|
|
135
|
+
export interface PanelSelectionDto {
|
|
136
|
+
root: string | null;
|
|
137
|
+
source: 'query' | 'active' | 'single' | 'config' | 'none';
|
|
138
|
+
}
|
|
139
|
+
export interface PanelMissingDto {
|
|
140
|
+
root: string;
|
|
141
|
+
title: string;
|
|
142
|
+
code: 'ENGINE_PROJECT_MISSING' | 'ENGINE_PROJECT_NOT_FOUND' | 'ENGINE_TXN_IN_PROGRESS';
|
|
143
|
+
reason: 'missing' | 'not-project' | 'damaged' | 'not_registered' | 'unreadable' | 'busy';
|
|
144
|
+
hint: string;
|
|
145
|
+
}
|
|
111
146
|
export interface PanelPayloadDto {
|
|
112
147
|
schemaVersion: number;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
projects: ProjectOptionDto[];
|
|
149
|
+
selection: PanelSelectionDto;
|
|
150
|
+
summary?: SummaryDto;
|
|
151
|
+
chapters?: ChaptersDto;
|
|
152
|
+
gates?: GatesDto;
|
|
153
|
+
resume?: {
|
|
154
|
+
text: string;
|
|
155
|
+
};
|
|
156
|
+
missing?: PanelMissingDto;
|
|
116
157
|
error?: string;
|
|
117
158
|
empty?: true;
|
|
118
159
|
}
|
|
@@ -4,7 +4,7 @@ import { buildView, type PanelView } from './viewmodel.js';
|
|
|
4
4
|
* Pure view: given a view model, return the element tree. Keeping the hooks in
|
|
5
5
|
* `NovelPanel` makes every visual state renderable without a browser.
|
|
6
6
|
*/
|
|
7
|
-
export declare function panelElement(view: PanelView, loading: boolean, onRefresh: () => void): ReactElement;
|
|
7
|
+
export declare function panelElement(view: PanelView, loading: boolean, onRefresh: () => void, onSelectProject?: (root: string | null) => void): ReactElement;
|
|
8
8
|
/** Stateful wrapper: fetch once, refresh on demand and after returning to the tab. */
|
|
9
9
|
export declare function NovelPanel(): ReactElement;
|
|
10
10
|
/** Sidebar icon: a small book mark, inheriting the current text colour. */
|
|
@@ -20,11 +20,42 @@ export interface PanelChapter {
|
|
|
20
20
|
details: string;
|
|
21
21
|
stageLabel: string;
|
|
22
22
|
}
|
|
23
|
+
export interface PanelProject {
|
|
24
|
+
root: string;
|
|
25
|
+
title: string;
|
|
26
|
+
label: string;
|
|
27
|
+
active: boolean;
|
|
28
|
+
registered: boolean;
|
|
29
|
+
state: 'ok' | 'damaged' | 'missing' | 'not-project' | 'unreadable';
|
|
30
|
+
identity_changed?: boolean;
|
|
31
|
+
group: 'writing' | 'damaged' | 'missing';
|
|
32
|
+
selected: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface PanelMissing {
|
|
35
|
+
root: string;
|
|
36
|
+
title: string;
|
|
37
|
+
code: string;
|
|
38
|
+
reason: string;
|
|
39
|
+
hint: string;
|
|
40
|
+
}
|
|
23
41
|
/** Everything the panel renders, derived from the wire payload alone. */
|
|
24
42
|
export interface PanelView {
|
|
25
|
-
state: 'ready' | 'loading' | 'empty' | 'error' | 'outdated';
|
|
43
|
+
state: 'ready' | 'loading' | 'empty' | 'error' | 'outdated' | 'choose' | 'missing';
|
|
26
44
|
title: string;
|
|
27
45
|
subtitle: string;
|
|
46
|
+
/** Absolute root of the selected project, when there is one. */
|
|
47
|
+
root: string;
|
|
48
|
+
projects: PanelProject[];
|
|
49
|
+
selectedRoot: string | null;
|
|
50
|
+
selection: {
|
|
51
|
+
root: string | null;
|
|
52
|
+
source: string;
|
|
53
|
+
};
|
|
54
|
+
missing?: PanelMissing;
|
|
55
|
+
resumeText: string;
|
|
56
|
+
/** True when the selected project exists but its definition is damaged. */
|
|
57
|
+
damaged: boolean;
|
|
58
|
+
volumeNumber?: number;
|
|
28
59
|
stageLabel: string;
|
|
29
60
|
/** The whole book reached its configured target; stage `done` alone is only one chapter. */
|
|
30
61
|
finished: boolean;
|
|
@@ -76,7 +107,10 @@ export interface PanelView {
|
|
|
76
107
|
quality: {
|
|
77
108
|
averageScore: number;
|
|
78
109
|
scores: number[];
|
|
79
|
-
trend: number;
|
|
110
|
+
trend: number | null;
|
|
111
|
+
trendText: string;
|
|
112
|
+
samples: number;
|
|
113
|
+
attempts: number;
|
|
80
114
|
};
|
|
81
115
|
health: {
|
|
82
116
|
ok: boolean;
|
|
@@ -89,6 +123,19 @@ export interface PanelView {
|
|
|
89
123
|
chapter: number;
|
|
90
124
|
status: string;
|
|
91
125
|
}>;
|
|
126
|
+
/** Machine advisories, shown separately from blocking gates. */
|
|
127
|
+
advisories: Array<{
|
|
128
|
+
code: string;
|
|
129
|
+
detail: string;
|
|
130
|
+
hint: string;
|
|
131
|
+
}>;
|
|
132
|
+
/** How many advisory codes the author already acknowledged. */
|
|
133
|
+
acknowledgedAdvisories: number;
|
|
134
|
+
/** Read-only upgrade findings, shown separately from live errors. */
|
|
135
|
+
legacy: Array<{
|
|
136
|
+
code: string;
|
|
137
|
+
detail: string;
|
|
138
|
+
}>;
|
|
92
139
|
nextTitle: string;
|
|
93
140
|
nextDetail: string;
|
|
94
141
|
nextTool: string;
|
|
@@ -97,7 +144,7 @@ export interface PanelView {
|
|
|
97
144
|
revision: string;
|
|
98
145
|
next: string;
|
|
99
146
|
}
|
|
100
|
-
export declare const PANEL_SCHEMA_VERSION =
|
|
147
|
+
export declare const PANEL_SCHEMA_VERSION = 5;
|
|
101
148
|
/**
|
|
102
149
|
* Project the wire payload into strings. Version mismatch wins over everything
|
|
103
150
|
* else: a stale panel must never render data from another schema as if it were
|
package/lib/types/constants.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const PLUGIN_NAME = "dsh-novel-write-engine";
|
|
|
4
4
|
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
|
-
export declare const REMOTE_VERSION =
|
|
7
|
+
export declare const REMOTE_VERSION = 5;
|
|
8
8
|
/** The panel is a recent-history view; the wire payload and UI use one cap. */
|
|
9
9
|
export declare const PANEL_CHAPTER_LIMIT = 30;
|
|
10
10
|
/** Project-relative locations. Every write goes through these constants. */
|
|
@@ -25,6 +25,9 @@ export declare const OUTLINE_DIR = "outline";
|
|
|
25
25
|
export declare const LOCK_FILE = ".engine.lock";
|
|
26
26
|
export declare const STAGING_DIR = ".staging";
|
|
27
27
|
export declare const REGISTRY_FILE = "projects.yaml";
|
|
28
|
+
export declare const SESSION_BINDINGS_FILE = "session-bindings.yaml";
|
|
29
|
+
export declare const METADATA_LOCK_FILE = ".metadata.lock";
|
|
30
|
+
export declare const ARCHIVE_DIGESTS_FILE = "state/archive-digests.json";
|
|
28
31
|
/** Files owned by the engine under `state/`. */
|
|
29
32
|
export declare const STATE_FILES: {
|
|
30
33
|
readonly pipeline: "pipeline.yaml";
|
package/lib/types/types.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare const SCENE_TYPES: readonly ["战斗/动作章", "日常过渡章
|
|
|
20
20
|
export type SceneType = (typeof SCENE_TYPES)[number];
|
|
21
21
|
/** A violation is blocking unless the rule pack declares it advisory. */
|
|
22
22
|
export type Severity = 'blocker' | 'advisory';
|
|
23
|
-
/** All thirty-
|
|
23
|
+
/** All thirty-two service operations (22 inherited + 9 book-level + restoreBackup). */
|
|
24
24
|
export declare const OPERATIONS: readonly ["initProject", "archiveChapter", "amend", "rollback", "reopenChapter", "submitWorldPlan", "submitChapterPlan", "submitDraft", "submitReview", "submitRevision", "runCheck", "submitBookPlan", "submitOutline", "importOutline", "submitWorldbook", "getStatus", "getBrief", "countWords", "queryState", "diff", "auditHistory", "advise", "search", "doctor", "report", "guide", "export", "panelSummary", "panelChapters", "panelGates", "stats", "restoreBackup"];
|
|
25
25
|
export type Operation = (typeof OPERATIONS)[number];
|
|
26
26
|
/** Structured, non-fatal note attached to a successful result. */
|
|
@@ -58,11 +58,18 @@ export interface ReadingProof {
|
|
|
58
58
|
/** Error families map one-to-one to the three repair actions a caller can take. */
|
|
59
59
|
export declare const ERROR_CODES: {
|
|
60
60
|
readonly PROJECT_NOT_FOUND: "ENGINE_PROJECT_NOT_FOUND";
|
|
61
|
+
readonly PROJECT_AMBIGUOUS: "ENGINE_PROJECT_AMBIGUOUS";
|
|
62
|
+
readonly PROJECT_MISSING: "ENGINE_PROJECT_MISSING";
|
|
63
|
+
readonly PROJECT_IDENTITY_MISMATCH: "ENGINE_PROJECT_IDENTITY_MISMATCH";
|
|
61
64
|
readonly PROJECT_EXISTS: "ENGINE_PROJECT_EXISTS";
|
|
62
65
|
readonly STAGE_ORDER: "ENGINE_STAGE_ORDER";
|
|
66
|
+
/** The exact same keyed submission already failed in the same context. */
|
|
67
|
+
readonly RETRY_UNCHANGED: "ENGINE_RETRY_UNCHANGED";
|
|
63
68
|
readonly GATE_BLOCKED: "ENGINE_GATE_BLOCKED";
|
|
64
69
|
readonly READING_PROOF_INVALID: "ENGINE_READING_PROOF_INVALID";
|
|
65
70
|
readonly SCORE_MISMATCH: "ENGINE_SCORE_MISMATCH";
|
|
71
|
+
/** A revision tried to rewrite reviewer-owned issue fields. */
|
|
72
|
+
readonly ISSUE_IMMUTABLE: "ENGINE_ISSUE_IMMUTABLE";
|
|
66
73
|
readonly DELTA_UNVERIFIED: "ENGINE_DELTA_UNVERIFIED";
|
|
67
74
|
readonly RESOURCE_NEGATIVE_BALANCE: "ENGINE_RESOURCE_NEGATIVE_BALANCE";
|
|
68
75
|
readonly SCHEMA_TOO_NEW: "ENGINE_SCHEMA_TOO_NEW";
|
|
@@ -461,6 +468,8 @@ export interface ForeshadowsDocument {
|
|
|
461
468
|
resolved_at: number | null;
|
|
462
469
|
description: string;
|
|
463
470
|
anchor?: string;
|
|
471
|
+
/** New 0.2.3 writes are distinguishable from pre-0.2.3 rows by this marker. */
|
|
472
|
+
format_version?: 1;
|
|
464
473
|
advance_log: Array<{
|
|
465
474
|
chapter: number;
|
|
466
475
|
note: string;
|
|
@@ -783,7 +792,13 @@ export interface BookReport {
|
|
|
783
792
|
min: number;
|
|
784
793
|
max: number;
|
|
785
794
|
last_five: number[];
|
|
786
|
-
|
|
795
|
+
/** Null when there are not enough distinct chapter reviews to compare. */
|
|
796
|
+
trend: number | null;
|
|
797
|
+
trend_basis: 'last_five_vs_previous_five' | 'insufficient_samples' | 'insufficient_history';
|
|
798
|
+
/** Number of chapters represented by a final review. */
|
|
799
|
+
samples: number;
|
|
800
|
+
/** Committed review rounds for this report, including history. */
|
|
801
|
+
attempts: number;
|
|
787
802
|
dimensions: Array<{
|
|
788
803
|
id: string;
|
|
789
804
|
average: number;
|
|
@@ -984,6 +999,15 @@ export interface ChapterStatusRow {
|
|
|
984
999
|
stage: Stage;
|
|
985
1000
|
passed: boolean;
|
|
986
1001
|
}
|
|
1002
|
+
/** Where an omitted project root came from. */
|
|
1003
|
+
export type SelectionSource = 'explicit' | 'session' | 'workspace' | 'active' | 'config' | 'single';
|
|
1004
|
+
/** One resolved project choice, before the project is loaded. */
|
|
1005
|
+
export interface Selection {
|
|
1006
|
+
root: string;
|
|
1007
|
+
source: SelectionSource;
|
|
1008
|
+
title?: string;
|
|
1009
|
+
created_at?: string;
|
|
1010
|
+
}
|
|
987
1011
|
/** The `getStatus` payload. */
|
|
988
1012
|
export interface StatusReport {
|
|
989
1013
|
project: {
|
|
@@ -992,10 +1016,17 @@ export interface StatusReport {
|
|
|
992
1016
|
platform: Platform;
|
|
993
1017
|
root: string;
|
|
994
1018
|
};
|
|
1019
|
+
/** Why this project was chosen; lets the model notice a wrong book. */
|
|
1020
|
+
selection?: {
|
|
1021
|
+
source: SelectionSource;
|
|
1022
|
+
root: string;
|
|
1023
|
+
};
|
|
995
1024
|
/** The chapter requested for metrics; may be historical. */
|
|
996
1025
|
chapter: number;
|
|
997
1026
|
/** The chapter the pipeline is currently working on. */
|
|
998
1027
|
active_chapter: number;
|
|
1028
|
+
/** SHA-256 of the active manuscript, or null when the stage has no draft. */
|
|
1029
|
+
manuscript_hash: string | null;
|
|
999
1030
|
stage: Stage;
|
|
1000
1031
|
rounds: RoundCounters;
|
|
1001
1032
|
blocking: BlockingItem[];
|
|
@@ -1072,6 +1103,10 @@ export interface AuditRow {
|
|
|
1072
1103
|
/** True when no transaction started: the gate/input refused the call. */
|
|
1073
1104
|
attempt?: boolean;
|
|
1074
1105
|
blocking?: BlockingItem[];
|
|
1106
|
+
/** Keyed operation fingerprint; lets an identical failed retry be refused. */
|
|
1107
|
+
retry_key?: string;
|
|
1108
|
+
/** Brief/stage/revision context for `retry_key`. */
|
|
1109
|
+
retry_context?: string;
|
|
1075
1110
|
}
|
|
1076
1111
|
/** One diff hunk between two revisions of a chapter. */
|
|
1077
1112
|
export interface DiffReport {
|
package/package.json
CHANGED
package/rulepacks/default.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
schema_version: 1
|
|
2
2
|
name: default
|
|
3
|
-
version: "1.0.
|
|
3
|
+
version: "1.0.1"
|
|
4
4
|
|
|
5
5
|
lexicon:
|
|
6
6
|
zero_tolerance:
|
|
@@ -145,7 +145,9 @@ punctuation:
|
|
|
145
145
|
base: { 战斗/动作章: 3, 日常过渡章: 4, 情感爆发章: 6, 纯叙事章: 3 }
|
|
146
146
|
scale_words: 3000
|
|
147
147
|
token: "——"
|
|
148
|
-
|
|
148
|
+
# Chinese web fiction often uses short sentences; 8/100 flagged ordinary
|
|
149
|
+
# narrative (e.g. 9.52/100). Keep a ceiling, but calibrate it for Chinese.
|
|
150
|
+
period_density: { min: 1.5, max: 10 }
|
|
149
151
|
ellipsis_run: { max: 1, token: "……" }
|
|
150
152
|
uniform_sentence: { min_sentences: 8, ratio: 0.6 }
|
|
151
153
|
consecutive_le: { max: 3, char: "了" }
|
package/skills/entry.md
CHANGED
|
@@ -16,6 +16,13 @@ description: 引擎版小说创作流程——状态、门控、字数与写盘
|
|
|
16
16
|
5. **不编设定。** 人物、地点、伏笔、资源状态拿不准就 `novel_state` 查。
|
|
17
17
|
6. **归档是一次事务,且必须核销。** `novel_archive` 必须带 `state_delta`:本章每个角色/伏笔/事件/新设定/关系逐条声明 `created|updated|no_change`;不要手工拼章节索引或交接文件。
|
|
18
18
|
|
|
19
|
+
## 项目选择(0.2.2)
|
|
20
|
+
|
|
21
|
+
- 第一次调用先 `novel_status`,看 `selection.root` 与 `selection.source`;不要凭记忆猜项目。
|
|
22
|
+
- 没有绑定且工作区/注册表有多本书时,引擎返回 `ENGINE_PROJECT_AMBIGUOUS` 并列出候选;这时显式传一次绝对 `project_root`,成功后它会成为当前会话的持久绑定,后续省略即沿用。不要用全局 `active` 猜。
|
|
23
|
+
- 用户要切换时,用 `/novel use <路径|书名|.>`;`/novel current` 查绑定,`/novel unbind` 只清会话绑定,`/novel scan` 只发现/登记、不选择。
|
|
24
|
+
- `novel_word_count` 的纯文本模式与 `novel_diff` 的 `before`+`after` 文本模式不读项目、不绑定;项目模式才传 `project_root`。
|
|
25
|
+
|
|
19
26
|
## 书级规划(0.2.0)
|
|
20
27
|
|
|
21
28
|
新项目先 `novel_guide`;推荐但不强制:`novel_blueprint`(蓝图/卷)→ `novel_outline`(章卡)→ `novel_worldbook`(设定集)。已有大纲可用 `novel_outline_import` 导入 Markdown/YAML/JSON。只要存在章卡,P2/P3 就按章卡校验标题、场景、字数与 cast;严格模式还要求章卡必须存在。要改已归档章先 `novel_reopen`(必须带 reason)。
|