dsh-novel-write-engine 0.2.0 → 0.2.2

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/lib/index.d.ts CHANGED
@@ -22,8 +22,8 @@ declare const SCENE_TYPES: readonly ["战斗/动作章", "日常过渡章", "情
22
22
  type SceneType = (typeof SCENE_TYPES)[number];
23
23
  /** A violation is blocking unless the rule pack declares it advisory. */
24
24
  type Severity = 'blocker' | 'advisory';
25
- /** All thirty-one service operations (22 inherited from 0.1.x, 9 book-level). */
26
- 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"];
25
+ /** All thirty-two service operations (22 inherited + 9 book-level + restoreBackup). */
26
+ 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"];
27
27
  type Operation = (typeof OPERATIONS)[number];
28
28
  /** Structured, non-fatal note attached to a successful result. */
29
29
  interface Warning {
@@ -60,6 +60,9 @@ interface ReadingProof {
60
60
  /** Error families map one-to-one to the three repair actions a caller can take. */
61
61
  declare const ERROR_CODES: {
62
62
  readonly PROJECT_NOT_FOUND: "ENGINE_PROJECT_NOT_FOUND";
63
+ readonly PROJECT_AMBIGUOUS: "ENGINE_PROJECT_AMBIGUOUS";
64
+ readonly PROJECT_MISSING: "ENGINE_PROJECT_MISSING";
65
+ readonly PROJECT_IDENTITY_MISMATCH: "ENGINE_PROJECT_IDENTITY_MISMATCH";
63
66
  readonly PROJECT_EXISTS: "ENGINE_PROJECT_EXISTS";
64
67
  readonly STAGE_ORDER: "ENGINE_STAGE_ORDER";
65
68
  readonly GATE_BLOCKED: "ENGINE_GATE_BLOCKED";
@@ -161,6 +164,7 @@ interface CheckDocument {
161
164
  content_hash: string;
162
165
  machine: {
163
166
  violations: Violation[];
167
+ advisories?: Violation[];
164
168
  metrics: WordMetrics;
165
169
  };
166
170
  semantic: {
@@ -171,6 +175,8 @@ interface CheckDocument {
171
175
  note: string;
172
176
  }>;
173
177
  };
178
+ /** Advisory codes the author has explicitly acknowledged for this content. */
179
+ acknowledged?: string[];
174
180
  verdict: 'pass' | 'fail';
175
181
  }
176
182
  /** Every persisted document whose damage status/panel can report. */
@@ -189,6 +195,8 @@ interface WordMetrics {
189
195
  numbers: number;
190
196
  chinese_punct: number;
191
197
  english_punct: number;
198
+ /** All punctuation counted by the engine; total = cjk + words + numbers + punctuation. */
199
+ punctuation_chars: number;
192
200
  non_ws_chars: number;
193
201
  total_lines: number;
194
202
  non_empty_lines: number;
@@ -211,6 +219,10 @@ interface ChapterRecord {
211
219
  status: 'draft' | 'reviewed';
212
220
  content_hash: string;
213
221
  summary: ChapterSummary;
222
+ /** How hard this chapter was before it was archived. */
223
+ rounds?: RoundCounters;
224
+ final_check?: 'pass' | 'fail';
225
+ archived_at?: string;
214
226
  /** Keys allocated by the archive transaction; lets a cross-process retry replay. */
215
227
  assigned_ids?: Record<string, string>;
216
228
  /** Digest of the archive request that produced this row. */
@@ -225,6 +237,7 @@ interface ChapterSummary {
225
237
  key: string;
226
238
  title: string;
227
239
  description: string;
240
+ anchor?: string;
228
241
  }>;
229
242
  advanced: Array<{
230
243
  key: string;
@@ -258,6 +271,8 @@ interface RoundCounters {
258
271
  review: number;
259
272
  revise: number;
260
273
  check: number;
274
+ /** Lifetime rollback count on a chapter record; never stored in the live pipeline. */
275
+ rollbacks?: number;
261
276
  }
262
277
  /** How one ledger entity changed in this chapter. */
263
278
  type DeltaAction = 'created' | 'updated' | 'no_change';
@@ -289,6 +304,8 @@ interface ForeshadowDelta {
289
304
  action: DeltaAction;
290
305
  reason?: string;
291
306
  title?: string;
307
+ /** Verbatim manuscript quote used for verification; title stays a readable label. */
308
+ anchor?: string;
292
309
  description?: string;
293
310
  note?: string;
294
311
  new_info?: boolean;
@@ -331,6 +348,28 @@ interface ArchiveHandoff {
331
348
  act?: string;
332
349
  act_progress?: string;
333
350
  }
351
+ /** One issued task-book receipt; the brief itself is rendered on demand. */
352
+ interface BriefReceipt {
353
+ chapter: number;
354
+ stage: BriefStage;
355
+ digest: string;
356
+ issued_at: string;
357
+ }
358
+ /** `state/briefs.yaml`: append-only task-book evidence outside content transactions. */
359
+ interface BriefReceiptsDocument {
360
+ schema_version: 1;
361
+ receipts: BriefReceipt[];
362
+ }
363
+ /** The active draft's own metadata; P2 plan stays immutable after submission. */
364
+ interface DraftState {
365
+ chapter: number;
366
+ title: string;
367
+ scene_type: SceneType;
368
+ narrator: string;
369
+ summary: ChapterSummary;
370
+ content_hash: string;
371
+ updated_at: string;
372
+ }
334
373
  /** Pipeline document (`state/pipeline.yaml`). */
335
374
  interface PipelineState {
336
375
  schema_version: 1;
@@ -343,6 +382,8 @@ interface PipelineState {
343
382
  digest: string;
344
383
  }>;
345
384
  blocking: BlockingItem[];
385
+ /** Current draft summary/title; never written into the P2 plan file. */
386
+ draft?: DraftState;
346
387
  /** Hash of the review document last written by submitReview/submitRevision. */
347
388
  review_digest?: string;
348
389
  last_action: {
@@ -422,6 +463,7 @@ interface ForeshadowsDocument {
422
463
  status: 'planted' | 'dormant' | 'advanced' | 'resolved';
423
464
  resolved_at: number | null;
424
465
  description: string;
466
+ anchor?: string;
425
467
  advance_log: Array<{
426
468
  chapter: number;
427
469
  note: string;
@@ -614,6 +656,14 @@ interface WorldbookBackstory {
614
656
  time: string;
615
657
  event: string;
616
658
  }
659
+ /** One declared numeric canon, checked against the manuscript on request. */
660
+ interface WorldbookFact {
661
+ alias: string;
662
+ value: number;
663
+ unit: string;
664
+ tolerance?: number;
665
+ note?: string;
666
+ }
617
667
  /** Worldbuilding plan, persisted in `outline/worldbook.yaml`. */
618
668
  interface WorldbookDocument {
619
669
  schema_version: 1;
@@ -626,6 +676,8 @@ interface WorldbookDocument {
626
676
  characters: WorldbookCharacter[];
627
677
  glossary: WorldbookGlossary[];
628
678
  backstory: WorldbookBackstory[];
679
+ /** Optional numeric canon; old documents omit it. */
680
+ facts?: WorldbookFact[];
629
681
  themes: string[];
630
682
  }
631
683
  /** One hit from `novel_search`; the scope names the source family. */
@@ -663,6 +715,22 @@ interface DoctorReport {
663
715
  infos: number;
664
716
  };
665
717
  }
718
+ /** One canonical outline-progress calculation shared by every adapter. */
719
+ interface OutlineProgress {
720
+ present: boolean;
721
+ target: number | null;
722
+ total: number;
723
+ planned: number;
724
+ archived: number;
725
+ missingCards: number;
726
+ plannedCoverage: number | null;
727
+ archivedCoverage: number | null;
728
+ /** Compatibility alias: planned coverage when a target exists, otherwise card coverage. */
729
+ coverage: number;
730
+ /** Fraction of archived chapters that have a matching archived card. */
731
+ cardCoverage: number;
732
+ volumes: number;
733
+ }
666
734
  /** Book-wide quality, outline, cast and pacing aggregates. */
667
735
  interface BookReport {
668
736
  progress: {
@@ -672,13 +740,22 @@ interface BookReport {
672
740
  average_words: number;
673
741
  percent: number;
674
742
  remaining_words: number | null;
743
+ remaining_words_min: number | null;
744
+ remaining_words_average: number | null;
745
+ remaining_words_basis: 'actual_average' | 'minimum_target' | 'none';
675
746
  };
676
747
  outline: {
677
748
  present: boolean;
678
749
  strict: boolean;
750
+ target: number | null;
751
+ total: number;
679
752
  planned: number;
680
753
  archived: number;
754
+ missing_cards: number;
755
+ planned_coverage: number | null;
756
+ archived_coverage: number | null;
681
757
  coverage: number;
758
+ card_coverage: number;
682
759
  volumes: Array<{
683
760
  title: string;
684
761
  start: number;
@@ -701,6 +778,7 @@ interface BookReport {
701
778
  rules: number;
702
779
  glossary: number;
703
780
  backstory: number;
781
+ facts?: number;
704
782
  };
705
783
  quality: {
706
784
  average: number;
@@ -715,6 +793,22 @@ interface BookReport {
715
793
  samples: number;
716
794
  }>;
717
795
  };
796
+ /** Per-chapter effort survives archive (pipeline rounds are reset). */
797
+ effort: {
798
+ reviews: number;
799
+ revisions: number;
800
+ checks: number;
801
+ rollbacks: number;
802
+ chapters: Array<{
803
+ num: number;
804
+ reviews: number;
805
+ revisions: number;
806
+ checks: number;
807
+ rollbacks: number;
808
+ final_check: 'pass' | 'fail' | null;
809
+ archived_at: string | null;
810
+ }>;
811
+ };
718
812
  pacing: {
719
813
  scene_types: Record<string, number>;
720
814
  hook_strength: Record<string, number>;
@@ -805,10 +899,23 @@ interface ReviewInput {
805
899
  total: number;
806
900
  proof: ReadingProof;
807
901
  }
902
+ /** One exact, unique replacement used by patch-mode revisions. */
903
+ interface RevisionEdit {
904
+ old_text: string;
905
+ new_text: string;
906
+ /** 1-based occurrence; defaults to requiring exactly one match. */
907
+ occurrence?: number;
908
+ }
909
+ /** Patch-mode revision; content and patch are mutually exclusive. */
910
+ interface RevisionPatch {
911
+ expected_hash: string;
912
+ edits: RevisionEdit[];
913
+ }
808
914
  /** Revision submission payload. */
809
915
  interface RevisionInput {
810
916
  chapter: number;
811
- content: string;
917
+ content?: string;
918
+ patch?: RevisionPatch;
812
919
  summary: ChapterSummary;
813
920
  }
814
921
  /** Semantic check items supplied by the model. */
@@ -880,6 +987,15 @@ interface ChapterStatusRow {
880
987
  stage: Stage;
881
988
  passed: boolean;
882
989
  }
990
+ /** Where an omitted project root came from. */
991
+ type SelectionSource = 'explicit' | 'session' | 'workspace' | 'active' | 'config' | 'single';
992
+ /** One resolved project choice, before the project is loaded. */
993
+ interface Selection {
994
+ root: string;
995
+ source: SelectionSource;
996
+ title?: string;
997
+ created_at?: string;
998
+ }
883
999
  /** The `getStatus` payload. */
884
1000
  interface StatusReport {
885
1001
  project: {
@@ -888,6 +1004,11 @@ interface StatusReport {
888
1004
  platform: Platform;
889
1005
  root: string;
890
1006
  };
1007
+ /** Why this project was chosen; lets the model notice a wrong book. */
1008
+ selection?: {
1009
+ source: SelectionSource;
1010
+ root: string;
1011
+ };
891
1012
  /** The chapter requested for metrics; may be historical. */
892
1013
  chapter: number;
893
1014
  /** The chapter the pipeline is currently working on. */
@@ -910,10 +1031,15 @@ interface StatusReport {
910
1031
  strict: boolean;
911
1032
  outline: {
912
1033
  present: boolean;
1034
+ target: number | null;
913
1035
  total: number;
914
1036
  planned: number;
915
1037
  archived: number;
1038
+ missing_cards: number;
1039
+ planned_coverage: number | null;
1040
+ archived_coverage: number | null;
916
1041
  coverage: number;
1042
+ card_coverage: number;
917
1043
  volumes: number;
918
1044
  current: OutlineChapter | null;
919
1045
  };
@@ -925,6 +1051,7 @@ interface StatusReport {
925
1051
  rules: number;
926
1052
  glossary: number;
927
1053
  backstory: number;
1054
+ facts?: number;
928
1055
  };
929
1056
  };
930
1057
  next: {
@@ -945,6 +1072,8 @@ interface AdviceReport {
945
1072
  }
946
1073
  /** One history row derived from the journal. */
947
1074
  interface AuditRow {
1075
+ /** Stable chronological cursor; absent only on hand-built test rows. */
1076
+ seq?: number;
948
1077
  at: string;
949
1078
  op: string;
950
1079
  chapter: number;
@@ -952,6 +1081,14 @@ interface AuditRow {
952
1081
  txid: string;
953
1082
  status: 'committed' | 'aborted';
954
1083
  files: string[];
1084
+ /** Machine code and human reason for a refused attempt. */
1085
+ code?: string;
1086
+ reason?: string;
1087
+ hint?: string;
1088
+ stage?: string;
1089
+ /** True when no transaction started: the gate/input refused the call. */
1090
+ attempt?: boolean;
1091
+ blocking?: BlockingItem[];
955
1092
  }
956
1093
  /** One diff hunk between two revisions of a chapter. */
957
1094
  interface DiffReport {
@@ -1099,6 +1236,8 @@ type DocKind = 'project' | 'pipeline' | 'chapters' | 'characters' | 'world' | 'l
1099
1236
  interface RegistryEntry {
1100
1237
  root: string;
1101
1238
  title: string;
1239
+ /** Project identity; absent on registries written by 0.2.1 and earlier. */
1240
+ created_at?: string;
1102
1241
  updated_at: string;
1103
1242
  }
1104
1243
  interface BookPatch {
@@ -1151,6 +1290,7 @@ interface WorldbookPatch {
1151
1290
  characters?: WorldbookCharacter[];
1152
1291
  glossary?: WorldbookGlossary[];
1153
1292
  backstory?: WorldbookBackstory[];
1293
+ facts?: WorldbookFact[];
1154
1294
  themes?: string[];
1155
1295
  }
1156
1296
  /** One rendered task book. */
@@ -1282,10 +1422,15 @@ interface PanelBook {
1282
1422
  target: number | null;
1283
1423
  outline: {
1284
1424
  present: boolean;
1425
+ target: number | null;
1285
1426
  total: number;
1286
1427
  planned: number;
1287
1428
  archived: number;
1429
+ missing_cards: number;
1430
+ planned_coverage: number | null;
1431
+ archived_coverage: number | null;
1288
1432
  coverage: number;
1433
+ card_coverage: number;
1289
1434
  volumes: Array<{
1290
1435
  title: string;
1291
1436
  start: number;
@@ -1302,11 +1447,14 @@ interface PanelBook {
1302
1447
  rules: number;
1303
1448
  glossary: number;
1304
1449
  backstory: number;
1450
+ facts?: number;
1305
1451
  };
1306
1452
  }
1307
1453
  interface PanelSummaryDto {
1308
1454
  schemaVersion: number;
1309
1455
  panelId: string;
1456
+ /** Absolute project root, so a stale title cannot hide a wrong book. */
1457
+ root: string;
1310
1458
  title: string;
1311
1459
  genre: string;
1312
1460
  platform: string;
@@ -1375,6 +1523,47 @@ interface PanelGatesDto {
1375
1523
  /** Human-facing one-liner per blocking item. */
1376
1524
  explanations: string[];
1377
1525
  }
1526
+ /** One selectable project in the panel; state is cheap, not a full load. */
1527
+ interface PanelProjectOption {
1528
+ root: string;
1529
+ title: string;
1530
+ active: boolean;
1531
+ registered: boolean;
1532
+ state: 'ok' | 'damaged' | 'missing' | 'unreadable';
1533
+ }
1534
+ interface PanelSelection {
1535
+ root: string | null;
1536
+ source: 'query' | 'active' | 'single' | 'config' | 'none';
1537
+ }
1538
+ interface PanelMissing {
1539
+ root: string;
1540
+ title: string;
1541
+ code: 'ENGINE_PROJECT_MISSING' | 'ENGINE_PROJECT_NOT_FOUND';
1542
+ reason: 'missing' | 'not-project' | 'damaged' | 'not_registered' | 'unreadable';
1543
+ hint: string;
1544
+ }
1545
+ interface PanelBundle {
1546
+ projects: PanelProjectOption[];
1547
+ selection: PanelSelection;
1548
+ summary?: PanelSummaryDto;
1549
+ chapters?: PanelChaptersDto;
1550
+ gates?: PanelGatesDto;
1551
+ resume?: {
1552
+ text: string;
1553
+ };
1554
+ missing?: PanelMissing;
1555
+ empty?: true;
1556
+ error?: string;
1557
+ }
1558
+ interface ResumePromptInput {
1559
+ title: string;
1560
+ root: string;
1561
+ chapter: number | string;
1562
+ stage: string;
1563
+ nextTool: string;
1564
+ }
1565
+ /** One shared prompt template for `/novel resume` and the panel copy button. */
1566
+ declare function formatResumePrompt(input: ResumePromptInput): string;
1378
1567
  /** The nine-phase progress strip: phases before the active one are done. */
1379
1568
  declare function phaseStrip(stage: Stage, finished?: boolean): Array<{
1380
1569
  id: string;
@@ -1386,6 +1575,15 @@ declare function projectChapter(record: ChapterRecord): PanelChapterRow;
1386
1575
  declare function projectSummary(snapshot: PanelSnapshot): PanelSummaryDto;
1387
1576
  declare function projectChapters(snapshot: PanelSnapshot): PanelChaptersDto;
1388
1577
  declare function projectGates(snapshot: PanelSnapshot): PanelGatesDto;
1578
+ /** How much of a candidate project can be inspected without loading a snapshot. */
1579
+ type ProjectState = 'ok' | 'damaged' | 'not-project' | 'missing' | 'unreadable';
1580
+ interface ProjectInspection {
1581
+ root: string;
1582
+ state: ProjectState;
1583
+ title: string;
1584
+ created_at?: string;
1585
+ error?: string;
1586
+ }
1389
1587
  /** Plugin configuration, mapped from `Config` in `src/index.ts`. */
1390
1588
  interface EngineConfig {
1391
1589
  projectRoot: string;
@@ -1447,6 +1645,15 @@ type EngineEvent = {
1447
1645
  chapter: number;
1448
1646
  restore_txid: string;
1449
1647
  };
1648
+ } | {
1649
+ name: 'novel/restored';
1650
+ payload: {
1651
+ schemaVersion: 1;
1652
+ projectRoot: string;
1653
+ txid: string;
1654
+ chapter: number;
1655
+ paths: string[];
1656
+ };
1450
1657
  } | {
1451
1658
  name: 'novel/exported';
1452
1659
  payload: {
@@ -1518,6 +1725,8 @@ interface CheckSubmission {
1518
1725
  /** The five P6 semantic items; omission is a failed check, never a pass. */
1519
1726
  semantic: SemanticItem[];
1520
1727
  scope?: string[];
1728
+ /** Advisory codes the author has read and accepts; blockers cannot be acknowledged. */
1729
+ acknowledge?: string[];
1521
1730
  root?: string;
1522
1731
  }
1523
1732
  interface ArchiveSubmission {
@@ -1534,6 +1743,13 @@ interface AmendSubmission {
1534
1743
  reason: string;
1535
1744
  root?: string;
1536
1745
  }
1746
+ interface RestoreSubmission {
1747
+ txid: string;
1748
+ chapter?: number;
1749
+ /** Restrict to these project-relative files; omitted restores every chapter file in the snapshot. */
1750
+ paths?: string[];
1751
+ root?: string;
1752
+ }
1537
1753
  interface RollbackSubmission {
1538
1754
  chapter: number;
1539
1755
  toStage: 'P1' | 'P2' | 'P3' | 'P4' | 'P5' | 'P6';
@@ -1548,6 +1764,12 @@ interface DiffInput {
1548
1764
  }
1549
1765
  interface AuditInput {
1550
1766
  limit?: number;
1767
+ cursor?: number;
1768
+ chapter?: number;
1769
+ op?: string;
1770
+ status?: 'committed' | 'aborted';
1771
+ since?: string;
1772
+ until?: string;
1551
1773
  root?: string;
1552
1774
  }
1553
1775
  interface ExportInput {
@@ -1556,8 +1778,10 @@ interface ExportInput {
1556
1778
  root?: string;
1557
1779
  }
1558
1780
  interface StateQueryInput {
1559
- kind: DocKind | 'novel' | 'planWorld' | 'planChapter';
1781
+ kind: DocKind | 'novel' | 'planWorld' | 'planChapter' | 'history' | 'backups' | 'briefs';
1560
1782
  chapter?: number;
1783
+ /** Select one historical revision instead of listing history. */
1784
+ revision?: number;
1561
1785
  /** Chapter index defaults to a compact read; opt in to the full summaries. */
1562
1786
  include_summary?: boolean;
1563
1787
  /** Inclusive chapter range for the `chapters` index. */
@@ -1606,6 +1830,11 @@ interface ReportSubmission {
1606
1830
  interface GuideInput {
1607
1831
  root?: string;
1608
1832
  }
1833
+ /** Session/workspace context of one tool or command call. */
1834
+ interface EngineCaller {
1835
+ sessionId?: string;
1836
+ cwd?: string;
1837
+ }
1609
1838
  /**
1610
1839
  * The single write authority. Tools, commands, the panel and task books never
1611
1840
  * touch a file; every mutation is one of these operations.
@@ -1619,8 +1848,8 @@ declare class EngineService {
1619
1848
  private readonly briefContextCache;
1620
1849
  private readonly briefs;
1621
1850
  private readonly now;
1622
- /** The project this process last touched; the panel follows it without a workspace hint. */
1623
- private lastRoot;
1851
+ private readonly metadataMutex;
1852
+ private readonly callerContext;
1624
1853
  constructor(config: EngineConfig, rulepack: Rulepack | (() => Rulepack), options?: {
1625
1854
  briefs?: BriefLibrary;
1626
1855
  now?: () => Date;
@@ -1641,15 +1870,52 @@ declare class EngineService {
1641
1870
  private readonly signalContext;
1642
1871
  /** Bind the host cancellation signal to the engine work of one tool call. */
1643
1872
  withSignal<T>(signal: AbortSignal | undefined, task: () => Promise<T>): Promise<T>;
1873
+ /** Bind the calling Agent session and cwd to every engine operation in `task`. */
1874
+ withCaller<T>(caller: EngineCaller | undefined, task: () => Promise<T>): Promise<T>;
1875
+ private caller;
1644
1876
  /** The configured default project root. */
1645
1877
  defaultRoot(): string;
1878
+ /** Resolve a caller-supplied root against the session cwd, like /novel use. */
1879
+ private resolveProjectPath;
1880
+ /** The resolved metadata directory; resolves relative config once per call. */
1881
+ private stateRoot;
1882
+ private stateStore;
1883
+ private bindings;
1884
+ /**
1885
+ * Serialize metadata read-modify-write across sessions and processes. The
1886
+ * project manuscript lock is a different lock; a slow write never blocks a
1887
+ * registry update.
1888
+ */
1889
+ withMetadataLock<T>(task: () => Promise<T>): Promise<T>;
1890
+ private inspectConfigRoot;
1646
1891
  /**
1647
- * Resolve an omitted root through the cross-project registry. The registry is
1648
- * convenience state, so a corrupt or unreadable registry falls back to the
1649
- * configured root instead of making every call fail.
1892
+ * Resolve the four-layer choice: explicit, session binding, bounded workspace
1893
+ * discovery, then registry/config defaults. There is deliberately no
1894
+ * process-global "last project" layer.
1650
1895
  */
1896
+ /** Doctor/guide may run before any project exists; that is not a failure. */
1897
+ private resolveRootForInspection;
1651
1898
  private resolveRoot;
1652
1899
  private storeFor;
1900
+ /** Persist one explicit choice for this DSH session. */
1901
+ private bindSelection;
1902
+ /**
1903
+ * Best-effort discovery bookkeeping. A metadata failure must never turn a
1904
+ * successful project read/write into a failure; the registry is convenience.
1905
+ */
1906
+ private autoRegister;
1907
+ /** Record the session binding/registration implied by a successful selection. */
1908
+ private recordSelection;
1909
+ /**
1910
+ * Read paths may record convenience metadata, but a corrupt or read-only
1911
+ * state directory must not turn a successful explicit-root read into a
1912
+ * failure. Resolution without an explicit root still reads the binding
1913
+ * fail-closed; writes use recordSelection directly and report a committed
1914
+ * mutation with a warning if its metadata update fails.
1915
+ */
1916
+ private recordSelectionBestEffort;
1917
+ /** Registration/activation/use need a readable project identity. */
1918
+ private assertProjectHealthy;
1653
1919
  initProject(input: InitProjectInput): Promise<OpResult<{
1654
1920
  root: string;
1655
1921
  title: string;
@@ -1689,13 +1955,35 @@ declare class EngineService {
1689
1955
  stage: Stage;
1690
1956
  restore_txid: string;
1691
1957
  }>>;
1958
+ /**
1959
+ * Restore one active chapter from a backup transaction. Archived chapters
1960
+ * are refused: their manuscript hash is bound by the index, so restoring
1961
+ * behind the index would corrupt the project. Reopen first, then restore.
1962
+ */
1963
+ restoreBackup(input: RestoreSubmission): Promise<OpResult<{
1964
+ txid: string;
1965
+ chapter: number;
1966
+ restored: string[];
1967
+ }>>;
1692
1968
  /**
1693
1969
  * Read-only paths must surface a committed-journal mismatch. The write path
1694
1970
  * already fails closed; without this, status recommends the next write and
1695
1971
  * the model only learns about the mismatch when that write aborts.
1696
1972
  */
1697
1973
  private journalBlocking;
1974
+ /**
1975
+ * Count every committed attempt for one chapter, not just the loop since the
1976
+ * last rollback. `pipeline.rounds` is deliberately reset by rollback to keep
1977
+ * the retry budget fair; it is not the chapter's lifetime effort.
1978
+ */
1979
+ private lifetimeRounds;
1698
1980
  getStatus(input?: StatusInput): Promise<OpResult<StatusReport>>;
1981
+ /**
1982
+ * Build a status report from an already-loaded snapshot. This deliberately
1983
+ * has no selection side effect: read-only callers such as /novel resume must
1984
+ * not register or bind the project just by reading it.
1985
+ */
1986
+ private statusFor;
1699
1987
  countWords(input?: WordCountInput): Promise<OpResult<WordMetrics>>;
1700
1988
  queryState(input: StateQueryInput): Promise<OpResult<unknown>>;
1701
1989
  advise(input?: {
@@ -1756,6 +2044,9 @@ declare class EngineService {
1756
2044
  chapter: number;
1757
2045
  verdict: 'pass' | 'fail';
1758
2046
  violations: number;
2047
+ advisories: number;
2048
+ advisory_codes: string[];
2049
+ acknowledged: string[];
1759
2050
  metrics: WordMetrics;
1760
2051
  stage: Stage;
1761
2052
  }>>;
@@ -1788,14 +2079,21 @@ declare class EngineService {
1788
2079
  panelGates(input?: {
1789
2080
  root?: string;
1790
2081
  }): Promise<PanelGatesDto>;
1791
- /** One snapshot, three projections: the panel never mixes revisions. */
2082
+ /**
2083
+ * Build the read-only panel payload. The query root is accepted only when it
2084
+ * is registered, active or the configured root; arbitrary paths never reach
2085
+ * the filesystem from the panel route.
2086
+ */
1792
2087
  panelBundle(input?: {
1793
2088
  root?: string;
1794
- }): Promise<{
1795
- summary: PanelSummaryDto;
1796
- chapters: PanelChaptersDto;
1797
- gates: PanelGatesDto;
1798
- }>;
2089
+ }): Promise<PanelBundle>;
2090
+ /** Read-only resume text shared with the panel copy button. */
2091
+ resumePrompt(input?: {
2092
+ root?: string;
2093
+ }): Promise<OpResult<{
2094
+ text: string | null;
2095
+ selection: StatusReport['selection'];
2096
+ }>>;
1799
2097
  stats(input?: {
1800
2098
  root?: string;
1801
2099
  }): Promise<{
@@ -1807,38 +2105,90 @@ declare class EngineService {
1807
2105
  lastScore: number;
1808
2106
  openForeshadows: number;
1809
2107
  plannedChapters: number;
1810
- outlineCoverage: number;
2108
+ totalCards: number;
2109
+ outlineCoverage: number | null;
2110
+ archivedCoverage: number | null;
2111
+ missingCards: number;
1811
2112
  blueprintReady: boolean;
1812
2113
  worldbookEntries: number;
1813
2114
  strictOutline: boolean;
1814
2115
  targetChapters: number | null;
1815
2116
  }>;
1816
2117
  private registry;
1817
- /** List known projects (convenience only; a project opens without a row). */
2118
+ /** List known projects with a cheap directory state for the panel/command. */
1818
2119
  listProjects(): Promise<{
1819
2120
  active: string | null;
1820
- projects: RegistryEntry[];
2121
+ projects: Array<RegistryEntry & {
2122
+ state: ProjectState;
2123
+ }>;
1821
2124
  }>;
1822
- /** Record the current project in the registry. */
2125
+ /**
2126
+ * Record an explicit project row. An unchanged duplicate is a no-op; a path
2127
+ * reused by another project updates the row and reports `replaced`.
2128
+ */
1823
2129
  registerProject(input?: {
1824
2130
  root?: string;
1825
2131
  }): Promise<{
1826
2132
  active: string | null;
1827
2133
  projects: RegistryEntry[];
2134
+ replaced: boolean;
1828
2135
  }>;
1829
- /** Choose the default project for future convenience calls. */
2136
+ /** Choose the panel/default project. This never binds the current session. */
1830
2137
  setActiveProject(input: {
1831
2138
  root: string;
1832
2139
  }): Promise<{
1833
2140
  active: string | null;
2141
+ replaced: boolean;
1834
2142
  }>;
1835
- /** Forget a registry row; the project directory is never touched. */
2143
+ /** Forget a registry row; the project directory and bindings are never touched. */
1836
2144
  forgetProject(input: {
1837
2145
  root: string;
1838
2146
  }): Promise<{
1839
2147
  active: string | null;
1840
2148
  projects: RegistryEntry[];
1841
2149
  }>;
2150
+ /**
2151
+ * Bounded workspace scan. Normal mode is read-only; `register` updates only
2152
+ * healthy, newly discovered rows, never active or session bindings.
2153
+ */
2154
+ scanProjects(input: {
2155
+ cwd: string;
2156
+ register?: boolean;
2157
+ }): Promise<{
2158
+ cwd: string;
2159
+ candidates: Array<ProjectInspection & {
2160
+ registered: boolean;
2161
+ identity_changed?: boolean;
2162
+ reason?: string;
2163
+ }>;
2164
+ truncated: boolean;
2165
+ added: string[];
2166
+ already: string[];
2167
+ skipped: Array<{
2168
+ root: string;
2169
+ reason: string;
2170
+ }>;
2171
+ identity_changed: string[];
2172
+ }>;
2173
+ /** Current session binding, or the inferred fallback when none is set. */
2174
+ currentProject(): Promise<{
2175
+ selection: Selection | null;
2176
+ bound: boolean;
2177
+ state?: ProjectState;
2178
+ error?: string;
2179
+ }>;
2180
+ /** Explicitly switch this session; failure leaves the old binding untouched. */
2181
+ useProject(input: {
2182
+ target: string;
2183
+ }): Promise<{
2184
+ selection: Selection;
2185
+ title: string;
2186
+ created_at: string;
2187
+ }>;
2188
+ /** Clear only this session's choice; never touches a project or registry row. */
2189
+ unbindProject(): Promise<{
2190
+ cleared: boolean;
2191
+ }>;
1842
2192
  /**
1843
2193
  * Import one ledger from a settings pack. Always previews with a diff; a
1844
2194
  * dry run never writes, and an apply is a normal transaction.
@@ -1924,6 +2274,7 @@ declare class EngineService {
1924
2274
  /** State damage that makes this operation unsafe to start. */
1925
2275
  private stateGateBlock;
1926
2276
  /** Whether the stored brief still signs the current snapshot. */
2277
+ private readBriefReceipts;
1927
2278
  private briefIsCurrent;
1928
2279
  /** Arguments valid for the tool `STAGE_NEXT` recommends at this stage. */
1929
2280
  private nextArgs;
@@ -1956,11 +2307,13 @@ declare class EngineService {
1956
2307
  private assertGates;
1957
2308
  private assertChapterParameter;
1958
2309
  private assertChapter;
1959
- /** True when anything survives in the project directory besides novel.yaml. */
1960
- private projectStateExists;
1961
2310
  /** A live writer lock makes a read-only snapshot potentially torn. */
1962
2311
  private lockIsLive;
2312
+ /** Resolve a selection, load it, then persist the explicit choice (best effort). */
1963
2313
  private load;
2314
+ /** Load exactly one already-resolved selection; never writes a binding. */
2315
+ private movedProjectHint;
2316
+ private loadAt;
1964
2317
  /** A repair may bypass the journal hash only when the document is unreadable. */
1965
2318
  private stateDocIsUnreadable;
1966
2319
  /** Read a review/check without letting one corrupt file wedge every read. */
@@ -1971,6 +2324,100 @@ declare class EngineService {
1971
2324
  }
1972
2325
  /** Read, compile and freeze a pack. Runtime callers always use the bundled default. */
1973
2326
  declare function loadRulepack(path?: string): Rulepack;
2327
+ /** Panel and session identifiers. */
2328
+ declare const PLUGIN_NAME = "dsh-novel-write-engine";
2329
+ declare const PANEL_ID = "novel-workshop";
2330
+ declare const PANEL_ROUTE = "/api/novel-write-engine/panel";
2331
+ declare const SKILL_NAME = "novel-write-engine";
2332
+ declare const REMOTE_VERSION = 4;
2333
+ /** The panel is a recent-history view; the wire payload and UI use one cap. */
2334
+ declare const PANEL_CHAPTER_LIMIT = 30;
2335
+ /** Project-relative locations. Every write goes through these constants. */
2336
+ declare const PROJECT_FILE = "novel.yaml";
2337
+ declare const OUTLINE_FILE = "outline/outline.yaml";
2338
+ declare const WORLDBOOK_FILE = "outline/worldbook.yaml";
2339
+ declare const STATE_DIR = "state";
2340
+ declare const CHAPTERS_DIR = "chapters";
2341
+ declare const PLAN_DIR = "plans";
2342
+ declare const REVIEW_DIR = "reviews";
2343
+ declare const CHECK_DIR = "checks";
2344
+ declare const EXPORT_DIR = "exports";
2345
+ declare const BACKUP_DIR = "backups";
2346
+ declare const HISTORY_DIR = "history";
2347
+ /** Brief receipts are append-only evidence, separate from content transactions. */
2348
+ declare const BRIEF_RECEIPTS_FILE = "state/briefs.yaml";
2349
+ declare const OUTLINE_DIR = "outline";
2350
+ declare const LOCK_FILE = ".engine.lock";
2351
+ declare const STAGING_DIR = ".staging";
2352
+ declare const REGISTRY_FILE = "projects.yaml";
2353
+ declare const SESSION_BINDINGS_FILE = "session-bindings.yaml";
2354
+ declare const METADATA_LOCK_FILE = ".metadata.lock";
2355
+ declare const ARCHIVE_DIGESTS_FILE = "state/archive-digests.json";
2356
+ /** Files owned by the engine under `state/`. */
2357
+ declare const STATE_FILES: {
2358
+ readonly pipeline: "pipeline.yaml";
2359
+ readonly chapters: "chapters.yaml";
2360
+ readonly characters: "characters.yaml";
2361
+ readonly world: "world.yaml";
2362
+ readonly locks: "locks.yaml";
2363
+ readonly timeline: "timeline.yaml";
2364
+ readonly foreshadows: "foreshadows.yaml";
2365
+ readonly relationships: "relationships.yaml";
2366
+ readonly handoff: "handoff.yaml";
2367
+ readonly journal: "journal.jsonl";
2368
+ };
2369
+ /** Chapter file name, zero padded to three digits. */
2370
+ declare const chapterFile: (chapter: number) => string;
2371
+ /** Kept for reference when a rollback discards the current draft. */
2372
+ declare const deprecatedChapterFile: (chapter: number) => string;
2373
+ declare const planWorldFile: (chapter: number) => string;
2374
+ declare const planChapterFile: (chapter: number) => string;
2375
+ declare const reviewFile: (chapter: number) => string;
2376
+ declare const checkFile: (chapter: number) => string;
2377
+ declare const reviewHistoryFile: (chapter: number, revision: number) => string;
2378
+ declare const checkHistoryFile: (chapter: number, revision: number) => string;
2379
+ declare const chapterHistoryFile: (chapter: number, revision: number) => string;
2380
+ declare const deprecatedHistoryFile: (chapter: number, revision: number) => string;
2381
+ declare const planHistoryFile: (chapter: number, revision: number) => string;
2382
+ declare const HISTORY_KINDS: readonly ["reviews", "checks", "chapters", "deprecated", "plans"];
2383
+ type HistoryKind = (typeof HISTORY_KINDS)[number];
2384
+ /** Limits that are structural, not taste. */
2385
+ declare const LIMITS: {
2386
+ /** P4 <-> P5/P6 retries before a forced rollback. */
2387
+ maxRounds: number;
2388
+ /** Lock staleness in milliseconds. */
2389
+ lockStaleMs: number;
2390
+ /** How long a writer waits for the keyed mutex before failing. */
2391
+ lockWaitMs: number;
2392
+ /** Reading proofs shorter than this cannot be verified. */
2393
+ minQuoteChars: number;
2394
+ /** World-plan text fields are protected context, so they need a hard bound. */
2395
+ maxWorldPlanItems: number;
2396
+ maxWorldPlanTextChars: number;
2397
+ maxWorldPlanObjectiveChars: number;
2398
+ /** Cast plus summary may exceed eight, but not without a bound. */
2399
+ maxActiveCast: number;
2400
+ /** Outline and worldbook are protected context, so they need hard bounds. */
2401
+ maxOutlineChapters: number;
2402
+ maxOutlineVolumes: number;
2403
+ maxOutlineTextFieldChars: number;
2404
+ maxOutlineNotesChars: number;
2405
+ maxWorldbookEntries: number;
2406
+ maxWorldbookTextChars: number;
2407
+ maxSearchQueryChars: number;
2408
+ };
2409
+ /** Re-exported enums make adapter code readable without importing types twice. */
2410
+ declare const SCENE_TYPE_DEFAULT: SceneType;
2411
+ declare const DEFAULT_GENRE: Genre;
2412
+ declare const DEFAULT_PLATFORM: Platform;
2413
+ declare const INITIAL_STAGE: Stage;
2414
+ /** The order used to render the nine-phase progress strip. */
2415
+ declare const PHASE_STRIP: readonly ["P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9"];
2416
+ /** Tool names, kept here so the arch test can assert one namespace. */
2417
+ declare const TOOL_NAMES: readonly ["novel_init", "novel_status", "novel_brief", "novel_plan_world", "novel_plan_chapter", "novel_write_draft", "novel_review", "novel_revise", "novel_check", "novel_archive", "novel_word_count", "novel_state", "novel_amend", "novel_rollback", "novel_diff", "novel_export", "novel_audit", "novel_pipeline", "novel_blueprint", "novel_outline", "novel_outline_import", "novel_worldbook", "novel_search", "novel_doctor", "novel_report", "novel_guide", "novel_reopen", "novel_restore"];
2418
+ type ToolName = (typeof TOOL_NAMES)[number];
2419
+ /** The ten high-frequency tools kept when `tools.mode = core`. */
2420
+ declare const CORE_TOOLS: readonly ToolName[];
1974
2421
  /** One rendered chapter, assembled by the engine before rendering. */
1975
2422
  interface ExportChapter {
1976
2423
  num: number;
@@ -2013,4 +2460,4 @@ declare const Config: Schema<Config>;
2013
2460
  * created lazily per project root.
2014
2461
  */
2015
2462
  declare function apply(ctx: Context, config: Config): void;
2016
- export { AdviceReport, ArchiveHandoff, AuditRow, BlockingItem, BookPlan, BookReport, BriefDocument, BriefLibrary, BriefStage, ChapterCastMember, ChapterPlanInput, ChapterRecord, ChapterStatusRow, ChapterSummary, ChaptersDocument, CharacterDelta, CharactersDocument, CheckDocument, Config, Deduction, DeltaAction, DiffReport, DoctorCheck, DoctorReport, DraftInput, ERROR_CODES, EngineBlockedError, EngineError, EngineFaultError, EngineInputError, EngineService, ErrorCode, ExportChapter, ExportKind, ForeshadowDelta, ForeshadowsDocument, GENRES, GateInput, GateOptions, GateResult, Genre, GuideReport, GuideStep, HandoffDocument, LocksDocument, MachineMetrics, NovelProject, OPERATIONS, OpResult, Operation, OutlineChapter, OutlineDocument, OutlineVolume, PLATFORMS, PanelBook, PanelChapterRow, PanelChaptersDto, PanelDraft, PanelGatesDto, PanelSnapshot, PanelSummaryDto, PipelineState, Platform, ReadingProof, ReadingProofReference, RelationshipDelta, RelationshipsDocument, ReviewDocument, ReviewInput, ReviewIssue, RevisionInput, RoundCounters, SCENE_TYPES, SCHEMA_VERSION, SceneType, ScoreDimension, SearchReport, SearchResultItem, SemanticItem, Severity, Stage, StateDelta, StateError, StateErrorCode, StatusReport, TimelineDelta, TimelineDocument, Violation, Warning, WordMetrics, WorldDelta, WorldDocument, WorldPlanInput, WorldbookBackstory, WorldbookCharacter, WorldbookDocument, WorldbookFaction, WorldbookGlossary, WorldbookLocation, WorldbookRule, applicableGates, apply, blockedError, briefStageFor, chapterHeading, evaluateGates, faultError, gateName, inject, inputError, loadRulepack, name, parseParagraphLocation, phaseStrip, projectChapter, projectChapters, projectGates, projectSummary, renderBlurb, renderCover, renderEpubChapter, renderMarkdown, renderPlainText, renderProposal, verifyReference };
2463
+ export { ARCHIVE_DIGESTS_FILE, AdviceReport, ArchiveHandoff, AuditRow, BACKUP_DIR, BRIEF_RECEIPTS_FILE, BlockingItem, BookPlan, BookReport, BriefDocument, BriefLibrary, BriefReceipt, BriefReceiptsDocument, BriefStage, CHAPTERS_DIR, CHECK_DIR, CORE_TOOLS, ChapterCastMember, ChapterPlanInput, ChapterRecord, ChapterStatusRow, ChapterSummary, ChaptersDocument, CharacterDelta, CharactersDocument, CheckDocument, Config, DEFAULT_GENRE, DEFAULT_PLATFORM, Deduction, DeltaAction, DiffReport, DoctorCheck, DoctorReport, DraftInput, DraftState, ERROR_CODES, EXPORT_DIR, EngineBlockedError, EngineError, EngineFaultError, EngineInputError, EngineService, ErrorCode, ExportChapter, ExportKind, ForeshadowDelta, ForeshadowsDocument, GENRES, GateInput, GateOptions, GateResult, Genre, GuideReport, GuideStep, HISTORY_DIR, HISTORY_KINDS, HandoffDocument, HistoryKind, INITIAL_STAGE, LIMITS, LOCK_FILE, LocksDocument, METADATA_LOCK_FILE, MachineMetrics, NovelProject, OPERATIONS, OUTLINE_DIR, OUTLINE_FILE, OpResult, Operation, OutlineChapter, OutlineDocument, OutlineProgress, OutlineVolume, PANEL_CHAPTER_LIMIT, PANEL_ID, PANEL_ROUTE, PHASE_STRIP, PLAN_DIR, PLATFORMS, PLUGIN_NAME, PROJECT_FILE, PanelBook, PanelBundle, PanelChapterRow, PanelChaptersDto, PanelDraft, PanelGatesDto, PanelMissing, PanelProjectOption, PanelSelection, PanelSnapshot, PanelSummaryDto, PipelineState, Platform, REGISTRY_FILE, REMOTE_VERSION, REVIEW_DIR, ReadingProof, ReadingProofReference, RelationshipDelta, RelationshipsDocument, ResumePromptInput, ReviewDocument, ReviewInput, ReviewIssue, RevisionEdit, RevisionInput, RevisionPatch, RoundCounters, SCENE_TYPES, SCENE_TYPE_DEFAULT, SCHEMA_VERSION, SESSION_BINDINGS_FILE, SKILL_NAME, STAGING_DIR, STATE_DIR, STATE_FILES, SceneType, ScoreDimension, SearchReport, SearchResultItem, Selection, SelectionSource, SemanticItem, Severity, Stage, StateDelta, StateError, StateErrorCode, StatusReport, TOOL_NAMES, TimelineDelta, TimelineDocument, ToolName, Violation, WORLDBOOK_FILE, Warning, WordMetrics, WorldDelta, WorldDocument, WorldPlanInput, WorldbookBackstory, WorldbookCharacter, WorldbookDocument, WorldbookFact, WorldbookFaction, WorldbookGlossary, WorldbookLocation, WorldbookRule, applicableGates, apply, blockedError, briefStageFor, chapterFile, chapterHeading, chapterHistoryFile, checkFile, checkHistoryFile, deprecatedChapterFile, deprecatedHistoryFile, evaluateGates, faultError, formatResumePrompt, gateName, inject, inputError, loadRulepack, name, parseParagraphLocation, phaseStrip, planChapterFile, planHistoryFile, planWorldFile, projectChapter, projectChapters, projectGates, projectSummary, renderBlurb, renderCover, renderEpubChapter, renderMarkdown, renderPlainText, renderProposal, reviewFile, reviewHistoryFile, verifyReference };