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/lib/index.d.ts CHANGED
@@ -22,7 +22,7 @@ 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). */
25
+ /** All thirty-two service operations (22 inherited + 9 book-level + restoreBackup). */
26
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. */
@@ -60,11 +60,18 @@ 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";
68
+ /** The exact same keyed submission already failed in the same context. */
69
+ readonly RETRY_UNCHANGED: "ENGINE_RETRY_UNCHANGED";
65
70
  readonly GATE_BLOCKED: "ENGINE_GATE_BLOCKED";
66
71
  readonly READING_PROOF_INVALID: "ENGINE_READING_PROOF_INVALID";
67
72
  readonly SCORE_MISMATCH: "ENGINE_SCORE_MISMATCH";
73
+ /** A revision tried to rewrite reviewer-owned issue fields. */
74
+ readonly ISSUE_IMMUTABLE: "ENGINE_ISSUE_IMMUTABLE";
68
75
  readonly DELTA_UNVERIFIED: "ENGINE_DELTA_UNVERIFIED";
69
76
  readonly RESOURCE_NEGATIVE_BALANCE: "ENGINE_RESOURCE_NEGATIVE_BALANCE";
70
77
  readonly SCHEMA_TOO_NEW: "ENGINE_SCHEMA_TOO_NEW";
@@ -461,6 +468,8 @@ 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 @@ interface BookReport {
783
792
  min: number;
784
793
  max: number;
785
794
  last_five: number[];
786
- trend: number;
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 @@ interface ChapterStatusRow {
984
999
  stage: Stage;
985
1000
  passed: boolean;
986
1001
  }
1002
+ /** Where an omitted project root came from. */
1003
+ type SelectionSource = 'explicit' | 'session' | 'workspace' | 'active' | 'config' | 'single';
1004
+ /** One resolved project choice, before the project is loaded. */
1005
+ interface Selection {
1006
+ root: string;
1007
+ source: SelectionSource;
1008
+ title?: string;
1009
+ created_at?: string;
1010
+ }
987
1011
  /** The `getStatus` payload. */
988
1012
  interface StatusReport {
989
1013
  project: {
@@ -992,10 +1016,17 @@ 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 @@ 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
  interface DiffReport {
@@ -1219,6 +1254,8 @@ type DocKind = 'project' | 'pipeline' | 'chapters' | 'characters' | 'world' | 'l
1219
1254
  interface RegistryEntry {
1220
1255
  root: string;
1221
1256
  title: string;
1257
+ /** Project identity; absent on registries written by 0.2.1 and earlier. */
1258
+ created_at?: string;
1222
1259
  updated_at: string;
1223
1260
  }
1224
1261
  interface BookPatch {
@@ -1382,7 +1419,10 @@ interface PanelSnapshot {
1382
1419
  quality?: {
1383
1420
  averageScore: number;
1384
1421
  scores: number[];
1385
- trend: number;
1422
+ trend: number | null;
1423
+ trend_basis?: 'last_five_vs_previous_five' | 'insufficient_samples' | 'insufficient_history';
1424
+ samples?: number;
1425
+ attempts?: number;
1386
1426
  };
1387
1427
  health?: {
1388
1428
  ok: boolean;
@@ -1395,6 +1435,19 @@ interface PanelSnapshot {
1395
1435
  chapter: number;
1396
1436
  status: string;
1397
1437
  }>;
1438
+ /** Unacknowledged machine advisories; never mixed into `blocking`. */
1439
+ advisories?: Array<{
1440
+ code: string;
1441
+ detail: string;
1442
+ hint?: string;
1443
+ }>;
1444
+ /** Number of advisory codes already acknowledged for the current check. */
1445
+ acknowledgedAdvisories?: number;
1446
+ /** Read-only upgrade findings; null when the project needs no explanation. */
1447
+ legacy?: Array<{
1448
+ code: string;
1449
+ detail: string;
1450
+ }>;
1398
1451
  }
1399
1452
  interface PanelBook {
1400
1453
  premise: string;
@@ -1434,6 +1487,8 @@ interface PanelBook {
1434
1487
  interface PanelSummaryDto {
1435
1488
  schemaVersion: number;
1436
1489
  panelId: string;
1490
+ /** Absolute project root, so a stale title cannot hide a wrong book. */
1491
+ root: string;
1437
1492
  title: string;
1438
1493
  genre: string;
1439
1494
  platform: string;
@@ -1465,7 +1520,10 @@ interface PanelSummaryDto {
1465
1520
  quality?: {
1466
1521
  averageScore: number;
1467
1522
  scores: number[];
1468
- trend: number;
1523
+ trend: number | null;
1524
+ trend_basis?: 'last_five_vs_previous_five' | 'insufficient_samples' | 'insufficient_history';
1525
+ samples?: number;
1526
+ attempts?: number;
1469
1527
  };
1470
1528
  health?: {
1471
1529
  ok: boolean;
@@ -1478,6 +1536,19 @@ interface PanelSummaryDto {
1478
1536
  chapter: number;
1479
1537
  status: string;
1480
1538
  }>;
1539
+ /** Unacknowledged machine advisories; never mixed into `blocking`. */
1540
+ advisories?: Array<{
1541
+ code: string;
1542
+ detail: string;
1543
+ hint?: string;
1544
+ }>;
1545
+ /** Number of advisory codes already acknowledged for the current check. */
1546
+ acknowledgedAdvisories?: number;
1547
+ /** Read-only upgrade findings; null when the project needs no explanation. */
1548
+ legacy?: Array<{
1549
+ code: string;
1550
+ detail: string;
1551
+ }>;
1481
1552
  }
1482
1553
  interface PanelChapterRow {
1483
1554
  num: number;
@@ -1502,6 +1573,49 @@ interface PanelGatesDto {
1502
1573
  /** Human-facing one-liner per blocking item. */
1503
1574
  explanations: string[];
1504
1575
  }
1576
+ /** One selectable project in the panel; state is cheap, not a full load. */
1577
+ interface PanelProjectOption {
1578
+ root: string;
1579
+ title: string;
1580
+ active: boolean;
1581
+ registered: boolean;
1582
+ state: 'ok' | 'damaged' | 'missing' | 'not-project' | 'unreadable';
1583
+ /** The path now holds a different project identity; writes will fail closed. */
1584
+ identity_changed?: boolean;
1585
+ }
1586
+ interface PanelSelection {
1587
+ root: string | null;
1588
+ source: 'query' | 'active' | 'single' | 'config' | 'none';
1589
+ }
1590
+ interface PanelMissing {
1591
+ root: string;
1592
+ title: string;
1593
+ code: 'ENGINE_PROJECT_MISSING' | 'ENGINE_PROJECT_NOT_FOUND' | 'ENGINE_TXN_IN_PROGRESS';
1594
+ reason: 'missing' | 'not-project' | 'damaged' | 'not_registered' | 'unreadable' | 'busy';
1595
+ hint: string;
1596
+ }
1597
+ interface PanelBundle {
1598
+ projects: PanelProjectOption[];
1599
+ selection: PanelSelection;
1600
+ summary?: PanelSummaryDto;
1601
+ chapters?: PanelChaptersDto;
1602
+ gates?: PanelGatesDto;
1603
+ resume?: {
1604
+ text: string;
1605
+ };
1606
+ missing?: PanelMissing;
1607
+ empty?: true;
1608
+ error?: string;
1609
+ }
1610
+ interface ResumePromptInput {
1611
+ title: string;
1612
+ root: string;
1613
+ chapter: number | string;
1614
+ stage: string;
1615
+ nextTool: string;
1616
+ }
1617
+ /** One shared prompt template for `/novel resume` and the panel copy button. */
1618
+ declare function formatResumePrompt(input: ResumePromptInput): string;
1505
1619
  /** The nine-phase progress strip: phases before the active one are done. */
1506
1620
  declare function phaseStrip(stage: Stage, finished?: boolean): Array<{
1507
1621
  id: string;
@@ -1513,6 +1627,15 @@ declare function projectChapter(record: ChapterRecord): PanelChapterRow;
1513
1627
  declare function projectSummary(snapshot: PanelSnapshot): PanelSummaryDto;
1514
1628
  declare function projectChapters(snapshot: PanelSnapshot): PanelChaptersDto;
1515
1629
  declare function projectGates(snapshot: PanelSnapshot): PanelGatesDto;
1630
+ /** How much of a candidate project can be inspected without loading a snapshot. */
1631
+ type ProjectState = 'ok' | 'damaged' | 'not-project' | 'missing' | 'unreadable';
1632
+ interface ProjectInspection {
1633
+ root: string;
1634
+ state: ProjectState;
1635
+ title: string;
1636
+ created_at?: string;
1637
+ error?: string;
1638
+ }
1516
1639
  /** Plugin configuration, mapped from `Config` in `src/index.ts`. */
1517
1640
  interface EngineConfig {
1518
1641
  projectRoot: string;
@@ -1623,6 +1746,16 @@ interface BriefInput {
1623
1746
  chapter?: number;
1624
1747
  root?: string;
1625
1748
  }
1749
+ type BriefPayload = {
1750
+ stage: BriefStage;
1751
+ chapter: number;
1752
+ digest: string;
1753
+ title: string;
1754
+ content: string;
1755
+ content_hash: string;
1756
+ fields: Record<string, unknown>;
1757
+ context: Record<string, unknown>;
1758
+ };
1626
1759
  interface WorldPlanSubmission extends WorldPlanInput {
1627
1760
  root?: string;
1628
1761
  }
@@ -1759,6 +1892,11 @@ interface ReportSubmission {
1759
1892
  interface GuideInput {
1760
1893
  root?: string;
1761
1894
  }
1895
+ /** Session/workspace context of one tool or command call. */
1896
+ interface EngineCaller {
1897
+ sessionId?: string;
1898
+ cwd?: string;
1899
+ }
1762
1900
  /**
1763
1901
  * The single write authority. Tools, commands, the panel and task books never
1764
1902
  * touch a file; every mutation is one of these operations.
@@ -1772,8 +1910,8 @@ declare class EngineService {
1772
1910
  private readonly briefContextCache;
1773
1911
  private readonly briefs;
1774
1912
  private readonly now;
1775
- /** The project this process last touched; the panel follows it without a workspace hint. */
1776
- private lastRoot;
1913
+ private readonly metadataMutex;
1914
+ private readonly callerContext;
1777
1915
  constructor(config: EngineConfig, rulepack: Rulepack | (() => Rulepack), options?: {
1778
1916
  briefs?: BriefLibrary;
1779
1917
  now?: () => Date;
@@ -1794,15 +1932,52 @@ declare class EngineService {
1794
1932
  private readonly signalContext;
1795
1933
  /** Bind the host cancellation signal to the engine work of one tool call. */
1796
1934
  withSignal<T>(signal: AbortSignal | undefined, task: () => Promise<T>): Promise<T>;
1935
+ /** Bind the calling Agent session and cwd to every engine operation in `task`. */
1936
+ withCaller<T>(caller: EngineCaller | undefined, task: () => Promise<T>): Promise<T>;
1937
+ private caller;
1797
1938
  /** The configured default project root. */
1798
1939
  defaultRoot(): string;
1940
+ /** Resolve a caller-supplied root against the session cwd, like /novel use. */
1941
+ private resolveProjectPath;
1942
+ /** The resolved metadata directory; resolves relative config once per call. */
1943
+ private stateRoot;
1944
+ private stateStore;
1945
+ private bindings;
1799
1946
  /**
1800
- * Resolve an omitted root through the cross-project registry. The registry is
1801
- * convenience state, so a corrupt or unreadable registry falls back to the
1802
- * configured root instead of making every call fail.
1947
+ * Serialize metadata read-modify-write across sessions and processes. The
1948
+ * project manuscript lock is a different lock; a slow write never blocks a
1949
+ * registry update.
1803
1950
  */
1951
+ withMetadataLock<T>(task: () => Promise<T>): Promise<T>;
1952
+ private inspectConfigRoot;
1953
+ /**
1954
+ * Resolve the four-layer choice: explicit, session binding, bounded workspace
1955
+ * discovery, then registry/config defaults. There is deliberately no
1956
+ * process-global "last project" layer.
1957
+ */
1958
+ /** Doctor/guide may run before any project exists; that is not a failure. */
1959
+ private resolveRootForInspection;
1804
1960
  private resolveRoot;
1805
1961
  private storeFor;
1962
+ /** Persist one explicit choice for this DSH session. */
1963
+ private bindSelection;
1964
+ /**
1965
+ * Best-effort discovery bookkeeping. A metadata failure must never turn a
1966
+ * successful project read/write into a failure; the registry is convenience.
1967
+ */
1968
+ private autoRegister;
1969
+ /** Record the session binding/registration implied by a successful selection. */
1970
+ private recordSelection;
1971
+ /**
1972
+ * Read paths may record convenience metadata, but a corrupt or read-only
1973
+ * state directory must not turn a successful explicit-root read into a
1974
+ * failure. Resolution without an explicit root still reads the binding
1975
+ * fail-closed; writes use recordSelection directly and report a committed
1976
+ * mutation with a warning if its metadata update fails.
1977
+ */
1978
+ private recordSelectionBestEffort;
1979
+ /** Registration/activation/use need a readable project identity. */
1980
+ private assertProjectHealthy;
1806
1981
  initProject(input: InitProjectInput): Promise<OpResult<{
1807
1982
  root: string;
1808
1983
  title: string;
@@ -1858,6 +2033,13 @@ declare class EngineService {
1858
2033
  * the model only learns about the mismatch when that write aborts.
1859
2034
  */
1860
2035
  private journalBlocking;
2036
+ /**
2037
+ * Count review attempts per archived chapter. The journal is authoritative;
2038
+ * history fills any per-chapter gap where it has more distinct historical
2039
+ * reviews than committed journal rows (legacy or a truncated upgrade). A
2040
+ * chapter can never report fewer attempts than readable current reviews.
2041
+ */
2042
+ private reviewAttemptsByChapter;
1861
2043
  /**
1862
2044
  * Count every committed attempt for one chapter, not just the loop since the
1863
2045
  * last rollback. `pipeline.rounds` is deliberately reset by rollback to keep
@@ -1865,6 +2047,12 @@ declare class EngineService {
1865
2047
  */
1866
2048
  private lifetimeRounds;
1867
2049
  getStatus(input?: StatusInput): Promise<OpResult<StatusReport>>;
2050
+ /**
2051
+ * Build a status report from an already-loaded snapshot. This deliberately
2052
+ * has no selection side effect: read-only callers such as /novel resume must
2053
+ * not register or bind the project just by reading it.
2054
+ */
2055
+ private statusFor;
1868
2056
  countWords(input?: WordCountInput): Promise<OpResult<WordMetrics>>;
1869
2057
  queryState(input: StateQueryInput): Promise<OpResult<unknown>>;
1870
2058
  advise(input?: {
@@ -1879,20 +2067,18 @@ declare class EngineService {
1879
2067
  state_errors: StateError[];
1880
2068
  }>>;
1881
2069
  search(input: SearchInput): Promise<OpResult<SearchReport>>;
2070
+ /**
2071
+ * Read-only upgrade audit. It never edits a legacy document: the point is to
2072
+ * explain why an old project's numbers look different, not to rewrite history.
2073
+ */
2074
+ private legacyIssues;
1882
2075
  doctor(input?: DoctorInput): Promise<OpResult<DoctorReport>>;
1883
2076
  private recoveryForSafe;
1884
2077
  report(input?: ReportSubmission): Promise<OpResult<BookReport>>;
1885
2078
  guide(input?: GuideInput): Promise<OpResult<GuideReport>>;
1886
- getBrief(input: BriefInput): Promise<OpResult<{
1887
- stage: string;
1888
- chapter: number;
1889
- digest: string;
1890
- title: string;
1891
- content: string;
1892
- content_hash: string;
1893
- fields: Record<string, unknown>;
1894
- context: Record<string, unknown>;
1895
- }>>;
2079
+ getBrief(input: BriefInput): Promise<OpResult<BriefPayload>>;
2080
+ private assertBriefChapter;
2081
+ private briefPayload;
1896
2082
  submitWorldPlan(input: WorldPlanSubmission): Promise<OpResult<{
1897
2083
  chapter: number;
1898
2084
  stage: Stage;
@@ -1960,14 +2146,21 @@ declare class EngineService {
1960
2146
  panelGates(input?: {
1961
2147
  root?: string;
1962
2148
  }): Promise<PanelGatesDto>;
1963
- /** One snapshot, three projections: the panel never mixes revisions. */
2149
+ /**
2150
+ * Build the read-only panel payload. The query root is accepted only when it
2151
+ * is registered, active or the configured root; arbitrary paths never reach
2152
+ * the filesystem from the panel route.
2153
+ */
1964
2154
  panelBundle(input?: {
1965
2155
  root?: string;
1966
- }): Promise<{
1967
- summary: PanelSummaryDto;
1968
- chapters: PanelChaptersDto;
1969
- gates: PanelGatesDto;
1970
- }>;
2156
+ }): Promise<PanelBundle>;
2157
+ /** Read-only resume text shared with the panel copy button. */
2158
+ resumePrompt(input?: {
2159
+ root?: string;
2160
+ }): Promise<OpResult<{
2161
+ text: string | null;
2162
+ selection: StatusReport['selection'];
2163
+ }>>;
1971
2164
  stats(input?: {
1972
2165
  root?: string;
1973
2166
  }): Promise<{
@@ -1989,31 +2182,97 @@ declare class EngineService {
1989
2182
  targetChapters: number | null;
1990
2183
  }>;
1991
2184
  private registry;
1992
- /** List known projects (convenience only; a project opens without a row). */
2185
+ /** List known projects with a cheap directory state for the panel/command. */
1993
2186
  listProjects(): Promise<{
1994
2187
  active: string | null;
1995
- projects: RegistryEntry[];
2188
+ projects: Array<RegistryEntry & {
2189
+ state: ProjectState;
2190
+ identity_changed?: boolean;
2191
+ }>;
1996
2192
  }>;
1997
- /** Record the current project in the registry. */
2193
+ /**
2194
+ * Record an explicit project row. An unchanged duplicate is a no-op; a path
2195
+ * reused by another project updates the row and reports `replaced`.
2196
+ */
1998
2197
  registerProject(input?: {
1999
2198
  root?: string;
2000
2199
  }): Promise<{
2001
2200
  active: string | null;
2002
2201
  projects: RegistryEntry[];
2202
+ replaced: boolean;
2003
2203
  }>;
2004
- /** Choose the default project for future convenience calls. */
2204
+ /** Choose the panel/default project. This never binds the current session. */
2005
2205
  setActiveProject(input: {
2006
2206
  root: string;
2007
2207
  }): Promise<{
2008
2208
  active: string | null;
2209
+ replaced: boolean;
2009
2210
  }>;
2010
- /** Forget a registry row; the project directory is never touched. */
2211
+ /** Forget a registry row; the project directory and bindings are never touched. */
2011
2212
  forgetProject(input: {
2012
2213
  root: string;
2013
2214
  }): Promise<{
2014
2215
  active: string | null;
2015
2216
  projects: RegistryEntry[];
2016
2217
  }>;
2218
+ /**
2219
+ * Remove only registry rows whose directories are missing, not a project, or
2220
+ * unreadable. The default is a dry run; project files and session bindings are
2221
+ * never touched, and `--yes` is required at the command layer.
2222
+ */
2223
+ pruneProjects(input?: {
2224
+ apply?: boolean;
2225
+ }): Promise<{
2226
+ candidates: Array<{
2227
+ root: string;
2228
+ title: string;
2229
+ state: ProjectState;
2230
+ }>;
2231
+ removed: string[];
2232
+ active: string | null;
2233
+ }>;
2234
+ /**
2235
+ * Bounded workspace scan. Normal mode is read-only; `register` updates only
2236
+ * healthy, newly discovered rows, never active or session bindings.
2237
+ */
2238
+ scanProjects(input: {
2239
+ cwd: string;
2240
+ register?: boolean;
2241
+ }): Promise<{
2242
+ cwd: string;
2243
+ candidates: Array<ProjectInspection & {
2244
+ registered: boolean;
2245
+ identity_changed?: boolean;
2246
+ reason?: string;
2247
+ }>;
2248
+ truncated: boolean;
2249
+ added: string[];
2250
+ already: string[];
2251
+ skipped: Array<{
2252
+ root: string;
2253
+ reason: string;
2254
+ }>;
2255
+ identity_changed: string[];
2256
+ }>;
2257
+ /** Current session binding, or the inferred fallback when none is set. */
2258
+ currentProject(): Promise<{
2259
+ selection: Selection | null;
2260
+ bound: boolean;
2261
+ state?: ProjectState;
2262
+ error?: string;
2263
+ }>;
2264
+ /** Explicitly switch this session; failure leaves the old binding untouched. */
2265
+ useProject(input: {
2266
+ target: string;
2267
+ }): Promise<{
2268
+ selection: Selection;
2269
+ title: string;
2270
+ created_at: string;
2271
+ }>;
2272
+ /** Clear only this session's choice; never touches a project or registry row. */
2273
+ unbindProject(): Promise<{
2274
+ cleared: boolean;
2275
+ }>;
2017
2276
  /**
2018
2277
  * Import one ledger from a settings pack. Always previews with a diff; a
2019
2278
  * dry run never writes, and an apply is a normal transaction.
@@ -2071,10 +2330,10 @@ declare class EngineService {
2071
2330
  private bookStatus;
2072
2331
  private panelSnapshot;
2073
2332
  /**
2074
- * The archive stage has only one legal operation, so a status/panel that
2075
- * recommends it without checking the same gates as the archive creates a
2076
- * loop. Evaluate the real archive gates through `gateInput`, then add the
2077
- * machine pass and semantic evidence that archive re-checks separately.
2333
+ * The archive stage can be left by a direct revision (the 0.2.3 workflow) or
2334
+ * by rollback. Evaluate the same gates as archive so status/panel do not
2335
+ * recommend an operation that will immediately fail; when the failure is
2336
+ * prose-level, `nextStep` recommends the direct P5 revision instead.
2078
2337
  */
2079
2338
  private archivePreflight;
2080
2339
  private nextStep;
@@ -2100,6 +2359,7 @@ declare class EngineService {
2100
2359
  private stateGateBlock;
2101
2360
  /** Whether the stored brief still signs the current snapshot. */
2102
2361
  private readBriefReceipts;
2362
+ private assertBriefReceiptsWritable;
2103
2363
  private briefIsCurrent;
2104
2364
  /** Arguments valid for the tool `STAGE_NEXT` recommends at this stage. */
2105
2365
  private nextArgs;
@@ -2132,11 +2392,13 @@ declare class EngineService {
2132
2392
  private assertGates;
2133
2393
  private assertChapterParameter;
2134
2394
  private assertChapter;
2135
- /** True when anything survives in the project directory besides novel.yaml. */
2136
- private projectStateExists;
2137
2395
  /** A live writer lock makes a read-only snapshot potentially torn. */
2138
2396
  private lockIsLive;
2397
+ /** Resolve a selection, load it, then persist the explicit choice (best effort). */
2139
2398
  private load;
2399
+ /** Load exactly one already-resolved selection; never writes a binding. */
2400
+ private movedProjectHint;
2401
+ private loadAt;
2140
2402
  /** A repair may bypass the journal hash only when the document is unreadable. */
2141
2403
  private stateDocIsUnreadable;
2142
2404
  /** Read a review/check without letting one corrupt file wedge every read. */
@@ -2147,6 +2409,100 @@ declare class EngineService {
2147
2409
  }
2148
2410
  /** Read, compile and freeze a pack. Runtime callers always use the bundled default. */
2149
2411
  declare function loadRulepack(path?: string): Rulepack;
2412
+ /** Panel and session identifiers. */
2413
+ declare const PLUGIN_NAME = "dsh-novel-write-engine";
2414
+ declare const PANEL_ID = "novel-workshop";
2415
+ declare const PANEL_ROUTE = "/api/novel-write-engine/panel";
2416
+ declare const SKILL_NAME = "novel-write-engine";
2417
+ declare const REMOTE_VERSION = 5;
2418
+ /** The panel is a recent-history view; the wire payload and UI use one cap. */
2419
+ declare const PANEL_CHAPTER_LIMIT = 30;
2420
+ /** Project-relative locations. Every write goes through these constants. */
2421
+ declare const PROJECT_FILE = "novel.yaml";
2422
+ declare const OUTLINE_FILE = "outline/outline.yaml";
2423
+ declare const WORLDBOOK_FILE = "outline/worldbook.yaml";
2424
+ declare const STATE_DIR = "state";
2425
+ declare const CHAPTERS_DIR = "chapters";
2426
+ declare const PLAN_DIR = "plans";
2427
+ declare const REVIEW_DIR = "reviews";
2428
+ declare const CHECK_DIR = "checks";
2429
+ declare const EXPORT_DIR = "exports";
2430
+ declare const BACKUP_DIR = "backups";
2431
+ declare const HISTORY_DIR = "history";
2432
+ /** Brief receipts are append-only evidence, separate from content transactions. */
2433
+ declare const BRIEF_RECEIPTS_FILE = "state/briefs.yaml";
2434
+ declare const OUTLINE_DIR = "outline";
2435
+ declare const LOCK_FILE = ".engine.lock";
2436
+ declare const STAGING_DIR = ".staging";
2437
+ declare const REGISTRY_FILE = "projects.yaml";
2438
+ declare const SESSION_BINDINGS_FILE = "session-bindings.yaml";
2439
+ declare const METADATA_LOCK_FILE = ".metadata.lock";
2440
+ declare const ARCHIVE_DIGESTS_FILE = "state/archive-digests.json";
2441
+ /** Files owned by the engine under `state/`. */
2442
+ declare const STATE_FILES: {
2443
+ readonly pipeline: "pipeline.yaml";
2444
+ readonly chapters: "chapters.yaml";
2445
+ readonly characters: "characters.yaml";
2446
+ readonly world: "world.yaml";
2447
+ readonly locks: "locks.yaml";
2448
+ readonly timeline: "timeline.yaml";
2449
+ readonly foreshadows: "foreshadows.yaml";
2450
+ readonly relationships: "relationships.yaml";
2451
+ readonly handoff: "handoff.yaml";
2452
+ readonly journal: "journal.jsonl";
2453
+ };
2454
+ /** Chapter file name, zero padded to three digits. */
2455
+ declare const chapterFile: (chapter: number) => string;
2456
+ /** Kept for reference when a rollback discards the current draft. */
2457
+ declare const deprecatedChapterFile: (chapter: number) => string;
2458
+ declare const planWorldFile: (chapter: number) => string;
2459
+ declare const planChapterFile: (chapter: number) => string;
2460
+ declare const reviewFile: (chapter: number) => string;
2461
+ declare const checkFile: (chapter: number) => string;
2462
+ declare const reviewHistoryFile: (chapter: number, revision: number) => string;
2463
+ declare const checkHistoryFile: (chapter: number, revision: number) => string;
2464
+ declare const chapterHistoryFile: (chapter: number, revision: number) => string;
2465
+ declare const deprecatedHistoryFile: (chapter: number, revision: number) => string;
2466
+ declare const planHistoryFile: (chapter: number, revision: number) => string;
2467
+ declare const HISTORY_KINDS: readonly ["reviews", "checks", "chapters", "deprecated", "plans"];
2468
+ type HistoryKind = (typeof HISTORY_KINDS)[number];
2469
+ /** Limits that are structural, not taste. */
2470
+ declare const LIMITS: {
2471
+ /** P4 <-> P5/P6 retries before a forced rollback. */
2472
+ maxRounds: number;
2473
+ /** Lock staleness in milliseconds. */
2474
+ lockStaleMs: number;
2475
+ /** How long a writer waits for the keyed mutex before failing. */
2476
+ lockWaitMs: number;
2477
+ /** Reading proofs shorter than this cannot be verified. */
2478
+ minQuoteChars: number;
2479
+ /** World-plan text fields are protected context, so they need a hard bound. */
2480
+ maxWorldPlanItems: number;
2481
+ maxWorldPlanTextChars: number;
2482
+ maxWorldPlanObjectiveChars: number;
2483
+ /** Cast plus summary may exceed eight, but not without a bound. */
2484
+ maxActiveCast: number;
2485
+ /** Outline and worldbook are protected context, so they need hard bounds. */
2486
+ maxOutlineChapters: number;
2487
+ maxOutlineVolumes: number;
2488
+ maxOutlineTextFieldChars: number;
2489
+ maxOutlineNotesChars: number;
2490
+ maxWorldbookEntries: number;
2491
+ maxWorldbookTextChars: number;
2492
+ maxSearchQueryChars: number;
2493
+ };
2494
+ /** Re-exported enums make adapter code readable without importing types twice. */
2495
+ declare const SCENE_TYPE_DEFAULT: SceneType;
2496
+ declare const DEFAULT_GENRE: Genre;
2497
+ declare const DEFAULT_PLATFORM: Platform;
2498
+ declare const INITIAL_STAGE: Stage;
2499
+ /** The order used to render the nine-phase progress strip. */
2500
+ declare const PHASE_STRIP: readonly ["P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9"];
2501
+ /** Tool names, kept here so the arch test can assert one namespace. */
2502
+ 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"];
2503
+ type ToolName = (typeof TOOL_NAMES)[number];
2504
+ /** The ten high-frequency tools kept when `tools.mode = core`. */
2505
+ declare const CORE_TOOLS: readonly ToolName[];
2150
2506
  /** One rendered chapter, assembled by the engine before rendering. */
2151
2507
  interface ExportChapter {
2152
2508
  num: number;
@@ -2189,4 +2545,4 @@ declare const Config: Schema<Config>;
2189
2545
  * created lazily per project root.
2190
2546
  */
2191
2547
  declare function apply(ctx: Context, config: Config): void;
2192
- export { AdviceReport, ArchiveHandoff, AuditRow, BlockingItem, BookPlan, BookReport, BriefDocument, BriefLibrary, BriefReceipt, BriefReceiptsDocument, BriefStage, ChapterCastMember, ChapterPlanInput, ChapterRecord, ChapterStatusRow, ChapterSummary, ChaptersDocument, CharacterDelta, CharactersDocument, CheckDocument, Config, Deduction, DeltaAction, DiffReport, DoctorCheck, DoctorReport, DraftInput, DraftState, 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, OutlineProgress, OutlineVolume, PLATFORMS, PanelBook, PanelChapterRow, PanelChaptersDto, PanelDraft, PanelGatesDto, PanelSnapshot, PanelSummaryDto, PipelineState, Platform, ReadingProof, ReadingProofReference, RelationshipDelta, RelationshipsDocument, ReviewDocument, ReviewInput, ReviewIssue, RevisionEdit, RevisionInput, RevisionPatch, 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, WorldbookFact, 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 };
2548
+ 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 };