novel-writer-cli 0.0.1

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 (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/agents/chapter-writer.md +142 -0
  4. package/agents/character-weaver.md +117 -0
  5. package/agents/consistency-auditor.md +85 -0
  6. package/agents/plot-architect.md +128 -0
  7. package/agents/quality-judge.md +232 -0
  8. package/agents/style-analyzer.md +109 -0
  9. package/agents/style-refiner.md +97 -0
  10. package/agents/summarizer.md +128 -0
  11. package/agents/world-builder.md +161 -0
  12. package/dist/__tests__/character-voice.test.js +445 -0
  13. package/dist/__tests__/commit-prototype-pollution.test.js +45 -0
  14. package/dist/__tests__/engagement.test.js +382 -0
  15. package/dist/__tests__/foreshadow-visibility.test.js +131 -0
  16. package/dist/__tests__/hook-ledger.test.js +1028 -0
  17. package/dist/__tests__/naming-lint.test.js +132 -0
  18. package/dist/__tests__/narrative-health-injection.test.js +359 -0
  19. package/dist/__tests__/next-step-prejudge-guardrails.test.js +325 -0
  20. package/dist/__tests__/next-step-title-fix.test.js +153 -0
  21. package/dist/__tests__/platform-profile.test.js +274 -0
  22. package/dist/__tests__/promise-ledger.test.js +189 -0
  23. package/dist/__tests__/readability-lint.test.js +209 -0
  24. package/dist/__tests__/text-utils.test.js +39 -0
  25. package/dist/__tests__/title-policy.test.js +147 -0
  26. package/dist/advance.js +75 -0
  27. package/dist/character-voice.js +805 -0
  28. package/dist/checkpoint.js +126 -0
  29. package/dist/cli.js +563 -0
  30. package/dist/cliche-lint.js +515 -0
  31. package/dist/commit.js +1460 -0
  32. package/dist/consistency-auditor.js +684 -0
  33. package/dist/engagement.js +687 -0
  34. package/dist/errors.js +7 -0
  35. package/dist/fingerprint.js +16 -0
  36. package/dist/foreshadow-visibility.js +214 -0
  37. package/dist/fs-utils.js +68 -0
  38. package/dist/hook-ledger.js +721 -0
  39. package/dist/hook-policy.js +107 -0
  40. package/dist/instruction-gates.js +51 -0
  41. package/dist/instructions.js +406 -0
  42. package/dist/latest-summary-loader.js +29 -0
  43. package/dist/lock.js +121 -0
  44. package/dist/naming-lint.js +531 -0
  45. package/dist/ner.js +73 -0
  46. package/dist/next-step.js +408 -0
  47. package/dist/novel-ask.js +270 -0
  48. package/dist/output.js +9 -0
  49. package/dist/platform-constraints.js +518 -0
  50. package/dist/platform-profile.js +325 -0
  51. package/dist/prejudge-guardrails.js +370 -0
  52. package/dist/project.js +40 -0
  53. package/dist/promise-ledger.js +723 -0
  54. package/dist/readability-lint.js +555 -0
  55. package/dist/safe-parse.js +36 -0
  56. package/dist/safe-path.js +29 -0
  57. package/dist/scoring-weights.js +290 -0
  58. package/dist/steps.js +60 -0
  59. package/dist/text-utils.js +18 -0
  60. package/dist/title-policy.js +251 -0
  61. package/dist/type-guards.js +6 -0
  62. package/dist/validate.js +131 -0
  63. package/docs/user/README.md +17 -0
  64. package/docs/user/guardrails.md +179 -0
  65. package/docs/user/interactive-gates.md +124 -0
  66. package/docs/user/novel-cli.md +289 -0
  67. package/docs/user/ops.md +123 -0
  68. package/docs/user/quick-start.md +97 -0
  69. package/docs/user/spec-system.md +166 -0
  70. package/docs/user/storylines.md +144 -0
  71. package/package.json +48 -0
  72. package/schemas/README.md +18 -0
  73. package/schemas/character-voice-drift.schema.json +135 -0
  74. package/schemas/character-voice-profiles.schema.json +141 -0
  75. package/schemas/engagement-metrics.schema.json +38 -0
  76. package/schemas/hook-ledger.schema.json +108 -0
  77. package/schemas/platform-profile.schema.json +235 -0
  78. package/schemas/promise-ledger.schema.json +97 -0
  79. package/scripts/calibrate-quality-judge.sh +91 -0
  80. package/scripts/compare-regression-runs.sh +86 -0
  81. package/scripts/lib/_common.py +131 -0
  82. package/scripts/lib/calibrate_quality_judge.py +312 -0
  83. package/scripts/lib/compare_regression_runs.py +142 -0
  84. package/scripts/lib/run_regression.py +621 -0
  85. package/scripts/lint-blacklist.sh +201 -0
  86. package/scripts/lint-cliche.sh +370 -0
  87. package/scripts/lint-readability.sh +404 -0
  88. package/scripts/query-foreshadow.sh +252 -0
  89. package/scripts/run-ner.sh +669 -0
  90. package/scripts/run-regression.sh +122 -0
  91. package/skills/cli-step/SKILL.md +158 -0
  92. package/skills/continue/SKILL.md +348 -0
  93. package/skills/continue/references/context-contracts.md +169 -0
  94. package/skills/continue/references/continuity-checks.md +187 -0
  95. package/skills/continue/references/file-protocols.md +64 -0
  96. package/skills/continue/references/foreshadowing.md +130 -0
  97. package/skills/continue/references/gate-decision.md +53 -0
  98. package/skills/continue/references/periodic-maintenance.md +46 -0
  99. package/skills/novel-writing/SKILL.md +77 -0
  100. package/skills/novel-writing/references/quality-rubric.md +140 -0
  101. package/skills/novel-writing/references/style-guide.md +145 -0
  102. package/skills/start/SKILL.md +458 -0
  103. package/skills/start/references/quality-review.md +86 -0
  104. package/skills/start/references/setting-update.md +44 -0
  105. package/skills/start/references/vol-planning.md +61 -0
  106. package/skills/start/references/vol-review.md +58 -0
  107. package/skills/status/SKILL.md +116 -0
  108. package/skills/status/references/sample-output.md +60 -0
  109. package/templates/ai-blacklist.json +79 -0
  110. package/templates/brief-template.md +46 -0
  111. package/templates/genre-weight-profiles.json +90 -0
  112. package/templates/novel-ask/example.answer.json +12 -0
  113. package/templates/novel-ask/example.question.json +51 -0
  114. package/templates/platform-profile.json +148 -0
  115. package/templates/style-profile-template.json +58 -0
  116. package/templates/web-novel-cliche-lint.json +41 -0
@@ -0,0 +1,144 @@
1
+ # 多线叙事指南
2
+
3
+ 本系统支持多 POV 群像、势力暗线、跨卷伏笔交汇等复杂叙事结构。
4
+
5
+ ## 核心概念
6
+
7
+ ### 故事线(Storyline)
8
+
9
+ 每条故事线有固定 ID(连字符命名,如 `main-arc`、`faction-war`),一经定义不可重命名。
10
+
11
+ **类型**(`type:` 前缀):
12
+
13
+ | 类型 | 说明 |
14
+ |------|------|
15
+ | `type:main_arc` | 主线,贯穿全书 |
16
+ | `type:faction_conflict` | 势力冲突线 |
17
+ | `type:conspiracy` | 阴谋暗线 |
18
+ | `type:mystery` | 悬疑/谜题线 |
19
+ | `type:character_arc` | 角色成长线 |
20
+ | `type:parallel_timeline` | 平行时间线 |
21
+
22
+ ### 卷级角色(Volume Role)
23
+
24
+ 每条故事线在每卷中有不同角色:
25
+
26
+ - **primary**:本卷主要推进的线(通常 1-2 条)
27
+ - **secondary**:辅助线,有最小出场频率要求
28
+ - **seasoning**:调味线,偶尔出场,不做频率要求
29
+
30
+ ## 快速起步
31
+
32
+ 快速起步阶段,系统只初始化 1 条 `type:main_arc` 主线:
33
+
34
+ ```json
35
+ {
36
+ "storylines": [
37
+ {
38
+ "id": "main-arc",
39
+ "name": "主线名称",
40
+ "type": "type:main_arc",
41
+ "scope": "novel",
42
+ "status": "active"
43
+ }
44
+ ]
45
+ }
46
+ ```
47
+
48
+ 后续在卷规划阶段,PlotArchitect 会根据剧情需要建议新增故事线。
49
+
50
+ ## 活跃线限制
51
+
52
+ **同时活跃 ≤4 条**。这是硬约束(LS-002),超出时系统会要求你暂停或合并故事线。
53
+
54
+ 为什么限制 4 条?因为每章写作时 ChapterWriter 需要接收所有活跃线的 context,线太多会导致 context 过载和串线风险。
55
+
56
+ ## 卷级调度
57
+
58
+ 进入卷规划时,PlotArchitect 生成 `storyline-schedule.json`:
59
+
60
+ ```json
61
+ {
62
+ "active_storylines": [
63
+ { "storyline_id": "main-arc", "volume_role": "primary" },
64
+ { "storyline_id": "faction-war", "volume_role": "secondary" }
65
+ ],
66
+ "interleaving_pattern": {
67
+ "secondary_min_appearance": "every_8_chapters"
68
+ },
69
+ "convergence_events": [
70
+ {
71
+ "event": "两线在第 20 章交汇",
72
+ "involved_storylines": ["main-arc", "faction-war"],
73
+ "target_chapter_range": [18, 22]
74
+ }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ 关键字段:
80
+
81
+ - `secondary_min_appearance`:副线最小出场间隔(如 `every_8_chapters` = 每 8 章至少出现 1 次)
82
+ - `convergence_events`:交汇事件——多条线在指定章节范围内汇合
83
+
84
+ ## 防串线机制
85
+
86
+ 三层防护:
87
+
88
+ 1. **结构化 Context**:ChapterWriter 每次只接收当前章相关的故事线状态,而非全部
89
+ 2. **反串线指令**:prompt 中明确标注「本章属于 X 线,不得出现 Y 线的信息」
90
+ 3. **QualityJudge 后验**:LS-005 规则检查跨线实体是否泄漏
91
+
92
+ 每次续写都是独立的 LLM 调用,不依赖前一章的会话历史。
93
+
94
+ ## 交汇事件
95
+
96
+ 当多条故事线需要汇合时(如主角终于遇到暗线反派),在 `convergence_events` 中定义:
97
+
98
+ - 涉及哪些线
99
+ - 目标章节范围
100
+ - 交汇后的状态变化
101
+
102
+ 卷末回顾时,系统会检查交汇事件是否在预定范围内达成。
103
+
104
+ ## 故事线记忆
105
+
106
+ 每条故事线有独立记忆文件 `storylines/{id}/memory.md`(≤500 字),由 Summarizer 每章更新。
107
+
108
+ 记忆文件记录这条线的关键事实,供 ChapterWriter 写作时参考,避免遗忘已建立的设定。
109
+
110
+ ## 桥梁关系
111
+
112
+ 故事线之间可以定义桥梁(bridge),表示两条线通过共享伏笔或角色关联:
113
+
114
+ ```json
115
+ {
116
+ "relationships": [
117
+ {
118
+ "from": "main-arc",
119
+ "to": "conspiracy",
120
+ "type": "bridge:shared_foreshadowing",
121
+ "bridges": {
122
+ "shared_foreshadowing": ["foreshadow-id-1", "foreshadow-id-2"]
123
+ }
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ 卷末回顾时,系统检查桥梁是否断链(共享伏笔是否在全局索引中存在)。
130
+
131
+ ## 日常操作
132
+
133
+ | 场景 | 操作 |
134
+ |------|------|
135
+ | 新增故事线 | 卷规划时由 PlotArchitect 建议,你确认 |
136
+ | 暂停故事线 | 将 status 改为 `dormant`(不计入活跃线) |
137
+ | 合并故事线 | 交汇事件达成后,将一条线标记为 `resolved` |
138
+ | 查看节奏 | `/novel:status` 展示各线出场频率和休眠提醒 |
139
+
140
+ ## 注意事项
141
+
142
+ - 故事线 ID 用连字符(`main-arc`),类型用下划线 + `type:` 前缀(`type:main_arc`)
143
+ - 不要手动编辑 `storylines/storylines.json`,通过 `/novel:start` → 更新设定来操作
144
+ - 多线体系的完整能力在卷规划阶段才会展开,快速起步阶段保持简单
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "novel-writer-cli",
3
+ "version": "0.0.1",
4
+ "description": "Executor-agnostic novel orchestration CLI",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "novel": "dist/cli.js",
9
+ "novel-writer-cli": "dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist/",
13
+ "agents/",
14
+ "skills/",
15
+ "schemas/",
16
+ "scripts/",
17
+ "templates/",
18
+ "docs/user/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/DankerMu/novel-writer-cli.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/DankerMu/novel-writer-cli/issues"
28
+ },
29
+ "homepage": "https://github.com/DankerMu/novel-writer-cli#readme",
30
+ "scripts": {
31
+ "dev": "tsx src/cli.ts",
32
+ "build": "tsc -p tsconfig.json",
33
+ "start": "node dist/cli.js",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit",
35
+ "test": "npm run build && node --test dist/__tests__/*.test.js"
36
+ },
37
+ "dependencies": {
38
+ "commander": "^14.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.0.0",
42
+ "tsx": "^4.20.0",
43
+ "typescript": "^5.9.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ }
48
+ }
@@ -0,0 +1,18 @@
1
+ # JSON Schemas
2
+
3
+ This directory contains machine-readable JSON Schemas that act as **single sources of truth** (SSOT) for project-facing JSON/JSONL files.
4
+
5
+ ## Integration plan
6
+
7
+ - Templates SHOULD be derived from these schemas (and may include a `$schema` pointer for editor tooling).
8
+ - Runtime validators SHOULD validate project files against these schemas (e.g., via Ajv or python-jsonschema) and fail fast on enum/range violations.
9
+ - Specs SHOULD reference the schema path instead of duplicating field definitions.
10
+
11
+ ## Available schemas
12
+
13
+ - `schemas/platform-profile.schema.json` — `platform-profile.json` (M6 baseline + M7 optional extensions)
14
+ - `schemas/hook-ledger.schema.json` — `hook-ledger.json` (M7 retention hook ledger)
15
+ - `schemas/promise-ledger.schema.json` — `promise-ledger.json` (M7 long-horizon narrative promise ledger)
16
+ - `schemas/engagement-metrics.schema.json` — `engagement-metrics.jsonl` record (M7 engagement density stream)
17
+ - `schemas/character-voice-profiles.schema.json` — `character-voice-profiles.json` (M7 per-character voice profiles)
18
+ - `schemas/character-voice-drift.schema.json` — `character-voice-drift.json` (M7 voice drift directives)
@@ -0,0 +1,135 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "schema_version": 1,
4
+ "title": "character-voice-drift.json",
5
+ "type": "object",
6
+ "required": ["schema_version", "generated_at", "as_of", "window", "profiles_path", "characters"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "Optional schema pointer for editor tooling. Example: schemas/character-voice-drift.schema.json"
12
+ },
13
+ "schema_version": {
14
+ "type": "integer",
15
+ "enum": [1],
16
+ "description": "Schema version of character-voice-drift.json (SSOT in schemas/character-voice-drift.schema.json)."
17
+ },
18
+ "generated_at": { "type": "string", "format": "date-time" },
19
+ "as_of": {
20
+ "type": "object",
21
+ "required": ["chapter", "volume"],
22
+ "additionalProperties": false,
23
+ "properties": {
24
+ "chapter": { "type": "integer", "minimum": 1 },
25
+ "volume": { "type": "integer", "minimum": 0 }
26
+ }
27
+ },
28
+ "window": {
29
+ "type": "object",
30
+ "required": ["chapter_start", "chapter_end", "window_chapters"],
31
+ "additionalProperties": false,
32
+ "properties": {
33
+ "chapter_start": { "type": "integer", "minimum": 1 },
34
+ "chapter_end": { "type": "integer", "minimum": 1 },
35
+ "window_chapters": { "type": "integer", "minimum": 1 }
36
+ }
37
+ },
38
+ "profiles_path": { "type": "string", "minLength": 1 },
39
+ "characters": {
40
+ "type": "array",
41
+ "items": { "$ref": "#/$defs/character" }
42
+ }
43
+ },
44
+ "$defs": {
45
+ "drifted_metric": {
46
+ "type": "object",
47
+ "required": ["id", "baseline", "current", "detail"],
48
+ "additionalProperties": false,
49
+ "properties": {
50
+ "id": { "type": "string", "minLength": 1 },
51
+ "baseline": { "type": "number" },
52
+ "current": { "type": "number" },
53
+ "detail": { "type": "string", "minLength": 1 }
54
+ }
55
+ },
56
+ "signature_phrases": {
57
+ "type": "object",
58
+ "required": ["baseline", "current", "overlap"],
59
+ "additionalProperties": false,
60
+ "properties": {
61
+ "baseline": { "type": "array", "items": { "type": "string", "minLength": 1 } },
62
+ "current": { "type": "array", "items": { "type": "string", "minLength": 1 } },
63
+ "overlap": { "type": ["number", "null"], "minimum": 0, "maximum": 1 }
64
+ }
65
+ },
66
+ "metrics": {
67
+ "type": "object",
68
+ "required": [
69
+ "dialogue_samples",
70
+ "dialogue_chars",
71
+ "dialogue_len_avg",
72
+ "dialogue_len_p25",
73
+ "dialogue_len_p50",
74
+ "dialogue_len_p75",
75
+ "sentence_len_avg",
76
+ "sentence_len_p25",
77
+ "sentence_len_p50",
78
+ "sentence_len_p75",
79
+ "exclamation_per_100_chars",
80
+ "question_per_100_chars",
81
+ "ellipsis_per_100_chars"
82
+ ],
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "dialogue_samples": { "type": "integer", "minimum": 0 },
86
+ "dialogue_chars": { "type": "integer", "minimum": 0 },
87
+ "dialogue_len_avg": { "type": "number", "minimum": 0 },
88
+ "dialogue_len_p25": { "type": "integer", "minimum": 0 },
89
+ "dialogue_len_p50": { "type": "integer", "minimum": 0 },
90
+ "dialogue_len_p75": { "type": "integer", "minimum": 0 },
91
+ "sentence_len_avg": { "type": "number", "minimum": 0 },
92
+ "sentence_len_p25": { "type": "integer", "minimum": 0 },
93
+ "sentence_len_p50": { "type": "integer", "minimum": 0 },
94
+ "sentence_len_p75": { "type": "integer", "minimum": 0 },
95
+ "exclamation_per_100_chars": { "type": "number", "minimum": 0 },
96
+ "question_per_100_chars": { "type": "number", "minimum": 0 },
97
+ "ellipsis_per_100_chars": { "type": "number", "minimum": 0 }
98
+ }
99
+ },
100
+ "evidence": {
101
+ "type": "object",
102
+ "required": ["chapter", "excerpt"],
103
+ "additionalProperties": false,
104
+ "properties": {
105
+ "chapter": { "type": "integer", "minimum": 1 },
106
+ "excerpt": { "type": "string", "minLength": 1 }
107
+ }
108
+ },
109
+ "character": {
110
+ "type": "object",
111
+ "required": [
112
+ "character_id",
113
+ "display_name",
114
+ "drifted_metrics",
115
+ "signature_phrases",
116
+ "baseline_metrics",
117
+ "current_metrics",
118
+ "evidence",
119
+ "directives"
120
+ ],
121
+ "additionalProperties": false,
122
+ "properties": {
123
+ "character_id": { "type": "string", "minLength": 1 },
124
+ "display_name": { "type": "string", "minLength": 1 },
125
+ "drifted_metrics": { "type": "array", "items": { "$ref": "#/$defs/drifted_metric" } },
126
+ "signature_phrases": { "$ref": "#/$defs/signature_phrases" },
127
+ "baseline_metrics": { "$ref": "#/$defs/metrics" },
128
+ "current_metrics": { "$ref": "#/$defs/metrics" },
129
+ "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } },
130
+ "directives": { "type": "array", "items": { "type": "string", "minLength": 1 } }
131
+ }
132
+ }
133
+ }
134
+ }
135
+
@@ -0,0 +1,141 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "schema_version": 1,
4
+ "title": "character-voice-profiles.json",
5
+ "type": "object",
6
+ "required": ["schema_version", "created_at", "selection", "policy", "profiles"],
7
+ "additionalProperties": false,
8
+ "patternProperties": {
9
+ "^_.*$": { "$ref": "#/$defs/comment_value" }
10
+ },
11
+ "propertyNames": {
12
+ "not": { "enum": ["__proto__", "constructor", "prototype"] }
13
+ },
14
+ "properties": {
15
+ "$schema": {
16
+ "type": "string",
17
+ "description": "Optional schema pointer for editor tooling. Example: schemas/character-voice-profiles.schema.json"
18
+ },
19
+ "schema_version": {
20
+ "type": "integer",
21
+ "enum": [1],
22
+ "description": "Schema version of character-voice-profiles.json (SSOT in schemas/character-voice-profiles.schema.json)."
23
+ },
24
+ "created_at": { "type": "string", "format": "date-time" },
25
+ "selection": { "$ref": "#/$defs/selection" },
26
+ "policy": { "$ref": "#/$defs/policy" },
27
+ "profiles": {
28
+ "type": "array",
29
+ "items": { "$ref": "#/$defs/profile" }
30
+ }
31
+ },
32
+ "$defs": {
33
+ "comment_value": {
34
+ "description": "Free-form comment fields supported by templates/specs.",
35
+ "type": ["string", "number", "boolean", "null", "object", "array"]
36
+ },
37
+ "selection": {
38
+ "type": "object",
39
+ "required": ["protagonist_id"],
40
+ "additionalProperties": false,
41
+ "properties": {
42
+ "protagonist_id": { "type": "string", "minLength": 1 },
43
+ "core_cast_ids": { "type": "array", "items": { "type": "string", "minLength": 1 } }
44
+ }
45
+ },
46
+ "thresholds": {
47
+ "type": "object",
48
+ "required": [
49
+ "avg_dialogue_chars_ratio_low",
50
+ "avg_dialogue_chars_ratio_high",
51
+ "exclamation_per_100_chars_delta",
52
+ "question_per_100_chars_delta",
53
+ "ellipsis_per_100_chars_delta",
54
+ "signature_overlap_min"
55
+ ],
56
+ "additionalProperties": false,
57
+ "properties": {
58
+ "avg_dialogue_chars_ratio_low": { "type": "number", "exclusiveMinimum": 0 },
59
+ "avg_dialogue_chars_ratio_high": { "type": "number", "exclusiveMinimum": 0 },
60
+ "exclamation_per_100_chars_delta": { "type": "number", "minimum": 0 },
61
+ "question_per_100_chars_delta": { "type": "number", "minimum": 0 },
62
+ "ellipsis_per_100_chars_delta": { "type": "number", "minimum": 0 },
63
+ "signature_overlap_min": { "type": "number", "minimum": 0, "maximum": 1 }
64
+ }
65
+ },
66
+ "policy": {
67
+ "type": "object",
68
+ "required": ["window_chapters", "min_dialogue_samples", "drift_thresholds", "recovery_thresholds"],
69
+ "additionalProperties": false,
70
+ "properties": {
71
+ "window_chapters": { "type": "integer", "minimum": 1 },
72
+ "min_dialogue_samples": { "type": "integer", "minimum": 1 },
73
+ "drift_thresholds": { "$ref": "#/$defs/thresholds" },
74
+ "recovery_thresholds": { "$ref": "#/$defs/thresholds" }
75
+ }
76
+ },
77
+ "metrics": {
78
+ "type": "object",
79
+ "required": [
80
+ "dialogue_samples",
81
+ "dialogue_chars",
82
+ "dialogue_len_avg",
83
+ "dialogue_len_p25",
84
+ "dialogue_len_p50",
85
+ "dialogue_len_p75",
86
+ "sentence_len_avg",
87
+ "sentence_len_p25",
88
+ "sentence_len_p50",
89
+ "sentence_len_p75",
90
+ "exclamation_per_100_chars",
91
+ "question_per_100_chars",
92
+ "ellipsis_per_100_chars"
93
+ ],
94
+ "additionalProperties": false,
95
+ "properties": {
96
+ "dialogue_samples": { "type": "integer", "minimum": 0 },
97
+ "dialogue_chars": { "type": "integer", "minimum": 0 },
98
+ "dialogue_len_avg": { "type": "number", "minimum": 0 },
99
+ "dialogue_len_p25": { "type": "integer", "minimum": 0 },
100
+ "dialogue_len_p50": { "type": "integer", "minimum": 0 },
101
+ "dialogue_len_p75": { "type": "integer", "minimum": 0 },
102
+ "sentence_len_avg": { "type": "number", "minimum": 0 },
103
+ "sentence_len_p25": { "type": "integer", "minimum": 0 },
104
+ "sentence_len_p50": { "type": "integer", "minimum": 0 },
105
+ "sentence_len_p75": { "type": "integer", "minimum": 0 },
106
+ "exclamation_per_100_chars": { "type": "number", "minimum": 0 },
107
+ "question_per_100_chars": { "type": "number", "minimum": 0 },
108
+ "ellipsis_per_100_chars": { "type": "number", "minimum": 0 }
109
+ }
110
+ },
111
+ "profile": {
112
+ "type": "object",
113
+ "required": ["character_id", "display_name", "baseline_range", "baseline_metrics", "signature_phrases"],
114
+ "additionalProperties": false,
115
+ "patternProperties": {
116
+ "^_.*$": { "$ref": "#/$defs/comment_value" }
117
+ },
118
+ "propertyNames": {
119
+ "not": { "enum": ["__proto__", "constructor", "prototype"] }
120
+ },
121
+ "properties": {
122
+ "character_id": { "type": "string", "minLength": 1 },
123
+ "display_name": { "type": "string", "minLength": 1 },
124
+ "name_variants": { "type": "array", "items": { "type": "string", "minLength": 1 } },
125
+ "baseline_range": {
126
+ "type": "object",
127
+ "required": ["chapter_start", "chapter_end"],
128
+ "additionalProperties": false,
129
+ "properties": {
130
+ "chapter_start": { "type": "integer", "minimum": 1 },
131
+ "chapter_end": { "type": "integer", "minimum": 1 }
132
+ }
133
+ },
134
+ "baseline_metrics": { "$ref": "#/$defs/metrics" },
135
+ "signature_phrases": { "type": "array", "items": { "type": "string", "minLength": 1 } },
136
+ "taboo_phrases": { "type": "array", "items": { "type": "string", "minLength": 1 } }
137
+ }
138
+ }
139
+ }
140
+ }
141
+
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "schema_version": 1,
4
+ "title": "engagement-metrics.jsonl record",
5
+ "type": "object",
6
+ "required": [
7
+ "schema_version",
8
+ "generated_at",
9
+ "chapter",
10
+ "volume",
11
+ "word_count",
12
+ "plot_progression_beats",
13
+ "conflict_intensity",
14
+ "payoff_score",
15
+ "new_info_load_score",
16
+ "notes"
17
+ ],
18
+ "additionalProperties": false,
19
+ "propertyNames": {
20
+ "not": { "enum": ["__proto__", "constructor", "prototype"] }
21
+ },
22
+ "properties": {
23
+ "schema_version": {
24
+ "type": "integer",
25
+ "enum": [1],
26
+ "description": "Schema version of each engagement-metrics.jsonl record."
27
+ },
28
+ "generated_at": { "type": "string", "format": "date-time" },
29
+ "chapter": { "type": "integer", "minimum": 1 },
30
+ "volume": { "type": "integer", "minimum": 0 },
31
+ "word_count": { "type": "integer", "minimum": 0 },
32
+ "plot_progression_beats": { "type": "integer", "minimum": 0 },
33
+ "conflict_intensity": { "type": "integer", "minimum": 1, "maximum": 5 },
34
+ "payoff_score": { "type": "integer", "minimum": 1, "maximum": 5 },
35
+ "new_info_load_score": { "type": "integer", "minimum": 1, "maximum": 5 },
36
+ "notes": { "type": "string", "minLength": 1, "pattern": "\\S", "maxLength": 320 }
37
+ }
38
+ }
@@ -0,0 +1,108 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "schema_version": 1,
4
+ "title": "hook-ledger.json",
5
+ "type": "object",
6
+ "required": ["schema_version", "entries"],
7
+ "additionalProperties": false,
8
+ "patternProperties": {
9
+ "^_.*$": { "$ref": "#/$defs/comment_value" }
10
+ },
11
+ "properties": {
12
+ "$schema": {
13
+ "type": "string",
14
+ "description": "Optional schema pointer for editor tooling. Example: schemas/hook-ledger.schema.json"
15
+ },
16
+ "schema_version": {
17
+ "type": "integer",
18
+ "enum": [1],
19
+ "description": "Schema version of hook-ledger.json (SSOT in schemas/hook-ledger.schema.json)."
20
+ },
21
+ "entries": {
22
+ "type": "array",
23
+ "items": { "$ref": "#/$defs/hook_ledger_entry" }
24
+ }
25
+ },
26
+ "$defs": {
27
+ "comment_value": {
28
+ "description": "Free-form comment fields supported by templates/specs.",
29
+ "type": ["string", "number", "boolean", "null", "object", "array"]
30
+ },
31
+ "hook_ledger_entry": {
32
+ "type": "object",
33
+ "required": [
34
+ "id",
35
+ "chapter",
36
+ "hook_type",
37
+ "hook_strength",
38
+ "promise_text",
39
+ "status",
40
+ "fulfillment_window",
41
+ "fulfilled_chapter",
42
+ "created_at",
43
+ "updated_at"
44
+ ],
45
+ "additionalProperties": false,
46
+ "patternProperties": {
47
+ "^_.*$": { "$ref": "#/$defs/comment_value" }
48
+ },
49
+ "properties": {
50
+ "id": { "type": "string", "minLength": 1 },
51
+ "chapter": { "type": "integer", "minimum": 1 },
52
+ "hook_type": { "type": "string", "minLength": 1 },
53
+ "hook_strength": { "type": "integer", "minimum": 1, "maximum": 5 },
54
+ "promise_text": { "type": "string", "minLength": 1 },
55
+ "status": { "type": "string", "enum": ["open", "fulfilled", "lapsed"] },
56
+ "fulfillment_window": {
57
+ "type": "array",
58
+ "minItems": 2,
59
+ "maxItems": 2,
60
+ "items": { "type": "integer", "minimum": 1 }
61
+ },
62
+ "fulfilled_chapter": {
63
+ "type": ["integer", "null"],
64
+ "minimum": 1
65
+ },
66
+ "created_at": { "type": "string", "format": "date-time" },
67
+ "updated_at": { "type": "string", "format": "date-time" },
68
+ "evidence_snippet": { "type": "string" },
69
+ "sources": {
70
+ "type": "object",
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "eval_path": { "type": "string", "minLength": 1 }
74
+ }
75
+ },
76
+ "links": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "properties": {
80
+ "promise_ids": {
81
+ "type": "array",
82
+ "items": { "type": "string", "minLength": 1 }
83
+ },
84
+ "foreshadowing_ids": {
85
+ "type": "array",
86
+ "items": { "type": "string", "minLength": 1 }
87
+ }
88
+ }
89
+ },
90
+ "history": {
91
+ "type": "array",
92
+ "items": {
93
+ "type": "object",
94
+ "required": ["at", "chapter", "action"],
95
+ "additionalProperties": false,
96
+ "properties": {
97
+ "at": { "type": "string", "format": "date-time" },
98
+ "chapter": { "type": "integer", "minimum": 1 },
99
+ "action": { "type": "string", "minLength": 1 },
100
+ "detail": { "type": "string" }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+