dsh-novel-write-engine 0.1.1 → 0.1.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/briefs/P3.md CHANGED
@@ -10,6 +10,10 @@
10
10
  - 不写 Markdown:禁 `#`、`**`、`---`、`>`、表格。
11
11
  - 不写「Day 1」「第X卷」「读者」「作者」这类出戏标签。
12
12
 
13
+ ## 上下文
14
+
15
+ P3 brief 的 `context.world_plan` 是 P1 的本章约束;`context.characters` 是本章活跃角色的完整档案,`context.other_characters` 只是压缩索引。先读 `world_plan` 与 `characters` 再落笔;不要在正文里临时发明未申报的世界规则或人物。
16
+
13
17
  ## 写法
14
18
 
15
19
  1. 一段一个信息点。先动作,后反应,最后才允许一句心理。
package/briefs/P4.md CHANGED
@@ -70,5 +70,8 @@
70
70
 
71
71
  - `blocker` 必须修;`advisory` 可改可不改。
72
72
  - 每条 issue 写清 `problem` 与 `fix`,fix 要是可执行动作。
73
+ - `dimension` 必须是本任务书十五维之一(`R-W-01`…`R-W-15` 的实际集合);`location` 必须是「第 N 段」,且 N 在本章实际段落范围内。
74
+ - `blocker` 不能在 P4 或 P5 标记为 `waived`;只能修好后带正文证据标记 `fixed`。
75
+ - 判断不了是否「作者故意」时,先按普通问题处理;0.1.3 不提供自动豁免。
73
76
 
74
77
  完成后:仍有 blocker → P5 修订;全部清零 → P6 校验。
package/briefs/P5.md CHANGED
@@ -5,9 +5,10 @@
5
5
  ## 规则
6
6
 
7
7
  1. 每条 `blocker` issue 必须变成 `fixed`,并给 `evidence`——修订后正文里的**逐字引用**,引擎会回查。
8
- 2. `blocker` 不许 `waived`;只有 advisory 可以带理由豁免。
9
- 3. 改完字数仍要 ≥ {{min_words}},格式要求同 P3。
10
- 4. 改动会抬高或压低评分时,不要自己改分;下一轮 P4 重新评。
8
+ 2. `issues` 里只能改 `status` 与 `evidence`;`dimension`、`location`、`problem`、`fix`、`severity` 是评审归属,改写会被拒绝。新发现留到下一轮 P4。
9
+ 3. `blocker` 不许 `waived`;只有 advisory 可以带理由豁免。
10
+ 4. 改完字数仍要 ≥ {{min_words}},格式要求同 P3。
11
+ 5. 改动会抬高或压低评分时,不要自己改分;下一轮 P4 重新评。
11
12
 
12
13
  ## 提交
13
14
 
package/briefs/P6.md CHANGED
@@ -33,8 +33,14 @@
33
33
 
34
34
  ## 判定
35
35
 
36
+ - 调用时可以传 `scope`:默认 `["content","integrity"]`;只跑 `content` 或只跑 `integrity` 是合法的,空数组/未知值会被拒绝。只跑一类时另一类规则不参与本次 check,但**不会**进入 `ready_to_archive`:管线停在 P6 并返回 `CHECK_SCOPE_PARTIAL`,必须再跑一次完整 scope。
37
+ - 完整 scope 全过 → 管线进入 `ready_to_archive`,先 `novel_brief P8` 领取终审任务书,再调用 `novel_archive`。
36
38
  - 任一项 `fail` → 校验不过,先重新领取 `novel_brief P6`(check 已改变上下文,旧 digest 会失效),再调用 `novel_revise` 修订并回到 P4 重评(轮次 +1)。
37
- - 全过 → 管线进入 `ready_to_archive`,先 `novel_brief P8` 领取终审任务书,再调用 `novel_archive`。
39
+
40
+ ## 损坏与恢复
41
+
42
+ - 如果 `novel_status` 返回 `state_errors` 或 `STATE_UNREADABLE`,不要手改文件;plan/review/check 按 `next` 指向的 `novel_rollback` 回到重建点;八个 ledger 则按 `next` 调 `novel_amend`,先用 `novel_state` 取 raw 文档再提交完整替换。
43
+ - 如果 `novel_archive` 报 review 没有 `review_digest`,回退到 P4 重新 `novel_review`;不要编辑 `state/pipeline.yaml`。
38
44
 
39
45
  ## 不许做的
40
46
 
package/lib/client.js CHANGED
@@ -102,6 +102,10 @@ window.__ModuleLoader__.load({
102
102
  const stageLabel = (stage) => STAGE_LABELS[stage] ?? stage;
103
103
  const platformLabel = (platform) => PLATFORM_LABELS[platform] ?? platform;
104
104
  const toolLabel = (tool) => TOOL_LABELS[tool] ?? "继续";
105
+ const volumeLabel = (summary) => {
106
+ if (summary.volume === void 0) return "";
107
+ return ` · ${summary.volume_name === void 0 ? `第${summary.volume}卷` : summary.volume_name}${summary.act === void 0 ? "" : ` · ${summary.act}${summary.act_progress === void 0 ? "" : ` ${summary.act_progress}`}`}`;
108
+ };
105
109
  /**
106
110
  * Project the wire payload into strings. Version mismatch wins over everything
107
111
  * else: a stale panel must never render data from another schema as if it were
@@ -146,14 +150,15 @@ window.__ModuleLoader__.load({
146
150
  };
147
151
  });
148
152
  const scored = rows.filter((row) => row.tone === "ok");
149
- const average = scored.length === 0 ? 0 : scored.reduce((sum, row) => sum + row.scoreValue, 0) / scored.length;
153
+ const rowAverage = scored.length === 0 ? 0 : scored.reduce((sum, row) => sum + row.scoreValue, 0) / scored.length;
154
+ const average = summary.averageScore !== void 0 && Number.isFinite(summary.averageScore) ? summary.averageScore : rowAverage;
150
155
  const next = summary.next;
151
156
  const finished = summary.finished === true;
152
157
  const doneLabel = summary.stage === "done" ? finished ? "全书完成" : "本章已归档" : stageLabel(summary.stage);
153
158
  return {
154
159
  state: "ready",
155
160
  title: `《${summary.title}》`,
156
- subtitle: summary.stage === "done" ? `${summary.genre} · ${platformLabel(summary.platform)} · ${finished ? `共 ${summary.totalChapters} 章` : `下一章 第 ${summary.chapter} 章`}` : `${summary.genre} · ${platformLabel(summary.platform)} · 第 ${summary.chapter} 章`,
161
+ subtitle: summary.stage === "done" ? `${summary.genre} · ${platformLabel(summary.platform)} · ${finished ? `共 ${summary.totalChapters} 章` : `下一章 第 ${summary.chapter} 章`}${volumeLabel(summary)}` : `${summary.genre} · ${platformLabel(summary.platform)} · 第 ${summary.chapter} 章${volumeLabel(summary)}`,
157
162
  stageLabel: doneLabel,
158
163
  finished,
159
164
  revisionLabel: `第 ${summary.revision} 次修订`,
@@ -577,7 +582,7 @@ window.__ModuleLoader__.load({
577
582
  }, stat("已归档", String(view.stats.chapters), "章"), stat("总字数", view.stats.words.toLocaleString("zh-CN"), "字"), stat("平均评分", view.stats.chapters === 0 ? "—" : String(view.stats.averageScore), "/150"), stat("待处理", String(view.stats.blocking), "项")) : null, view.state === "loading" ? loadingState() : null, view.state === "error" || view.state === "empty" || view.state === "outdated" ? statePanel(view.state, view, loading, onRefresh) : null, ready ? phases(view) : null, ready && view.nextTitle !== "" ? (0, react.createElement)("section", {
578
583
  className: "nwe-next",
579
584
  "aria-label": "下一步"
580
- }, (0, react.createElement)("div", { className: "nwe-next-icon" }, arrowIcon()), (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "nwe-next-kicker" }, "NEXT MOVE"), (0, react.createElement)("div", { className: "nwe-next-title" }, view.nextTitle), (0, react.createElement)("div", { className: "nwe-next-detail" }, view.nextDetail)), view.nextTool === "" ? null : (0, react.createElement)("code", { className: "nwe-next-tool" }, view.nextTool)) : null, ready ? (0, react.createElement)("section", { className: "nwe-section" }, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "章节"), (0, react.createElement)("span", { className: "nwe-section-note" }, `${view.rows.length} 条记录${view.rows.length > 30 ? " · 显示最近 30 条" : ""}`)), view.rows.length === 0 ? (0, react.createElement)("div", {
585
+ }, (0, react.createElement)("div", { className: "nwe-next-icon" }, arrowIcon()), (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "nwe-next-kicker" }, "NEXT MOVE"), (0, react.createElement)("div", { className: "nwe-next-title" }, view.nextTitle), (0, react.createElement)("div", { className: "nwe-next-detail" }, view.nextDetail)), view.nextTool === "" ? null : (0, react.createElement)("code", { className: "nwe-next-tool" }, view.nextTool)) : null, ready ? (0, react.createElement)("section", { className: "nwe-section" }, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "章节"), (0, react.createElement)("span", { className: "nwe-section-note" }, `${view.stats.chapters} 条记录${view.stats.chapters > view.rows.filter((row) => row.tone === "ok").length ? ` · 显示最近 ${view.rows.length} 条` : ""}`)), view.rows.length === 0 ? (0, react.createElement)("div", {
581
586
  className: "nwe-empty",
582
587
  style: {
583
588
  marginTop: 12,
package/lib/index.d.ts CHANGED
@@ -171,6 +171,14 @@ interface CheckDocument {
171
171
  };
172
172
  verdict: 'pass' | 'fail';
173
173
  }
174
+ /** Every persisted document whose damage status/panel can report. */
175
+ type StateErrorCode = 'PROJECT_UNREADABLE' | 'PLAN_UNREADABLE' | 'WORLD_PLAN_UNREADABLE' | 'REVIEW_UNREADABLE' | 'CHECK_UNREADABLE' | 'CHAPTERS_UNREADABLE' | 'CHARACTERS_UNREADABLE' | 'WORLD_UNREADABLE' | 'LOCKS_UNREADABLE' | 'TIMELINE_UNREADABLE' | 'FORESHADOWS_UNREADABLE' | 'RELATIONSHIPS_UNREADABLE' | 'HANDOFF_UNREADABLE';
176
+ /** A state document that exists but cannot be read safely. */
177
+ interface StateError {
178
+ path: string;
179
+ code: StateErrorCode;
180
+ message: string;
181
+ }
174
182
  /** Word statistics with the single engine-wide counting rule. */
175
183
  interface WordMetrics {
176
184
  total_word_count: number;
@@ -497,6 +505,14 @@ interface WorldPlanInput {
497
505
  }>;
498
506
  continuity_notes: string[];
499
507
  }
508
+ /** A character the chapter plan puts on stage; no new persisted schema. */
509
+ interface ChapterCastMember {
510
+ name: string;
511
+ entry_state?: string;
512
+ goal?: string;
513
+ knowledge?: string[];
514
+ exit_change?: string;
515
+ }
500
516
  /** Chapter plan (`plans/chNNN.chapter.yaml`). */
501
517
  interface ChapterPlanInput {
502
518
  chapter: number;
@@ -507,6 +523,8 @@ interface ChapterPlanInput {
507
523
  opening: string;
508
524
  ending: string;
509
525
  target_words: [number, number];
526
+ /** Optional in 0.1.3: older plans remain readable; new plans may name 1–8 actors. */
527
+ cast?: ChapterCastMember[];
510
528
  }
511
529
  /** Draft submission payload. */
512
530
  interface DraftInput {
@@ -557,6 +575,8 @@ interface GateInput {
557
575
  summaryProvided?: boolean;
558
576
  review: ReviewDocument | null;
559
577
  check: CheckDocument | null;
578
+ /** When set, result-bearing gates run only the named check scopes. */
579
+ scope?: string[];
560
580
  semantic: SemanticItem[];
561
581
  proof: ReadingProof | null;
562
582
  rounds: RoundCounters;
@@ -608,7 +628,14 @@ interface StatusReport {
608
628
  stage: Stage;
609
629
  rounds: RoundCounters;
610
630
  blocking: BlockingItem[];
631
+ /** plan/review/check/ledger files that exist but cannot be read safely. */
632
+ state_errors: StateError[];
611
633
  metrics: WordMetrics | null;
634
+ /** Inherited handoff labels; absent before the first archive. */
635
+ volume?: number;
636
+ volume_name?: string;
637
+ act?: string;
638
+ act_progress?: string;
612
639
  next: {
613
640
  tool: string;
614
641
  args: Record<string, unknown>;
@@ -623,6 +650,7 @@ interface AdviceReport {
623
650
  args: Record<string, unknown>;
624
651
  reason: string;
625
652
  blocking: BlockingItem[];
653
+ state_errors: StateError[];
626
654
  }
627
655
  /** One history row derived from the journal. */
628
656
  interface AuditRow {
@@ -863,6 +891,10 @@ interface PanelSnapshot {
863
891
  check: number;
864
892
  };
865
893
  blocking: BlockingItem[];
894
+ volume?: number;
895
+ volume_name?: string;
896
+ act?: string;
897
+ act_progress?: string;
866
898
  /** Recommended next action; lets the panel explain a stage with no blockers. */
867
899
  next?: StatusReport['next'];
868
900
  /** True only when the configured total chapter target has been reached. */
@@ -872,6 +904,8 @@ interface PanelSnapshot {
872
904
  draft?: PanelDraft | null;
873
905
  /** Total words across the index, for the header. */
874
906
  totalWords: number;
907
+ /** Book-wide archived average, not just the panel's capped rows. */
908
+ averageScore?: number;
875
909
  /** The nine-phase strip state, derived from the pipeline stage. */
876
910
  phases: Array<{
877
911
  id: string;
@@ -890,6 +924,12 @@ interface PanelSummaryDto {
890
924
  revision: number;
891
925
  totalWords: number;
892
926
  totalChapters: number;
927
+ /** Book-wide archived average; absent on older payloads. */
928
+ averageScore?: number;
929
+ volume?: number;
930
+ volume_name?: string;
931
+ act?: string;
932
+ act_progress?: string;
893
933
  phases: Array<{
894
934
  id: string;
895
935
  done: boolean;
@@ -1073,6 +1113,10 @@ interface ExportInput {
1073
1113
  interface StateQueryInput {
1074
1114
  kind: DocKind | 'novel' | 'planWorld' | 'planChapter';
1075
1115
  chapter?: number;
1116
+ /** Chapter index defaults to a compact read; opt in to the full summaries. */
1117
+ include_summary?: boolean;
1118
+ /** Inclusive chapter range for the `chapters` index. */
1119
+ chapter_range?: [number, number];
1076
1120
  root?: string;
1077
1121
  }
1078
1122
  interface WordCountInput {
@@ -1089,6 +1133,8 @@ declare class EngineService {
1089
1133
  private readonly stores;
1090
1134
  private readonly mutex;
1091
1135
  private readonly replay;
1136
+ /** Status, panel and gates ask for the same stage context repeatedly. */
1137
+ private readonly briefContextCache;
1092
1138
  private readonly briefs;
1093
1139
  private readonly now;
1094
1140
  /** The project this process last touched; the panel follows it without a workspace hint. */
@@ -1128,6 +1174,12 @@ declare class EngineService {
1128
1174
  chapter: number;
1129
1175
  stage: Stage;
1130
1176
  }>>;
1177
+ /**
1178
+ * Read-only paths must surface a committed-journal mismatch. The write path
1179
+ * already fails closed; without this, status recommends the next write and
1180
+ * the model only learns about the mismatch when that write aborts.
1181
+ */
1182
+ private journalBlocking;
1131
1183
  getStatus(input?: StatusInput): Promise<OpResult<StatusReport>>;
1132
1184
  countWords(input?: WordCountInput): Promise<OpResult<WordMetrics>>;
1133
1185
  queryState(input: StateQueryInput): Promise<OpResult<unknown>>;
@@ -1140,6 +1192,7 @@ declare class EngineService {
1140
1192
  args: Record<string, unknown>;
1141
1193
  reason: string;
1142
1194
  blocking: BlockingItem[];
1195
+ state_errors: StateError[];
1143
1196
  }>>;
1144
1197
  getBrief(input: BriefInput): Promise<OpResult<{
1145
1198
  stage: string;
@@ -1275,11 +1328,32 @@ declare class EngineService {
1275
1328
  applied: boolean;
1276
1329
  }>>;
1277
1330
  private panelSnapshot;
1331
+ /**
1332
+ * The archive stage has only one legal operation, so a status/panel that
1333
+ * recommends it without checking the same gates as the archive creates a
1334
+ * loop. Evaluate the real archive gates through `gateInput`, then add the
1335
+ * machine pass and semantic evidence that archive re-checks separately.
1336
+ */
1337
+ private archivePreflight;
1278
1338
  private nextStep;
1279
1339
  /** A computed blocker for the retry budget the next operation would consume. */
1280
1340
  private roundsLimitBlock;
1281
1341
  /** Pipeline blockers plus budget exhaustion, so status/panel cannot say "all clear". */
1282
1342
  private effectiveBlocking;
1343
+ /**
1344
+ * A review that exists but cannot be tied to the pipeline digest would let a
1345
+ * hand-edited review pass archive. Archive already fails closed; status and
1346
+ * next must surface the same repair path instead of pointing at the archive
1347
+ * command forever.
1348
+ */
1349
+ private reviewDigestProblem;
1350
+ private stateErrors;
1351
+ /** The earliest rebuild point for the current state damage. */
1352
+ private recoveryFor;
1353
+ /** Never point at a stage later than the current one; rollback only moves backward. */
1354
+ private clampRollbackTarget;
1355
+ /** State damage that makes this operation unsafe to start. */
1356
+ private stateGateBlock;
1283
1357
  /** Whether the stored brief still signs the current snapshot. */
1284
1358
  private briefIsCurrent;
1285
1359
  /** Arguments valid for the tool `STAGE_NEXT` recommends at this stage. */
@@ -1292,10 +1366,20 @@ declare class EngineService {
1292
1366
  private briefContextFor;
1293
1367
  private briefDigest;
1294
1368
  private briefVariables;
1369
+ /**
1370
+ * The one place a gate input is assembled. Archive preflight evaluates the
1371
+ * same object as the real archive, so a gate cannot pass preflight and then
1372
+ * fail the write (the old PG-06/scope holes did exactly that).
1373
+ */
1374
+ private gateInput;
1295
1375
  private assertGates;
1296
1376
  private assertChapterParameter;
1297
1377
  private assertChapter;
1298
1378
  private load;
1379
+ /** A repair may bypass the journal hash only when the document is unreadable. */
1380
+ private stateDocIsUnreadable;
1381
+ /** Read a review/check without letting one corrupt file wedge every read. */
1382
+ private recoverableDoc;
1299
1383
  private mutate;
1300
1384
  private invalidateReplay;
1301
1385
  private emptySnapshot;
@@ -1342,4 +1426,4 @@ declare const Config: Schema<Config>;
1342
1426
  * created lazily per project root.
1343
1427
  */
1344
1428
  declare function apply(ctx: Context, config: Config): void;
1345
- export { AdviceReport, ArchiveHandoff, AuditRow, BlockingItem, BriefDocument, BriefLibrary, BriefStage, ChapterPlanInput, ChapterRecord, ChapterStatusRow, ChapterSummary, ChaptersDocument, CharacterDelta, CharactersDocument, CheckDocument, Config, Deduction, DeltaAction, DiffReport, DraftInput, ERROR_CODES, EngineBlockedError, EngineError, EngineFaultError, EngineInputError, EngineService, ErrorCode, ExportChapter, ExportKind, ForeshadowDelta, ForeshadowsDocument, GENRES, GateInput, GateOptions, GateResult, Genre, HandoffDocument, LocksDocument, MachineMetrics, NovelProject, OPERATIONS, OpResult, Operation, PLATFORMS, PanelChapterRow, PanelChaptersDto, PanelDraft, PanelGatesDto, PanelSnapshot, PanelSummaryDto, PipelineState, Platform, ReadingProof, ReadingProofReference, RelationshipDelta, RelationshipsDocument, ReviewDocument, ReviewInput, ReviewIssue, RevisionInput, RoundCounters, SCENE_TYPES, SCHEMA_VERSION, SceneType, ScoreDimension, SemanticItem, Severity, Stage, StateDelta, StatusReport, TimelineDelta, TimelineDocument, Violation, Warning, WordMetrics, WorldDelta, WorldDocument, WorldPlanInput, applicableGates, apply, blockedError, briefStageFor, chapterHeading, evaluateGates, faultError, gateName, inject, inputError, loadRulepack, name, parseParagraphLocation, phaseStrip, projectChapter, projectChapters, projectGates, projectSummary, renderBlurb, renderCover, renderEpubChapter, renderMarkdown, renderPlainText, verifyReference };
1429
+ export { AdviceReport, ArchiveHandoff, AuditRow, BlockingItem, BriefDocument, BriefLibrary, BriefStage, ChapterCastMember, ChapterPlanInput, ChapterRecord, ChapterStatusRow, ChapterSummary, ChaptersDocument, CharacterDelta, CharactersDocument, CheckDocument, Config, Deduction, DeltaAction, DiffReport, DraftInput, ERROR_CODES, EngineBlockedError, EngineError, EngineFaultError, EngineInputError, EngineService, ErrorCode, ExportChapter, ExportKind, ForeshadowDelta, ForeshadowsDocument, GENRES, GateInput, GateOptions, GateResult, Genre, HandoffDocument, LocksDocument, MachineMetrics, NovelProject, OPERATIONS, OpResult, Operation, PLATFORMS, PanelChapterRow, PanelChaptersDto, PanelDraft, PanelGatesDto, PanelSnapshot, PanelSummaryDto, PipelineState, Platform, ReadingProof, ReadingProofReference, RelationshipDelta, RelationshipsDocument, ReviewDocument, ReviewInput, ReviewIssue, RevisionInput, RoundCounters, SCENE_TYPES, SCHEMA_VERSION, SceneType, ScoreDimension, SemanticItem, Severity, Stage, StateDelta, StateError, StateErrorCode, StatusReport, TimelineDelta, TimelineDocument, Violation, Warning, WordMetrics, WorldDelta, WorldDocument, WorldPlanInput, applicableGates, apply, blockedError, briefStageFor, chapterHeading, evaluateGates, faultError, gateName, inject, inputError, loadRulepack, name, parseParagraphLocation, phaseStrip, projectChapter, projectChapters, projectGates, projectSummary, renderBlurb, renderCover, renderEpubChapter, renderMarkdown, renderPlainText, verifyReference };