dsh-openwrite 0.2.8

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.
Files changed (275) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +107 -0
  3. package/THIRD_PARTY_NOTICES.md +33 -0
  4. package/assets/logo-dark.svg +31 -0
  5. package/assets/logo-light.svg +31 -0
  6. package/cordis.patch.yml +11 -0
  7. package/docs/BENCHMARK_TASKS.md +54 -0
  8. package/docs/COST_MEASUREMENT.md +39 -0
  9. package/docs/DEVELOPMENT.md +65 -0
  10. package/docs/DSH_STORE.md +58 -0
  11. package/docs/FRAMEWORK_ADVANTAGES.md +288 -0
  12. package/docs/IMPROVEMENT_PLAN.md +252 -0
  13. package/docs/INSTALL.md +99 -0
  14. package/docs/LEARNING.md +68 -0
  15. package/docs/OPEN_SOURCE_NOVEL_AUDIT.md +191 -0
  16. package/docs/PLUGIN_MAINTENANCE.md +54 -0
  17. package/docs/PLUGIN_MAINTENANCE_RC7.md +157 -0
  18. package/docs/QA_20260908.md +103 -0
  19. package/docs/QA_20260909_BENCHMARK.md +24 -0
  20. package/docs/RELEASE_ACCEPTANCE.md +116 -0
  21. package/docs/REVIEW_DAG_FRAMEWORK.md +82 -0
  22. package/docs/TARGETED_NOVEL_PROJECT_REVIEW.md +167 -0
  23. package/docs/WORKFLOWS.md +74 -0
  24. package/docs/WORKSPACE_CONTEXT_CONTRACT.md +125 -0
  25. package/docs/WORKSPACE_INTEGRATION_GOAL.md +278 -0
  26. package/docs/research/autosave-race-repro.mjs +133 -0
  27. package/docs/research/framework-stats-2026-09-05.json +116 -0
  28. package/dog-tools.mjs +11 -0
  29. package/package.json +132 -0
  30. package/packages/openwrite-bridge/LICENSE +201 -0
  31. package/packages/openwrite-bridge/cordis.patch.yml +10 -0
  32. package/packages/openwrite-bridge/lib/client.d.ts +95 -0
  33. package/packages/openwrite-bridge/lib/client.js +193 -0
  34. package/packages/openwrite-bridge/lib/contracts-generated.d.ts +367 -0
  35. package/packages/openwrite-bridge/lib/contracts-generated.js +1764 -0
  36. package/packages/openwrite-bridge/lib/dog-delivery.d.ts +12 -0
  37. package/packages/openwrite-bridge/lib/dog-delivery.js +362 -0
  38. package/packages/openwrite-bridge/lib/dog-review.d.ts +50 -0
  39. package/packages/openwrite-bridge/lib/dog-review.js +470 -0
  40. package/packages/openwrite-bridge/lib/domain.d.ts +70 -0
  41. package/packages/openwrite-bridge/lib/domain.js +496 -0
  42. package/packages/openwrite-bridge/lib/index.d.ts +17 -0
  43. package/packages/openwrite-bridge/lib/index.js +89 -0
  44. package/packages/openwrite-bridge/lib/managed-runtime.d.ts +95 -0
  45. package/packages/openwrite-bridge/lib/managed-runtime.js +510 -0
  46. package/packages/openwrite-bridge/lib/preset-tools.d.ts +13 -0
  47. package/packages/openwrite-bridge/lib/preset-tools.js +18 -0
  48. package/packages/openwrite-bridge/lib/tool-policy.d.ts +6 -0
  49. package/packages/openwrite-bridge/lib/tool-policy.js +56 -0
  50. package/packages/openwrite-bridge/lib/tools.d.ts +24 -0
  51. package/packages/openwrite-bridge/lib/tools.js +2404 -0
  52. package/packages/openwrite-bridge/package.json +67 -0
  53. package/packages/studio-panel/LICENSE +21 -0
  54. package/packages/studio-panel/README.md +51 -0
  55. package/packages/studio-panel/cordis.patch.yml +5 -0
  56. package/packages/studio-panel/lib/client.js +158087 -0
  57. package/packages/studio-panel/lib/client.js.map +1 -0
  58. package/packages/studio-panel/lib/index.js +50 -0
  59. package/packages/studio-panel/lib/types/client/AssetEditor.d.ts +122 -0
  60. package/packages/studio-panel/lib/types/client/AssetsView.d.ts +43 -0
  61. package/packages/studio-panel/lib/types/client/BenchmarkView.d.ts +9 -0
  62. package/packages/studio-panel/lib/types/client/ContinuousReader.d.ts +13 -0
  63. package/packages/studio-panel/lib/types/client/CreationView.d.ts +6 -0
  64. package/packages/studio-panel/lib/types/client/DagCanvas.d.ts +11 -0
  65. package/packages/studio-panel/lib/types/client/DiscardDraftDialog.d.ts +8 -0
  66. package/packages/studio-panel/lib/types/client/DomainToolCard.d.ts +9 -0
  67. package/packages/studio-panel/lib/types/client/GraphView.d.ts +46 -0
  68. package/packages/studio-panel/lib/types/client/HeaderChrome.d.ts +18 -0
  69. package/packages/studio-panel/lib/types/client/LaunchOpenWrite.d.ts +13 -0
  70. package/packages/studio-panel/lib/types/client/LibraryView.d.ts +6 -0
  71. package/packages/studio-panel/lib/types/client/MarkdownText.d.ts +3 -0
  72. package/packages/studio-panel/lib/types/client/ModelView.d.ts +9 -0
  73. package/packages/studio-panel/lib/types/client/OperationsView.d.ts +6 -0
  74. package/packages/studio-panel/lib/types/client/OutlinePlans.d.ts +37 -0
  75. package/packages/studio-panel/lib/types/client/OutlineView.d.ts +29 -0
  76. package/packages/studio-panel/lib/types/client/ResearchView.d.ts +7 -0
  77. package/packages/studio-panel/lib/types/client/ReviewCard.d.ts +28 -0
  78. package/packages/studio-panel/lib/types/client/SceneWorkbench.d.ts +55 -0
  79. package/packages/studio-panel/lib/types/client/SearchView.d.ts +33 -0
  80. package/packages/studio-panel/lib/types/client/TasksView.d.ts +11 -0
  81. package/packages/studio-panel/lib/types/client/TransferWorkspaces.d.ts +19 -0
  82. package/packages/studio-panel/lib/types/client/TurnMutationSummary.d.ts +62 -0
  83. package/packages/studio-panel/lib/types/client/VditorBody.d.ts +55 -0
  84. package/packages/studio-panel/lib/types/client/WorkbenchStore.d.ts +121 -0
  85. package/packages/studio-panel/lib/types/client/api.d.ts +72 -0
  86. package/packages/studio-panel/lib/types/client/asset-drafts.d.ts +20 -0
  87. package/packages/studio-panel/lib/types/client/benchmark-options.d.ts +43 -0
  88. package/packages/studio-panel/lib/types/client/draft-store.d.ts +26 -0
  89. package/packages/studio-panel/lib/types/client/dto.d.ts +517 -0
  90. package/packages/studio-panel/lib/types/client/index.d.ts +4 -0
  91. package/packages/studio-panel/lib/types/client/locales.d.ts +15 -0
  92. package/packages/studio-panel/lib/types/client/manuscript-annotations.d.ts +59 -0
  93. package/packages/studio-panel/lib/types/client/manuscript-markers.d.ts +61 -0
  94. package/packages/studio-panel/lib/types/client/manuscript-mentions.d.ts +30 -0
  95. package/packages/studio-panel/lib/types/client/manuscript-overlay-dom.d.ts +7 -0
  96. package/packages/studio-panel/lib/types/client/manuscript-overlay.d.ts +42 -0
  97. package/packages/studio-panel/lib/types/client/manuscript-selection.d.ts +59 -0
  98. package/packages/studio-panel/lib/types/client/model-view-memory.d.ts +17 -0
  99. package/packages/studio-panel/lib/types/client/storage.d.ts +8 -0
  100. package/packages/studio-panel/lib/types/client/workbench-epochs.d.ts +16 -0
  101. package/packages/studio-panel/lib/types/client/workspace-context.d.ts +52 -0
  102. package/packages/studio-panel/lib/types/client/writing-scope.d.ts +7 -0
  103. package/packages/studio-panel/lib/types/index.d.ts +5 -0
  104. package/packages/studio-panel/package.json +105 -0
  105. package/packages/studio-panel/vendor/vditor/LICENSE +21 -0
  106. package/packages/studio-panel/vendor/vditor/dist/css/content-theme/dark.css +123 -0
  107. package/packages/studio-panel/vendor/vditor/dist/css/content-theme/light.css +101 -0
  108. package/packages/studio-panel/vendor/vditor/dist/index.css +1823 -0
  109. package/packages/studio-panel/vendor/vditor/dist/index.min.js +1 -0
  110. package/packages/studio-panel/vendor/vditor/dist/js/i18n/zh_CN.js +86 -0
  111. package/packages/studio-panel/vendor/vditor/dist/js/icons/ant.js +174 -0
  112. package/packages/studio-panel/vendor/vditor/dist/js/lute/lute.min.js +72 -0
  113. package/plugin.d.ts +3 -0
  114. package/plugin.mjs +80 -0
  115. package/presets/README.md +32 -0
  116. package/presets/openwrite/agent.cordis.yml +317 -0
  117. package/presets/openwrite/preset.yml +3 -0
  118. package/presets/openwrite/skills/canon/SKILL.md +15 -0
  119. package/presets/openwrite/skills/dialoguequality/SKILL.md +166 -0
  120. package/presets/openwrite/skills/dog-delivery-query/SKILL.md +40 -0
  121. package/presets/openwrite/skills/dog-import-query/SKILL.md +27 -0
  122. package/presets/openwrite/skills/dog-review-query/SKILL.md +40 -0
  123. package/presets/openwrite/skills/export-book/SKILL.md +14 -0
  124. package/presets/openwrite/skills/foreshadow/SKILL.md +14 -0
  125. package/presets/openwrite/skills/foreshadowing-system/SKILL.md +125 -0
  126. package/presets/openwrite/skills/learn/SKILL.md +24 -0
  127. package/presets/openwrite/skills/novel-creator/SKILL.md +88 -0
  128. package/presets/openwrite/skills/novel-creator/templates/beat_templates.yaml +179 -0
  129. package/presets/openwrite/skills/novel-creator/templates/example_usage.yaml +74 -0
  130. package/presets/openwrite/skills/novel-reviewer/SKILL.md +85 -0
  131. package/presets/openwrite/skills/oh-story-deslop/LICENSE +21 -0
  132. package/presets/openwrite/skills/oh-story-deslop/SKILL.md +36 -0
  133. package/presets/openwrite/skills/oh-story-deslop/agents/openai.yaml +4 -0
  134. package/presets/openwrite/skills/oh-story-deslop/references/method.md +22 -0
  135. package/presets/openwrite/skills/oh-story-long-analyze/LICENSE +21 -0
  136. package/presets/openwrite/skills/oh-story-long-analyze/SKILL.md +37 -0
  137. package/presets/openwrite/skills/oh-story-long-analyze/agents/openai.yaml +4 -0
  138. package/presets/openwrite/skills/oh-story-long-analyze/references/method.md +22 -0
  139. package/presets/openwrite/skills/oh-story-long-scan/LICENSE +21 -0
  140. package/presets/openwrite/skills/oh-story-long-scan/SKILL.md +36 -0
  141. package/presets/openwrite/skills/oh-story-long-scan/agents/openai.yaml +4 -0
  142. package/presets/openwrite/skills/oh-story-long-scan/references/method.md +21 -0
  143. package/presets/openwrite/skills/oh-story-long-write/LICENSE +21 -0
  144. package/presets/openwrite/skills/oh-story-long-write/SKILL.md +37 -0
  145. package/presets/openwrite/skills/oh-story-long-write/agents/openai.yaml +4 -0
  146. package/presets/openwrite/skills/oh-story-long-write/references/method.md +33 -0
  147. package/presets/openwrite/skills/oh-story-review/LICENSE +21 -0
  148. package/presets/openwrite/skills/oh-story-review/SKILL.md +36 -0
  149. package/presets/openwrite/skills/oh-story-review/agents/openai.yaml +4 -0
  150. package/presets/openwrite/skills/oh-story-review/references/method.md +25 -0
  151. package/presets/openwrite/skills/progress/SKILL.md +16 -0
  152. package/presets/openwrite/skills/review-chapter/SKILL.md +17 -0
  153. package/presets/openwrite/skills/revise-span/SKILL.md +16 -0
  154. package/presets/openwrite/skills/style-system/SKILL.md +135 -0
  155. package/presets/openwrite/skills/style-system/prompts/analyze_style.md +214 -0
  156. package/presets/openwrite/skills/style-system/prompts/batch_extract_style.md +66 -0
  157. package/presets/openwrite/skills/style-system/prompts/compose_style.md +83 -0
  158. package/presets/openwrite/skills/style-system/prompts/extract_style.md +84 -0
  159. package/presets/openwrite/skills/style-system/prompts/initialize_style.md +52 -0
  160. package/presets/openwrite/skills/style-system/templates/analysis_report.yaml +78 -0
  161. package/presets/openwrite/skills/style-system/templates/composed_style.md +96 -0
  162. package/presets/openwrite/skills/style-system/templates/extraction_report.yaml +80 -0
  163. package/presets/openwrite/skills/style-system/templates/fingerprint.yaml +61 -0
  164. package/presets/openwrite/skills/style-system/tools/README.md +33 -0
  165. package/presets/openwrite/skills/truth-validation/SKILL.md +119 -0
  166. package/presets/openwrite/skills/workflow-manager/SKILL.md +81 -0
  167. package/presets/openwrite/skills/world-query/SKILL.md +45 -0
  168. package/presets/openwrite/skills/write-next/SKILL.md +17 -0
  169. package/release/core-requirements.in +12 -0
  170. package/release/licenses/@deepseek-ai__cosmokit@1.8.3/LICENSE +21 -0
  171. package/release/licenses/@deepseek-ai__schemastery@3.18.2/LICENSE +21 -0
  172. package/release/licenses/@isaacs__fs-minipass@4.0.1/LICENSE +15 -0
  173. package/release/licenses/@standard-schema__spec@1.1.0/LICENSE +21 -0
  174. package/release/licenses/@types__d3-color@3.1.3/LICENSE +21 -0
  175. package/release/licenses/@types__d3-drag@3.0.7/LICENSE +21 -0
  176. package/release/licenses/@types__d3-interpolate@3.0.4/LICENSE +21 -0
  177. package/release/licenses/@types__d3-selection@3.0.11/LICENSE +21 -0
  178. package/release/licenses/@types__d3-transition@3.0.9/LICENSE +21 -0
  179. package/release/licenses/@types__d3-zoom@3.0.8/LICENSE +21 -0
  180. package/release/licenses/@xyflow__react@12.11.6/LICENSE +21 -0
  181. package/release/licenses/@xyflow__system@0.0.82/LICENSE +21 -0
  182. package/release/licenses/ajv-formats@3.0.1/LICENSE +21 -0
  183. package/release/licenses/ajv@8.20.0/LICENSE +22 -0
  184. package/release/licenses/chownr@3.0.0/LICENSE.md +63 -0
  185. package/release/licenses/classcat@5.0.5/LICENSE.md +7 -0
  186. package/release/licenses/commander@7.2.0/LICENSE +22 -0
  187. package/release/licenses/d3-array@3.2.4/LICENSE +13 -0
  188. package/release/licenses/d3-axis@3.0.0/LICENSE +13 -0
  189. package/release/licenses/d3-brush@3.0.0/LICENSE +13 -0
  190. package/release/licenses/d3-chord@3.0.1/LICENSE +13 -0
  191. package/release/licenses/d3-color@3.1.0/LICENSE +13 -0
  192. package/release/licenses/d3-contour@4.0.2/LICENSE +13 -0
  193. package/release/licenses/d3-delaunay@6.0.4/LICENSE +14 -0
  194. package/release/licenses/d3-dispatch@3.0.1/LICENSE +13 -0
  195. package/release/licenses/d3-drag@3.0.0/LICENSE +13 -0
  196. package/release/licenses/d3-dsv@3.0.1/LICENSE +13 -0
  197. package/release/licenses/d3-ease@3.0.1/LICENSE +28 -0
  198. package/release/licenses/d3-fetch@3.0.1/LICENSE +13 -0
  199. package/release/licenses/d3-force@3.0.0/LICENSE +13 -0
  200. package/release/licenses/d3-format@3.1.2/LICENSE +13 -0
  201. package/release/licenses/d3-geo@3.1.1/LICENSE +34 -0
  202. package/release/licenses/d3-hierarchy@3.1.2/LICENSE +13 -0
  203. package/release/licenses/d3-interpolate@3.0.1/LICENSE +13 -0
  204. package/release/licenses/d3-path@3.1.0/LICENSE +13 -0
  205. package/release/licenses/d3-polygon@3.0.1/LICENSE +13 -0
  206. package/release/licenses/d3-quadtree@3.0.1/LICENSE +13 -0
  207. package/release/licenses/d3-random@3.0.1/LICENSE +13 -0
  208. package/release/licenses/d3-scale-chromatic@3.1.0/LICENSE +28 -0
  209. package/release/licenses/d3-scale@4.0.2/LICENSE +13 -0
  210. package/release/licenses/d3-selection@3.0.0/LICENSE +13 -0
  211. package/release/licenses/d3-shape@3.2.0/LICENSE +13 -0
  212. package/release/licenses/d3-time-format@4.1.0/LICENSE +13 -0
  213. package/release/licenses/d3-time@3.1.0/LICENSE +13 -0
  214. package/release/licenses/d3-timer@3.0.1/LICENSE +13 -0
  215. package/release/licenses/d3-transition@3.0.1/LICENSE +13 -0
  216. package/release/licenses/d3-zoom@3.0.0/LICENSE +13 -0
  217. package/release/licenses/d3@7.9.0/LICENSE +13 -0
  218. package/release/licenses/delaunator@5.1.0/LICENSE +15 -0
  219. package/release/licenses/elkjs@0.12.0/LICENSE.md +263 -0
  220. package/release/licenses/fast-deep-equal@3.1.3/LICENSE +21 -0
  221. package/release/licenses/fast-uri@3.1.7/LICENSE +30 -0
  222. package/release/licenses/fflate@0.8.2/LICENSE +21 -0
  223. package/release/licenses/graceful-fs@4.2.11/LICENSE +15 -0
  224. package/release/licenses/iconv-lite@0.6.3/LICENSE +21 -0
  225. package/release/licenses/internmap@2.0.3/LICENSE +13 -0
  226. package/release/licenses/json-schema-traverse@1.0.0/LICENSE +21 -0
  227. package/release/licenses/lucide-react@0.468.0/LICENSE +15 -0
  228. package/release/licenses/minipass@7.1.3/LICENSE.md +55 -0
  229. package/release/licenses/minizlib@3.1.0/LICENSE +26 -0
  230. package/release/licenses/proper-lockfile@4.1.2/LICENSE +21 -0
  231. package/release/licenses/require-from-string@2.0.2/license +21 -0
  232. package/release/licenses/retry@0.12.0/License +21 -0
  233. package/release/licenses/robust-predicates@3.0.3/LICENSE +24 -0
  234. package/release/licenses/rw@1.3.3/LICENSE +26 -0
  235. package/release/licenses/safer-buffer@2.1.2/LICENSE +21 -0
  236. package/release/licenses/signal-exit@3.0.7/LICENSE.txt +16 -0
  237. package/release/licenses/tar@7.5.7/LICENSE.md +55 -0
  238. package/release/licenses/use-sync-external-store@1.6.0/LICENSE +21 -0
  239. package/release/licenses/yallist@5.0.0/LICENSE.md +63 -0
  240. package/release/licenses/zustand@4.5.7/LICENSE +21 -0
  241. package/release/openwrite-5.8.1-py3-none-any.whl +0 -0
  242. package/release/platform-overrides.in +5 -0
  243. package/release/python-constraints.in +96 -0
  244. package/release/requirements.lock +2927 -0
  245. package/release/runtime-manifest.json +98 -0
  246. package/release/sdk-compatibility.json +240 -0
  247. package/release/source.json +859 -0
  248. package/release/wheels/cryptography-50.0.1-cp312-abi3-macosx_13_0_x86_64.whl +0 -0
  249. package/release/wheels/licenses/LICENSE.txt +177 -0
  250. package/release/wheels/provenance.json +15 -0
  251. package/scripts/dog/delivery-stage.js +32 -0
  252. package/scripts/dog/import-chapter.js +20 -0
  253. package/scripts/dog/import-record.js +35 -0
  254. package/scripts/dog/review-dimension.js +41 -0
  255. package/scripts/dog/review-record.js +42 -0
  256. package/scripts/maintenance.mjs +179 -0
  257. package/scripts/prepare.mjs +21 -0
  258. package/vendor/dsh-dog/LICENSE +29 -0
  259. package/vendor/dsh-dog/README.md +114 -0
  260. package/vendor/dsh-dog/UPSTREAM.md +22 -0
  261. package/vendor/dsh-dog/cordis.patch.yml +14 -0
  262. package/vendor/dsh-dog/lib/client.js +2898 -0
  263. package/vendor/dsh-dog/lib/client.js.map +1 -0
  264. package/vendor/dsh-dog/lib/core-BiEBMLz3.d.ts +495 -0
  265. package/vendor/dsh-dog/lib/core-DdfMcrB4.js +2074 -0
  266. package/vendor/dsh-dog/lib/core.d.ts +2 -0
  267. package/vendor/dsh-dog/lib/core.js +2 -0
  268. package/vendor/dsh-dog/lib/index.d.ts +133 -0
  269. package/vendor/dsh-dog/lib/index.js +967 -0
  270. package/vendor/dsh-dog/package.json +97 -0
  271. package/vendor/dsh-dog/schemas/schema-0.2/graph.schema.json +139 -0
  272. package/vendor/dsh-dog/schemas/schema-0.2/report.schema.json +87 -0
  273. package/vendor/dsh-dog/schemas/schema-0.2/run.schema.json +259 -0
  274. package/vendor/dsh-dog/schemas/schema-0.2/runtime-event.schema.json +105 -0
  275. package/vendor/dsh-dog/schemas/schema-0.2/verification.schema.json +40 -0
@@ -0,0 +1,12 @@
1
+ import { type JsonValue } from './client.js';
2
+ /**
3
+ * Anti-drift guard (docs/WORKSPACE_CONTEXT_CONTRACT.md §7): the workspace
4
+ * payload's project root must canonicalize to the current tool context root.
5
+ * Anything else means the Studio response belongs to another Workspace and
6
+ * materializing it here would leak artifacts across roots.
7
+ */
8
+ export declare function assertWorkspaceRootMatch(payloadRoot: string, expectedRoot: string): void;
9
+ /** Refuse write targets that escape the project root (e.g. via a hostile novel_id). */
10
+ export declare function assertContained(target: string, root: string): void;
11
+ /** Rebuild a chapter-delivery graph from canonical manuscript, review and revision records. */
12
+ export declare function materializeChapterDelivery(workspaceValue: unknown, chapterId: string, threshold?: number, expectedRoot?: string): Promise<JsonValue>;
@@ -0,0 +1,362 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { realpathSync } from 'node:fs';
3
+ import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
4
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
5
+ import { StudioError } from './client.js';
6
+ import { validateReviewV2Decision } from './contracts-generated.js';
7
+ const HARD_SEVERITIES = new Set(['critical', 'blocker']);
8
+ function record(value) {
9
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
10
+ }
11
+ function values(value) {
12
+ return Array.isArray(value) ? value : [];
13
+ }
14
+ function strings(value) {
15
+ return values(value).map(item => String(item)).filter(Boolean);
16
+ }
17
+ /** Read an artifact. Only a missing file is tolerated (undefined); corrupt
18
+ * JSON, non-object roots, and empty objects are contract failures so a
19
+ * malformed artifact can never masquerade as an absent one. */
20
+ async function readJson(path) {
21
+ let text;
22
+ try {
23
+ text = await readFile(path, 'utf8');
24
+ }
25
+ catch (error) {
26
+ if (error.code === 'ENOENT')
27
+ return undefined;
28
+ throw new Error(`DoG artifact unreadable: ${basename(path)}`);
29
+ }
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(text);
33
+ }
34
+ catch {
35
+ throw new Error(`DoG artifact corrupt JSON: ${basename(path)}`);
36
+ }
37
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
38
+ throw new Error(`DoG artifact root must be a JSON object: ${basename(path)}`);
39
+ }
40
+ const value = parsed;
41
+ if (Object.keys(value).length === 0) {
42
+ throw new Error(`DoG artifact empty object: ${basename(path)}`);
43
+ }
44
+ return value;
45
+ }
46
+ async function atomicWrite(path, value) {
47
+ await mkdir(dirname(path), { recursive: true });
48
+ const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}`);
49
+ try {
50
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
51
+ await rename(temporary, path);
52
+ }
53
+ catch (error) {
54
+ await rm(temporary, { force: true });
55
+ throw error;
56
+ }
57
+ }
58
+ async function findNamedFiles(root, filename) {
59
+ const found = [];
60
+ async function walk(directory) {
61
+ let entries;
62
+ try {
63
+ entries = await readdir(directory, { withFileTypes: true });
64
+ }
65
+ catch {
66
+ return;
67
+ }
68
+ for (const entry of entries) {
69
+ const path = join(directory, entry.name);
70
+ if (entry.isDirectory())
71
+ await walk(path);
72
+ else if (entry.isFile() && entry.name === filename)
73
+ found.push(path);
74
+ }
75
+ }
76
+ await walk(root);
77
+ return found;
78
+ }
79
+ function reviewSeverity(issue) {
80
+ return String(issue['review_severity'] ?? issue['legacy_severity'] ?? issue['severity'] ?? 'warning').toLowerCase();
81
+ }
82
+ /** v1-only adapter: derive delivery from legacy score/passed/severity. Never
83
+ * used when review_v2 is present; v2 records carry OpenWrite's canonical
84
+ * delivery_status. */
85
+ function legacyDeliveryStatus(review, threshold) {
86
+ const hard = values(review['issue_details']).some(item => HARD_SEVERITIES.has(reviewSeverity(record(item))));
87
+ if (hard)
88
+ return 'blocked';
89
+ if (review['passed'] === false)
90
+ return 'revise';
91
+ return Number(review['score'] ?? 0) >= threshold ? 'pass' : 'revise';
92
+ }
93
+ function deliveryStatus(review, threshold) {
94
+ const v2 = record(review['review_v2']);
95
+ if (Object.keys(v2).length > 0)
96
+ return String(v2['delivery_status'] ?? 'inconclusive').toLowerCase();
97
+ return legacyDeliveryStatus(review, threshold);
98
+ }
99
+ function stage(chapterId, name, verdict, status, evidence) {
100
+ return {
101
+ schemaVersion: 'dsh-novel.delivery.stage.v2', recordType: 'delivery-stage',
102
+ chapterId, stage: name, verdict, status, evidence,
103
+ };
104
+ }
105
+ /**
106
+ * Anti-drift guard (docs/WORKSPACE_CONTEXT_CONTRACT.md §7): the workspace
107
+ * payload's project root must canonicalize to the current tool context root.
108
+ * Anything else means the Studio response belongs to another Workspace and
109
+ * materializing it here would leak artifacts across roots.
110
+ */
111
+ export function assertWorkspaceRootMatch(payloadRoot, expectedRoot) {
112
+ let canonical;
113
+ try {
114
+ canonical = realpathSync(payloadRoot);
115
+ }
116
+ catch {
117
+ throw new StudioError(`DoG materialization workspace root cannot be canonicalized: ${payloadRoot}`, 409, 'WORKSPACE_CONTEXT_MISMATCH', { reason: 'not_found' });
118
+ }
119
+ if (canonical !== expectedRoot) {
120
+ throw new StudioError(`DoG materialization workspace root "${canonical}" does not match the current context root "${expectedRoot}"`, 409, 'WORKSPACE_CONTEXT_MISMATCH', { reason: 'root_mismatch' });
121
+ }
122
+ }
123
+ /** Refuse write targets that escape the project root (e.g. via a hostile novel_id). */
124
+ export function assertContained(target, root) {
125
+ const resolvedRoot = resolve(root);
126
+ const resolvedTarget = resolve(target);
127
+ if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(resolvedRoot + sep)) {
128
+ throw new StudioError(`DoG materialization target escapes the workspace root: ${target}`, 409, 'WORKSPACE_CONTEXT_MISMATCH', { reason: 'escape' });
129
+ }
130
+ }
131
+ /** Rebuild a chapter-delivery graph from canonical manuscript, review and revision records. */
132
+ export async function materializeChapterDelivery(workspaceValue, chapterId, threshold, expectedRoot) {
133
+ const workspace = record(workspaceValue);
134
+ const project = record(workspace['project']);
135
+ const snapshot = record(workspace['snapshot']);
136
+ const root = String(project['root'] ?? '').trim();
137
+ const novelId = String(snapshot['novel_id'] ?? '').trim();
138
+ if (!root || !novelId)
139
+ throw new Error('chapter delivery lacks workspace project root or novel_id');
140
+ if (!/^ch_\d+$/.test(chapterId))
141
+ throw new Error(`invalid chapter id for delivery graph: ${chapterId}`);
142
+ if (expectedRoot !== undefined)
143
+ assertWorkspaceRootMatch(root, expectedRoot);
144
+ try {
145
+ if (!(await stat(root)).isDirectory())
146
+ throw new Error('not a directory');
147
+ }
148
+ catch {
149
+ throw new Error(`chapter delivery project root is not a directory: ${root}`);
150
+ }
151
+ const novelRoot = join(root, 'data', 'novels', novelId);
152
+ const relativeDir = join('data', 'novels', novelId, 'data', 'dog', 'deliveries', chapterId).replaceAll('\\', '/');
153
+ const directory = join(root, relativeDir);
154
+ assertContained(directory, root);
155
+ const previous = await readJson(join(directory, 'delivery.json')) ?? {};
156
+ const effectiveThreshold = threshold ?? Number(previous['threshold'] ?? 70);
157
+ if (!Number.isInteger(effectiveThreshold) || effectiveThreshold < 0 || effectiveThreshold > 100) {
158
+ throw new Error('delivery threshold must be an integer between 0 and 100');
159
+ }
160
+ const manuscripts = await findNamedFiles(join(novelRoot, 'data', 'manuscript'), `${chapterId}.md`);
161
+ const manuscript = manuscripts.length === 1 ? manuscripts[0] : undefined;
162
+ const fallback = join(novelRoot, 'data', 'manuscript', 'arc_001', `${chapterId}.md`);
163
+ const manuscriptTarget = relative(root, manuscript ?? fallback).replaceAll('\\', '/');
164
+ let currentRevision = '';
165
+ if (manuscript !== undefined) {
166
+ currentRevision = `sha256:${createHash('sha256').update(await readFile(manuscript)).digest('hex')}`;
167
+ }
168
+ const review = await readJson(join(novelRoot, 'data', 'reviews', `${chapterId}.json`)) ?? {};
169
+ // Existence/type/version policy, mirroring Python review_store: a present
170
+ // review_v2 key (even null) must be a non-empty JSON object declaring the
171
+ // supported schema version; only records without the key ride the legacy
172
+ // v1 adapter.
173
+ const hasV2 = Object.hasOwn(review, 'review_v2');
174
+ const rawV2 = review['review_v2'];
175
+ if (hasV2) {
176
+ if (rawV2 === null || typeof rawV2 !== 'object' || Array.isArray(rawV2)) {
177
+ throw new Error(`review_v2 must be a JSON object when present, got ${rawV2 === null ? 'null' : typeof rawV2}`);
178
+ }
179
+ const versionProbe = rawV2;
180
+ if (Object.keys(versionProbe).length === 0) {
181
+ throw new Error('review_v2 empty object: unsupported or missing schema version');
182
+ }
183
+ if (versionProbe['schema_version'] !== 'openwrite.review.v2') {
184
+ throw new Error(`unsupported review_v2 schema version: ${String(versionProbe['schema_version'])}`);
185
+ }
186
+ // Full schema-derived contract check (generated from
187
+ // OpenWrite contracts/review-v2-decision.schema.json).
188
+ validateReviewV2Decision(rawV2);
189
+ }
190
+ const reviewSourceRevision = String(review['source_revision'] ?? '');
191
+ const reviewStale = Boolean(review['stale'])
192
+ || (Object.keys(review).length > 0 && currentRevision !== '' && reviewSourceRevision !== currentRevision);
193
+ const reviewCurrent = Object.keys(review).length > 0 && currentRevision !== '' && !reviewStale;
194
+ const currentDeliveryStatus = Object.keys(review).length > 0 ? deliveryStatus(review, effectiveThreshold) : 'inconclusive';
195
+ const reviewPassed = reviewCurrent && currentDeliveryStatus === 'pass';
196
+ const issues = values(review['issue_details']).map(record).filter(item => Object.keys(item).length > 0);
197
+ const issueIds = issues.map(item => String(item['id'] ?? '')).filter(Boolean);
198
+ const hardIssueIds = issues
199
+ .filter(item => HARD_SEVERITIES.has(reviewSeverity(item)))
200
+ .map(item => String(item['id'] ?? '')).filter(Boolean);
201
+ const revisionDir = join(novelRoot, 'data', 'revisions', chapterId);
202
+ let proposalNames = [];
203
+ try {
204
+ proposalNames = (await readdir(revisionDir)).filter(name => /^rev_.*\.json$/.test(name)).sort();
205
+ }
206
+ catch {
207
+ proposalNames = [];
208
+ }
209
+ const proposalRecords = await Promise.all(proposalNames.map(name => readJson(join(revisionDir, name))));
210
+ const proposals = proposalRecords.filter((item) => item !== undefined && item['kind'] === 'review_fix');
211
+ const applied = proposals.filter(item => item['status'] === 'applied');
212
+ const pending = proposals.filter(item => item['status'] === 'proposed');
213
+ const appliedToCurrent = applied.filter(item => String(item['applied_revision'] ?? '') === currentRevision);
214
+ const writingStage = stage(chapterId, 'writing', currentRevision !== '' ? 'pass' : 'inconclusive', currentRevision !== '' ? 'committed' : 'missing', {
215
+ manuscriptTarget, currentRevision,
216
+ });
217
+ let reviewStage;
218
+ if (Object.keys(review).length === 0) {
219
+ reviewStage = stage(chapterId, 'review', 'inconclusive', 'missing', {});
220
+ }
221
+ else if (!reviewCurrent) {
222
+ reviewStage = stage(chapterId, 'review', 'inconclusive', reviewStale ? 'stale' : 'unverifiable', {
223
+ sourceRevision: reviewSourceRevision, currentRevision, staleReason: review['stale_reason'] ?? null,
224
+ });
225
+ }
226
+ else {
227
+ const v2 = record(review['review_v2']);
228
+ reviewStage = stage(chapterId, 'review', reviewPassed ? 'pass' : ['inconclusive', 'stale'].includes(currentDeliveryStatus) ? 'inconclusive' : 'fail', 'current', {
229
+ score: review['score'] ?? null, threshold: effectiveThreshold, passedGate: reviewPassed,
230
+ qualityScore: v2['quality_score'] ?? review['score'] ?? null, coverage: v2['coverage'] ?? 1,
231
+ gateStatus: v2['gate_status'] ?? (hardIssueIds.length > 0 ? 'blocked' : 'pass'),
232
+ deliveryStatus: currentDeliveryStatus,
233
+ issueIds, hardIssueIds, sourceRevision: reviewSourceRevision,
234
+ });
235
+ }
236
+ let revisionStage;
237
+ if (reviewPassed) {
238
+ revisionStage = stage(chapterId, 'revision', 'pass', 'not_required', {
239
+ appliedProposalIds: applied.map(item => String(item['proposal_id'] ?? '')).filter(Boolean),
240
+ });
241
+ }
242
+ else if (reviewStale && appliedToCurrent.length > 0) {
243
+ revisionStage = stage(chapterId, 'revision', 'pass', 'applied_requires_rereview', {
244
+ appliedProposalIds: appliedToCurrent.map(item => String(item['proposal_id'] ?? '')).filter(Boolean),
245
+ addressedIssueIds: [...new Set(appliedToCurrent.flatMap(item => strings(item['review_issue_ids'])))].sort(),
246
+ });
247
+ }
248
+ else if (pending.length > 0) {
249
+ revisionStage = stage(chapterId, 'revision', 'inconclusive', 'proposal_pending', {
250
+ proposalIds: pending.map(item => String(item['proposal_id'] ?? '')).filter(Boolean), requiredIssueIds: issueIds,
251
+ });
252
+ }
253
+ else {
254
+ revisionStage = stage(chapterId, 'revision', 'inconclusive', 'revision_required', { requiredIssueIds: issueIds });
255
+ }
256
+ let applicationStage;
257
+ if (reviewPassed && appliedToCurrent.length === 0) {
258
+ applicationStage = stage(chapterId, 'application', 'pass', 'not_required', {});
259
+ }
260
+ else if (appliedToCurrent.length > 0) {
261
+ applicationStage = stage(chapterId, 'application', 'pass', 'applied', {
262
+ proposalIds: appliedToCurrent.map(item => String(item['proposal_id'] ?? '')).filter(Boolean), appliedRevision: currentRevision,
263
+ });
264
+ }
265
+ else if (pending.length > 0) {
266
+ applicationStage = stage(chapterId, 'application', 'inconclusive', 'awaiting_application', {
267
+ proposalIds: pending.map(item => String(item['proposal_id'] ?? '')).filter(Boolean),
268
+ });
269
+ }
270
+ else {
271
+ applicationStage = stage(chapterId, 'application', 'inconclusive', 'waiting_for_revision', {});
272
+ }
273
+ let rereviewStage;
274
+ const rereviewAfterApplication = appliedToCurrent.length > 0 && reviewCurrent && reviewSourceRevision === currentRevision;
275
+ if (rereviewAfterApplication) {
276
+ rereviewStage = stage(chapterId, 'rereview', reviewPassed ? 'pass' : 'fail', 'completed', {
277
+ sourceRevision: reviewSourceRevision, deliveryStatus: currentDeliveryStatus,
278
+ });
279
+ }
280
+ else if (appliedToCurrent.length > 0) {
281
+ rereviewStage = stage(chapterId, 'rereview', 'inconclusive', 'required', { currentRevision });
282
+ }
283
+ else if (reviewPassed) {
284
+ rereviewStage = stage(chapterId, 'rereview', 'pass', 'not_required', {});
285
+ }
286
+ else {
287
+ rereviewStage = stage(chapterId, 'rereview', 'inconclusive', 'waiting_for_application', {});
288
+ }
289
+ let closureStage;
290
+ if (reviewPassed) {
291
+ const delta = record(review['issue_delta']);
292
+ closureStage = stage(chapterId, 'closure', 'pass', 'closed', {
293
+ score: review['score'] ?? null, sourceRevision: reviewSourceRevision,
294
+ resolvedIssueIds: values(delta['resolved']).map(record).map(item => String(item['id'] ?? '')).filter(Boolean),
295
+ });
296
+ }
297
+ else if (reviewStale && appliedToCurrent.length > 0) {
298
+ closureStage = stage(chapterId, 'closure', 'inconclusive', 'rereview_required', {
299
+ currentRevision, appliedProposalIds: appliedToCurrent.map(item => String(item['proposal_id'] ?? '')).filter(Boolean),
300
+ });
301
+ }
302
+ else if (reviewCurrent) {
303
+ closureStage = stage(chapterId, 'closure', 'fail', 'review_failed', {
304
+ score: review['score'] ?? null, threshold: effectiveThreshold, issueIds, hardIssueIds,
305
+ });
306
+ }
307
+ else {
308
+ closureStage = stage(chapterId, 'closure', 'inconclusive', 'review_required', {});
309
+ }
310
+ const stages = {
311
+ writing: writingStage, review: reviewStage, revision: revisionStage,
312
+ application: applicationStage, rereview: rereviewStage, closure: closureStage,
313
+ };
314
+ await Promise.all(Object.entries(stages).map(([name, value]) => atomicWrite(join(directory, `${name}.json`), value)));
315
+ const manifest = {
316
+ schemaVersion: 'dsh-novel.delivery.manifest.v2', recordType: 'chapter-delivery',
317
+ chapterId, novelId, threshold: effectiveThreshold, manuscriptTarget, currentRevision,
318
+ readyForDelivery: currentRevision !== '' && closureStage['verdict'] === 'pass', verdict: closureStage['verdict'], stages,
319
+ decisionSource: Object.hasOwn(review, 'review_v2') ? 'v2' : 'v1-adapter',
320
+ revisionTrail: proposals.map(item => ({
321
+ proposalId: String(item['proposal_id'] ?? ''), status: String(item['status'] ?? ''),
322
+ issueIds: strings(item['review_issue_ids']), sourceRevision: String(item['source_revision'] ?? ''),
323
+ appliedRevision: String(item['applied_revision'] ?? ''),
324
+ })),
325
+ };
326
+ await atomicWrite(join(directory, 'delivery.json'), manifest);
327
+ const nodes = {
328
+ root: {
329
+ kind: 'composite', title: `${chapterId} 章节交付`, constraint: 'hard', target: `${relativeDir}/delivery.json`,
330
+ completion: { op: 'all', items: Object.keys(stages).map(id => ({ op: 'ref', id })) },
331
+ verifier: { mode: 'programmatic', script: 'delivery-stage' },
332
+ },
333
+ };
334
+ const stageTitles = [
335
+ ['writing', '正文已成形'], ['review', '当前正文已评审'], ['revision', '修订提案已生成'],
336
+ ['application', '修订已应用'], ['rereview', '新正文已复评'], ['closure', '问题经复评关闭'],
337
+ ];
338
+ for (const [name, title] of stageTitles) {
339
+ nodes[name] = {
340
+ kind: 'leaf', title, constraint: 'hard', target: `${relativeDir}/${name}.json`,
341
+ verifier: { mode: 'programmatic', script: 'delivery-stage' },
342
+ };
343
+ }
344
+ const graph = {
345
+ schemaVersion: '0.9', id: `novel-delivery-${chapterId}`, root: 'root', nodes,
346
+ contains: Object.keys(stages).map(child => ({ parent: 'root', child, required: true, failure: 'fatal' })),
347
+ dependsOn: [
348
+ { source: 'review', target: 'writing', data: ['manuscript'] },
349
+ { source: 'revision', target: 'review', data: ['review'] },
350
+ { source: 'application', target: 'revision', data: ['revision'] },
351
+ { source: 'rereview', target: 'application', data: ['application'] },
352
+ { source: 'closure', target: 'rereview', data: ['rereview'] },
353
+ ],
354
+ };
355
+ const graphPath = join(directory, 'dog-graph.json');
356
+ await atomicWrite(graphPath, graph);
357
+ return {
358
+ status: 'ready', manifestPath: join(directory, 'delivery.json'), graphPath,
359
+ graphTarget: relative(root, graphPath), readyForDelivery: manifest.readyForDelivery,
360
+ stages: Object.fromEntries(Object.entries(stages).map(([name, value]) => [name, value['status']])),
361
+ };
362
+ }
@@ -0,0 +1,50 @@
1
+ import type { JsonValue } from './client.js';
2
+ type RecordValue = Record<string, unknown>;
3
+ export declare const DOG_REVIEW_DIMENSIONS: Readonly<Record<number, string>>;
4
+ export declare const DOG_REVIEW_DOMAINS: readonly [{
5
+ readonly id: "coherence";
6
+ readonly name: "连贯与逻辑";
7
+ readonly weight: 20;
8
+ readonly legacyCheckIds: readonly [2, 3, 4, 5, 9, 11, 35];
9
+ }, {
10
+ readonly id: "character";
11
+ readonly name: "角色与关系";
12
+ readonly weight: 15;
13
+ readonly legacyCheckIds: readonly [1, 13, 14, 16, 34, 36];
14
+ }, {
15
+ readonly id: "plot";
16
+ readonly name: "情节与承诺";
17
+ readonly weight: 20;
18
+ readonly legacyCheckIds: readonly [6, 15, 24, 25, 32, 33];
19
+ }, {
20
+ readonly id: "pacing";
21
+ readonly name: "节奏与场景";
22
+ readonly weight: 15;
23
+ readonly legacyCheckIds: readonly [7, 17, 26];
24
+ }, {
25
+ readonly id: "prose";
26
+ readonly name: "文风与表达";
27
+ readonly weight: 15;
28
+ readonly legacyCheckIds: readonly [8, 10, 19, 20, 21, 22, 23];
29
+ }, {
30
+ readonly id: "canon";
31
+ readonly name: "正典与资料";
32
+ readonly weight: 15;
33
+ readonly legacyCheckIds: readonly [12, 18, 28, 29, 30, 31, 37];
34
+ }];
35
+ export declare function dogReviewFrameworkRevision(frameworkValue: unknown): string;
36
+ /** Bind OpenWrite's versioned review blueprint to one chapter. The bridge
37
+ * supplies paths only; all nodes and edges come from the canonical server. */
38
+ export declare function instantiateDogReviewFramework(frameworkValue: unknown, chapterId: string, relativeDirectory: string): RecordValue;
39
+ export declare function buildDogReviewBundle(reviewValue: unknown, chapterId: string, threshold: number): {
40
+ manifest: RecordValue;
41
+ dimensionRecords: RecordValue[];
42
+ };
43
+ /** Materialize the review response into model-free files consumable by dsh-dog. */
44
+ export declare function materializeDogReview(response: unknown, chapterId: string, threshold: number, expectedRoot?: string): Promise<JsonValue>;
45
+ export declare function materializeCompletedDogTaskReview(response: unknown, workspace: unknown, framework: unknown, threshold?: number, expectedRoot?: string): Promise<JsonValue | undefined>;
46
+ export declare function reviewChapterId(args: {
47
+ path?: string;
48
+ chapter_id?: string;
49
+ }): string;
50
+ export {};