ghost-reader 0.0.1__tar.gz

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 (51) hide show
  1. ghost_reader-0.0.1/LICENSE +21 -0
  2. ghost_reader-0.0.1/PKG-INFO +221 -0
  3. ghost_reader-0.0.1/README.md +193 -0
  4. ghost_reader-0.0.1/pyproject.toml +60 -0
  5. ghost_reader-0.0.1/setup.cfg +4 -0
  6. ghost_reader-0.0.1/setup.py +21 -0
  7. ghost_reader-0.0.1/src/ghost_reader/.release-version +1 -0
  8. ghost_reader-0.0.1/src/ghost_reader/__init__.py +3 -0
  9. ghost_reader-0.0.1/src/ghost_reader/agent_loader.py +64 -0
  10. ghost_reader-0.0.1/src/ghost_reader/cli.py +1124 -0
  11. ghost_reader-0.0.1/src/ghost_reader/constants.py +75 -0
  12. ghost_reader-0.0.1/src/ghost_reader/defaults/__init__.py +1 -0
  13. ghost_reader-0.0.1/src/ghost_reader/defaults/personas/__init__.py +1 -0
  14. ghost_reader-0.0.1/src/ghost_reader/defaults/personas/dex.yaml +30 -0
  15. ghost_reader-0.0.1/src/ghost_reader/defaults/personas/elena.yaml +30 -0
  16. ghost_reader-0.0.1/src/ghost_reader/defaults/personas/mara.yaml +30 -0
  17. ghost_reader-0.0.1/src/ghost_reader/defaults/personas/pip.yaml +30 -0
  18. ghost_reader-0.0.1/src/ghost_reader/defaults/personas/rook.yaml +30 -0
  19. ghost_reader-0.0.1/src/ghost_reader/defaults/templates/__init__.py +1 -0
  20. ghost_reader-0.0.1/src/ghost_reader/defaults/templates/blog-review.html +384 -0
  21. ghost_reader-0.0.1/src/ghost_reader/defaults/templates/report.html +1293 -0
  22. ghost_reader-0.0.1/src/ghost_reader/dialogue.py +283 -0
  23. ghost_reader-0.0.1/src/ghost_reader/errors.py +2 -0
  24. ghost_reader-0.0.1/src/ghost_reader/feedback_store.py +56 -0
  25. ghost_reader-0.0.1/src/ghost_reader/io.py +59 -0
  26. ghost_reader-0.0.1/src/ghost_reader/models.py +227 -0
  27. ghost_reader-0.0.1/src/ghost_reader/paths.py +68 -0
  28. ghost_reader-0.0.1/src/ghost_reader/project.py +277 -0
  29. ghost_reader-0.0.1/src/ghost_reader/release.py +56 -0
  30. ghost_reader-0.0.1/src/ghost_reader/report.py +264 -0
  31. ghost_reader-0.0.1/src/ghost_reader/reviews.py +89 -0
  32. ghost_reader-0.0.1/src/ghost_reader/revision.py +165 -0
  33. ghost_reader-0.0.1/src/ghost_reader/round.py +155 -0
  34. ghost_reader-0.0.1/src/ghost_reader/server.py +281 -0
  35. ghost_reader-0.0.1/src/ghost_reader/sync.py +112 -0
  36. ghost_reader-0.0.1/src/ghost_reader/telemetry.py +111 -0
  37. ghost_reader-0.0.1/src/ghost_reader/time.py +20 -0
  38. ghost_reader-0.0.1/src/ghost_reader/validators.py +66 -0
  39. ghost_reader-0.0.1/src/ghost_reader/verify.py +255 -0
  40. ghost_reader-0.0.1/src/ghost_reader.egg-info/PKG-INFO +221 -0
  41. ghost_reader-0.0.1/src/ghost_reader.egg-info/SOURCES.txt +49 -0
  42. ghost_reader-0.0.1/src/ghost_reader.egg-info/dependency_links.txt +1 -0
  43. ghost_reader-0.0.1/src/ghost_reader.egg-info/entry_points.txt +2 -0
  44. ghost_reader-0.0.1/src/ghost_reader.egg-info/requires.txt +6 -0
  45. ghost_reader-0.0.1/src/ghost_reader.egg-info/top_level.txt +1 -0
  46. ghost_reader-0.0.1/tests/test_cli_workflow.py +1144 -0
  47. ghost_reader-0.0.1/tests/test_e2e_workflow.py +372 -0
  48. ghost_reader-0.0.1/tests/test_feedback_store.py +144 -0
  49. ghost_reader-0.0.1/tests/test_persona_consistency.py +251 -0
  50. ghost_reader-0.0.1/tests/test_review_quality.py +275 -0
  51. ghost_reader-0.0.1/tests/test_server.py +376 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Chin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghost-reader
3
+ Version: 0.0.1
4
+ Summary: Persona-driven fiction review simulator CLI
5
+ Author-email: Kevin Chin <kevin@kevindchin.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/clchinkc/ghost-reader
8
+ Project-URL: Repository, https://github.com/clchinkc/ghost-reader
9
+ Project-URL: Documentation, https://github.com/clchinkc/ghost-reader/blob/main/agent-setup.md
10
+ Keywords: fiction,beta-reader,writing,feedback,review,persona
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Text Processing :: General
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: PyYAML>=6.0.2
23
+ Requires-Dist: pydantic>=2.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # Ghost Reader CLI
30
+
31
+ Local-first persona-driven fiction review simulator for structured beta-reader-style feedback.
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/ghost-reader)](https://pypi.org/project/ghost-reader/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
35
+
36
+ **Source:** https://github.com/clchinkc/ghost-reader
37
+
38
+ ## For AI agents
39
+
40
+ Agents with web fetch and shell access should start here:
41
+
42
+ 1. Fetch [agent-setup.md](agent-setup.md) (short bootstrap; do not scrape HTML report templates).
43
+ 2. Install [uv](https://docs.astral.sh/uv/) if needed, then install the CLI from `main`:
44
+
45
+ ```bash
46
+ curl -LsSf https://astral.sh/uv/install.sh | sh
47
+ uv tool install 'git+ssh://git@github.com/clchinkc/ghost-reader.git'
48
+ ghost-reader help
49
+ ghost-reader update check
50
+ ```
51
+
52
+ Requires access to the private repo (SSH key or `gh auth login` for HTTPS git URLs).
53
+
54
+ 3. Install the skill: copy `skills/ghost-reader/` into your harness skill directory, or invoke `Skill(ghost-reader)` when available.
55
+ 4. Read the operational contract: [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md) (workflow) and [skills/ghost-reader/reference.md](skills/ghost-reader/reference.md) (data model, Phase 3 roadmap).
56
+
57
+ ## Documentation map
58
+
59
+ | Document | Audience | Role |
60
+ |----------|----------|------|
61
+ | [agent-setup.md](agent-setup.md) | Agents | Bootstrap, install, 11-step workflow |
62
+ | [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md) | Agents | Operational contract |
63
+ | [skills/ghost-reader/reference.md](skills/ghost-reader/reference.md) | Agents | Schemas, paths, HTTP API |
64
+ | [llms.txt](llms.txt) | Agents | Command index |
65
+ | [examples/README.md](examples/README.md) | Everyone | Canonical sample trace (committed) |
66
+ | [AGENTS.md](AGENTS.md) / [CLAUDE.md](CLAUDE.md) | Harness | Pointers to the above |
67
+
68
+ No hosted web service, no authentication server, no API keys. The `serve` command binds to `127.0.0.1` only — local-first by design.
69
+
70
+ ## Current status (Phase 2)
71
+
72
+ - Local CLI with iterative refinement rounds (`round init`, `refine snapshot`).
73
+ - Structured YAML for personas, reviews, feedback, and revision prompts.
74
+ - Local HTML reports from `report/payload.json` and swappable templates.
75
+ - Bidirectional feedback via `ghost-reader serve` (`POST /api/feedback`, `/api/dialogue`).
76
+ - Persona dialogue (`dialogue append`, `dialogue context`).
77
+ - Content-safe local telemetry and local sync bundles.
78
+ - Verification statuses (per session): `review_ready_feedback_pending` → `refinement_complete` → `phase1_complete` (these are **verify** outputs, not separate product phases).
79
+
80
+ Artifact contracts: review and feedback **schema v2 only**; sessions use **round-*** directories (no flat legacy layout).
81
+
82
+ No hosted web app. Phase 3 persona recommendation is **planned** (not in the CLI yet); see roadmap below.
83
+
84
+ ## Install (users)
85
+
86
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) first.
87
+
88
+ | Method | Command |
89
+ |--------|---------|
90
+ | **pipx** (recommended) | `pipx install ghost-reader` |
91
+ | **uv tool** (recommended) | `uv tool install ghost-reader` |
92
+ | **uvx** (no install) | `uvx ghost-reader help` |
93
+ | **Git (private repo)** | `uv tool install 'git+ssh://git@github.com/clchinkc/ghost-reader.git'` |
94
+ | **Dev (clone)** | `git clone git@github.com:clchinkc/ghost-reader.git && cd ghost-reader && uv sync --extra dev` then `uv run ghost-reader help` |
95
+
96
+ `uv tool install` places `ghost-reader` on your PATH (typically `~/.local/bin`).
97
+ `pipx install` places it in a pipx-managed virtualenv.
98
+
99
+ ```bash
100
+ ghost-reader init # run from your story project directory
101
+ ghost-reader help
102
+ ```
103
+
104
+ ### Build wheel locally
105
+
106
+ ```bash
107
+ uv build
108
+ uv tool install dist/*.whl
109
+ ```
110
+
111
+ ### Version management
112
+
113
+ Published to PyPI via OIDC trusted publishing on `git tag v*`. The version in `pyproject.toml` is the single source of truth — keep it in sync with `src/ghost_reader/__init__.py` and `src/ghost_reader/.release-version`.
114
+
115
+ ## Sample trace
116
+
117
+ The repo includes a full **review_ready_feedback_pending** example at [examples/sample-trace/](examples/sample-trace/). See [examples/README.md](examples/README.md).
118
+
119
+ ```bash
120
+ cd examples/sample-trace
121
+ export GHOST_READER_HOME="$PWD/.ghostreader-home"
122
+ ghost-reader init
123
+ ghost-reader verify --session chapter-1
124
+ ghost-reader serve --session chapter-1
125
+ ```
126
+
127
+ Regenerate after contract changes: `uv run python scripts/refresh_sample_trace.py`
128
+
129
+ ## Quick workflow
130
+
131
+ Run all commands from the **story project directory** (where `.ghostreader/` lives), not from a clone of this repo unless you are developing Ghost Reader itself.
132
+
133
+ ```bash
134
+ cd /path/to/your-story-project
135
+ ghost-reader init
136
+ ghost-reader session create --id chapter-3 --story-unit "chapter 3" --personas mara,dex,pip
137
+ # LLM writes review YAML per persona, then:
138
+ ghost-reader artifact write --session chapter-3 --type review --persona mara --from mara.review.yaml
139
+ ghost-reader render --session chapter-3
140
+ ghost-reader verify --session chapter-3
141
+ ghost-reader serve --session chapter-3 --timeout 600
142
+ ```
143
+
144
+ After reviews render, **`serve` is the primary user path**: the user selects items in the browser and clicks **Submit Feedback**, which writes `feedback/feedback.yaml`. The agent then polls `ghost-reader feedback summary --session chapter-3`, runs `ghost-reader prompt revision`, rerenders, and verifies `phase1_complete`.
145
+
146
+ If `serve` is unavailable, open `file://` on `.ghostreader/sessions/<id>/round-<n>/report/index.html` and use the copy-paste **Ghost Reader Prompt** fallback.
147
+
148
+ ### Pasted text (no story repo yet)
149
+
150
+ ```bash
151
+ mkdir -p ~/ghost-reader-projects/my-chapter
152
+ cd ~/ghost-reader-projects/my-chapter
153
+ export GHOST_READER_HOME="$PWD/.ghostreader-home"
154
+ ghost-reader init
155
+ ghost-reader session create --id ch1 --story-unit "chapter 1"
156
+ ```
157
+
158
+ Use `/tmp/ghost-reader-*` only for disposable tests.
159
+
160
+ ### Developer setup (this repo)
161
+
162
+ ```bash
163
+ git clone git@github.com:clchinkc/ghost-reader.git
164
+ cd ghost-reader
165
+ uv sync --extra dev
166
+ uv run ghost-reader update check
167
+
168
+ cd /path/to/story-project
169
+ uv run --project /path/to/ghost-reader --no-editable ghost-reader init
170
+ ```
171
+
172
+ ## Report design contract
173
+
174
+ - Source text stays readable; annotations tie to cited passages.
175
+ - Full reader reasons live in persona sections, not only tooltips.
176
+ - Quiet, dense layout; no marketing-style chrome.
177
+
178
+ ## Test
179
+
180
+ ```bash
181
+ uv lock --check
182
+ uv run --with ruff ruff check .
183
+ uv run --with pytest pytest
184
+ ```
185
+
186
+ ## Code structure
187
+
188
+ | Module | Role |
189
+ |--------|------|
190
+ | `cli.py` | Command surface |
191
+ | `server.py` | Local bidirectional HTTP (`serve`) |
192
+ | `feedback_store.py` | Shared feedback persist + telemetry |
193
+ | `validators.py` | Artifact contracts |
194
+ | `report.py` | HTML from `payload.json` |
195
+ | `verify.py` | Session gates |
196
+ | `telemetry.py` / `sync.py` | Local events and export bundles |
197
+
198
+ ## Roadmap
199
+
200
+ ### Phase 1 — local CLI (shipped)
201
+ Local-first persona-driven review, structured YAML, HTML report, validation gates.
202
+
203
+ ### Phase 2 — iterative rounds (shipped)
204
+ Multi-round workflow, dialogue with personas, bidirectional serve UI, content-safe telemetry, local sync bundle. See [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md).
205
+
206
+ ### Phase 2.5 — CLI feedback-loop extension (planned, doc-first)
207
+ Extend the existing CLI loop (`serve` → `feedback add/summary` → `prompt revision`) for future remote-catalog sync without breaking local-first operation. Outbound: content-safe events + selected persona IDs + acted-on feedback categories + catalog refs + recommendation receipts. Inbound: catalog metadata + recommendation candidates + receipt IDs. **Raw story text never syncs by default.** No new command surface in this phase — `sync` is extended in docs first, then code.
208
+
209
+ ### Phase 3 — hosted option + remote catalog + recommendation engine (planned)
210
+ Persona recommendation as a spatial HTML comparison artifact (`recommendation.html`) and `ghost-reader recommend` are **not implemented yet**. Recommendation scores, usage analytics, and marketplace ranking live **server-side** (not in local persona YAML) per D003. Until then, users choose personas manually (`mara`, `dex`, `pip`, or optional `elena`, `rook`; or any custom persona validated via `persona show`). Design notes: [skills/ghost-reader/reference.md](skills/ghost-reader/reference.md#phase-3-persona-recommendation-planned).
211
+
212
+ ## Custom personas (skill-mediated, not a CLI command)
213
+
214
+ There is **no `ghost-reader persona import` or `persona export` command** in any phase. Custom personas are created by the LLM, validated by the CLI:
215
+
216
+ 1. The skill emits canonical YAML matching the persona schema.
217
+ 2. Place the file at `$GHOST_READER_HOME/personas/<id>.yaml`.
218
+ 3. CLI validates via `ghost-reader persona show <id>` (calls `read_persona()` and the Pydantic schema).
219
+ 4. The persona is then usable in `ghost-reader session create --personas <id>,...`.
220
+
221
+ Local persona YAML may carry `source`, `catalog_ref`, `tags` for provenance. It must NOT carry `matching`, `analytics`, `weights`, `telemetry`, `posture`, or `tolerance` (enforced in `models.py:check_forbidden_fields`). See [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md#custom-persona-ingestion-llm-driven-not-a-cli-command) and [reference.md](skills/ghost-reader/reference.md#custom-personas-skill-mediated-not-a-cli-command).
@@ -0,0 +1,193 @@
1
+ # Ghost Reader CLI
2
+
3
+ Local-first persona-driven fiction review simulator for structured beta-reader-style feedback.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/ghost-reader)](https://pypi.org/project/ghost-reader/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ **Source:** https://github.com/clchinkc/ghost-reader
9
+
10
+ ## For AI agents
11
+
12
+ Agents with web fetch and shell access should start here:
13
+
14
+ 1. Fetch [agent-setup.md](agent-setup.md) (short bootstrap; do not scrape HTML report templates).
15
+ 2. Install [uv](https://docs.astral.sh/uv/) if needed, then install the CLI from `main`:
16
+
17
+ ```bash
18
+ curl -LsSf https://astral.sh/uv/install.sh | sh
19
+ uv tool install 'git+ssh://git@github.com/clchinkc/ghost-reader.git'
20
+ ghost-reader help
21
+ ghost-reader update check
22
+ ```
23
+
24
+ Requires access to the private repo (SSH key or `gh auth login` for HTTPS git URLs).
25
+
26
+ 3. Install the skill: copy `skills/ghost-reader/` into your harness skill directory, or invoke `Skill(ghost-reader)` when available.
27
+ 4. Read the operational contract: [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md) (workflow) and [skills/ghost-reader/reference.md](skills/ghost-reader/reference.md) (data model, Phase 3 roadmap).
28
+
29
+ ## Documentation map
30
+
31
+ | Document | Audience | Role |
32
+ |----------|----------|------|
33
+ | [agent-setup.md](agent-setup.md) | Agents | Bootstrap, install, 11-step workflow |
34
+ | [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md) | Agents | Operational contract |
35
+ | [skills/ghost-reader/reference.md](skills/ghost-reader/reference.md) | Agents | Schemas, paths, HTTP API |
36
+ | [llms.txt](llms.txt) | Agents | Command index |
37
+ | [examples/README.md](examples/README.md) | Everyone | Canonical sample trace (committed) |
38
+ | [AGENTS.md](AGENTS.md) / [CLAUDE.md](CLAUDE.md) | Harness | Pointers to the above |
39
+
40
+ No hosted web service, no authentication server, no API keys. The `serve` command binds to `127.0.0.1` only — local-first by design.
41
+
42
+ ## Current status (Phase 2)
43
+
44
+ - Local CLI with iterative refinement rounds (`round init`, `refine snapshot`).
45
+ - Structured YAML for personas, reviews, feedback, and revision prompts.
46
+ - Local HTML reports from `report/payload.json` and swappable templates.
47
+ - Bidirectional feedback via `ghost-reader serve` (`POST /api/feedback`, `/api/dialogue`).
48
+ - Persona dialogue (`dialogue append`, `dialogue context`).
49
+ - Content-safe local telemetry and local sync bundles.
50
+ - Verification statuses (per session): `review_ready_feedback_pending` → `refinement_complete` → `phase1_complete` (these are **verify** outputs, not separate product phases).
51
+
52
+ Artifact contracts: review and feedback **schema v2 only**; sessions use **round-*** directories (no flat legacy layout).
53
+
54
+ No hosted web app. Phase 3 persona recommendation is **planned** (not in the CLI yet); see roadmap below.
55
+
56
+ ## Install (users)
57
+
58
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) first.
59
+
60
+ | Method | Command |
61
+ |--------|---------|
62
+ | **pipx** (recommended) | `pipx install ghost-reader` |
63
+ | **uv tool** (recommended) | `uv tool install ghost-reader` |
64
+ | **uvx** (no install) | `uvx ghost-reader help` |
65
+ | **Git (private repo)** | `uv tool install 'git+ssh://git@github.com/clchinkc/ghost-reader.git'` |
66
+ | **Dev (clone)** | `git clone git@github.com:clchinkc/ghost-reader.git && cd ghost-reader && uv sync --extra dev` then `uv run ghost-reader help` |
67
+
68
+ `uv tool install` places `ghost-reader` on your PATH (typically `~/.local/bin`).
69
+ `pipx install` places it in a pipx-managed virtualenv.
70
+
71
+ ```bash
72
+ ghost-reader init # run from your story project directory
73
+ ghost-reader help
74
+ ```
75
+
76
+ ### Build wheel locally
77
+
78
+ ```bash
79
+ uv build
80
+ uv tool install dist/*.whl
81
+ ```
82
+
83
+ ### Version management
84
+
85
+ Published to PyPI via OIDC trusted publishing on `git tag v*`. The version in `pyproject.toml` is the single source of truth — keep it in sync with `src/ghost_reader/__init__.py` and `src/ghost_reader/.release-version`.
86
+
87
+ ## Sample trace
88
+
89
+ The repo includes a full **review_ready_feedback_pending** example at [examples/sample-trace/](examples/sample-trace/). See [examples/README.md](examples/README.md).
90
+
91
+ ```bash
92
+ cd examples/sample-trace
93
+ export GHOST_READER_HOME="$PWD/.ghostreader-home"
94
+ ghost-reader init
95
+ ghost-reader verify --session chapter-1
96
+ ghost-reader serve --session chapter-1
97
+ ```
98
+
99
+ Regenerate after contract changes: `uv run python scripts/refresh_sample_trace.py`
100
+
101
+ ## Quick workflow
102
+
103
+ Run all commands from the **story project directory** (where `.ghostreader/` lives), not from a clone of this repo unless you are developing Ghost Reader itself.
104
+
105
+ ```bash
106
+ cd /path/to/your-story-project
107
+ ghost-reader init
108
+ ghost-reader session create --id chapter-3 --story-unit "chapter 3" --personas mara,dex,pip
109
+ # LLM writes review YAML per persona, then:
110
+ ghost-reader artifact write --session chapter-3 --type review --persona mara --from mara.review.yaml
111
+ ghost-reader render --session chapter-3
112
+ ghost-reader verify --session chapter-3
113
+ ghost-reader serve --session chapter-3 --timeout 600
114
+ ```
115
+
116
+ After reviews render, **`serve` is the primary user path**: the user selects items in the browser and clicks **Submit Feedback**, which writes `feedback/feedback.yaml`. The agent then polls `ghost-reader feedback summary --session chapter-3`, runs `ghost-reader prompt revision`, rerenders, and verifies `phase1_complete`.
117
+
118
+ If `serve` is unavailable, open `file://` on `.ghostreader/sessions/<id>/round-<n>/report/index.html` and use the copy-paste **Ghost Reader Prompt** fallback.
119
+
120
+ ### Pasted text (no story repo yet)
121
+
122
+ ```bash
123
+ mkdir -p ~/ghost-reader-projects/my-chapter
124
+ cd ~/ghost-reader-projects/my-chapter
125
+ export GHOST_READER_HOME="$PWD/.ghostreader-home"
126
+ ghost-reader init
127
+ ghost-reader session create --id ch1 --story-unit "chapter 1"
128
+ ```
129
+
130
+ Use `/tmp/ghost-reader-*` only for disposable tests.
131
+
132
+ ### Developer setup (this repo)
133
+
134
+ ```bash
135
+ git clone git@github.com:clchinkc/ghost-reader.git
136
+ cd ghost-reader
137
+ uv sync --extra dev
138
+ uv run ghost-reader update check
139
+
140
+ cd /path/to/story-project
141
+ uv run --project /path/to/ghost-reader --no-editable ghost-reader init
142
+ ```
143
+
144
+ ## Report design contract
145
+
146
+ - Source text stays readable; annotations tie to cited passages.
147
+ - Full reader reasons live in persona sections, not only tooltips.
148
+ - Quiet, dense layout; no marketing-style chrome.
149
+
150
+ ## Test
151
+
152
+ ```bash
153
+ uv lock --check
154
+ uv run --with ruff ruff check .
155
+ uv run --with pytest pytest
156
+ ```
157
+
158
+ ## Code structure
159
+
160
+ | Module | Role |
161
+ |--------|------|
162
+ | `cli.py` | Command surface |
163
+ | `server.py` | Local bidirectional HTTP (`serve`) |
164
+ | `feedback_store.py` | Shared feedback persist + telemetry |
165
+ | `validators.py` | Artifact contracts |
166
+ | `report.py` | HTML from `payload.json` |
167
+ | `verify.py` | Session gates |
168
+ | `telemetry.py` / `sync.py` | Local events and export bundles |
169
+
170
+ ## Roadmap
171
+
172
+ ### Phase 1 — local CLI (shipped)
173
+ Local-first persona-driven review, structured YAML, HTML report, validation gates.
174
+
175
+ ### Phase 2 — iterative rounds (shipped)
176
+ Multi-round workflow, dialogue with personas, bidirectional serve UI, content-safe telemetry, local sync bundle. See [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md).
177
+
178
+ ### Phase 2.5 — CLI feedback-loop extension (planned, doc-first)
179
+ Extend the existing CLI loop (`serve` → `feedback add/summary` → `prompt revision`) for future remote-catalog sync without breaking local-first operation. Outbound: content-safe events + selected persona IDs + acted-on feedback categories + catalog refs + recommendation receipts. Inbound: catalog metadata + recommendation candidates + receipt IDs. **Raw story text never syncs by default.** No new command surface in this phase — `sync` is extended in docs first, then code.
180
+
181
+ ### Phase 3 — hosted option + remote catalog + recommendation engine (planned)
182
+ Persona recommendation as a spatial HTML comparison artifact (`recommendation.html`) and `ghost-reader recommend` are **not implemented yet**. Recommendation scores, usage analytics, and marketplace ranking live **server-side** (not in local persona YAML) per D003. Until then, users choose personas manually (`mara`, `dex`, `pip`, or optional `elena`, `rook`; or any custom persona validated via `persona show`). Design notes: [skills/ghost-reader/reference.md](skills/ghost-reader/reference.md#phase-3-persona-recommendation-planned).
183
+
184
+ ## Custom personas (skill-mediated, not a CLI command)
185
+
186
+ There is **no `ghost-reader persona import` or `persona export` command** in any phase. Custom personas are created by the LLM, validated by the CLI:
187
+
188
+ 1. The skill emits canonical YAML matching the persona schema.
189
+ 2. Place the file at `$GHOST_READER_HOME/personas/<id>.yaml`.
190
+ 3. CLI validates via `ghost-reader persona show <id>` (calls `read_persona()` and the Pydantic schema).
191
+ 4. The persona is then usable in `ghost-reader session create --personas <id>,...`.
192
+
193
+ Local persona YAML may carry `source`, `catalog_ref`, `tags` for provenance. It must NOT carry `matching`, `analytics`, `weights`, `telemetry`, `posture`, or `tolerance` (enforced in `models.py:check_forbidden_fields`). See [skills/ghost-reader/SKILL.md](skills/ghost-reader/SKILL.md#custom-persona-ingestion-llm-driven-not-a-cli-command) and [reference.md](skills/ghost-reader/reference.md#custom-personas-skill-mediated-not-a-cli-command).
@@ -0,0 +1,60 @@
1
+ [project]
2
+ name = "ghost-reader"
3
+ version = "0.0.1"
4
+ description = "Persona-driven fiction review simulator CLI"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [
10
+ {name = "Kevin Chin", email = "kevin@kevindchin.com"},
11
+ ]
12
+ keywords = ["fiction", "beta-reader", "writing", "feedback", "review", "persona"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: End Users/Desktop",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Text Processing :: General",
22
+ ]
23
+ dependencies = [
24
+ "PyYAML>=6.0.2",
25
+ "pydantic>=2.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/clchinkc/ghost-reader"
30
+ Repository = "https://github.com/clchinkc/ghost-reader"
31
+ Documentation = "https://github.com/clchinkc/ghost-reader/blob/main/agent-setup.md"
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=8.0",
36
+ "ruff>=0.4.0",
37
+ ]
38
+
39
+ [project.scripts]
40
+ ghost-reader = "ghost_reader.cli:entrypoint"
41
+
42
+ [build-system]
43
+ requires = ["setuptools>=69", "wheel"]
44
+ build-backend = "setuptools.build_meta"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.setuptools.package-data]
50
+ ghost_reader = [".release-version", "defaults/personas/*.yaml", "defaults/templates/*.html"]
51
+
52
+ [tool.ruff]
53
+ src = ["src"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+ pythonpath = ["src", "."]
58
+ markers = [
59
+ "llm: tests that require a live LLM (Gemini) API",
60
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import tomllib
5
+
6
+ from setuptools import setup
7
+ from setuptools.command.build_py import build_py as _build_py
8
+
9
+
10
+ class build_py(_build_py):
11
+ def run(self) -> None:
12
+ super().run()
13
+ version = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))[
14
+ "project"
15
+ ]["version"]
16
+ target = Path(self.build_lib) / "ghost_reader" / ".release-version"
17
+ target.parent.mkdir(parents=True, exist_ok=True)
18
+ target.write_text(f"{version}\n", encoding="utf-8")
19
+
20
+
21
+ setup(cmdclass={"build_py": build_py})
@@ -0,0 +1,3 @@
1
+ """Ghost Reader local-first CLI package."""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,64 @@
1
+ """Load agent YAML definitions for Ghost Reader personas."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import yaml
7
+
8
+
9
+ def discover_agent_personas(home: Path) -> list[str]:
10
+ """Scan agents/ directory for *.agent.yaml files and extract persona IDs."""
11
+ agents_dir = home / "agents"
12
+ persona_ids: list[str] = []
13
+ if agents_dir.exists():
14
+ for f in sorted(agents_dir.glob("*.agent.yaml")):
15
+ try:
16
+ raw = f.read_text(encoding="utf-8")
17
+ agent = yaml.safe_load(raw)
18
+ pid = (agent.get("metadata") or {}).get("persona_id")
19
+ if pid:
20
+ persona_ids.append(pid)
21
+ except (yaml.YAMLError, OSError, AttributeError):
22
+ continue
23
+ return persona_ids or _legacy_personas()
24
+
25
+
26
+ def _legacy_personas() -> list[str]:
27
+ """Fallback to hardcoded persona list when no agent YAML files found."""
28
+ return ["mara", "dex", "pip", "elena", "rook"]
29
+
30
+
31
+ def load_agent_yaml(home: Path, persona_id: str) -> Optional[dict]:
32
+ """Load an agent YAML for a specific persona ID. Returns parsed dict or None."""
33
+ agents_dir = home / "agents"
34
+ if not agents_dir.exists():
35
+ return None
36
+ for f in sorted(agents_dir.glob("*.agent.yaml")):
37
+ try:
38
+ raw = f.read_text(encoding="utf-8")
39
+ agent = yaml.safe_load(raw)
40
+ pid = (agent.get("metadata") or {}).get("persona_id")
41
+ if pid == persona_id:
42
+ return dict(agent)
43
+ except (yaml.YAMLError, OSError, AttributeError):
44
+ continue
45
+ return None
46
+
47
+
48
+ def load_persona_from_agent(home: Path, persona_id: str) -> Optional[dict]:
49
+ """Load an agent YAML and extract persona-compatible data.
50
+
51
+ Returns a dict with id, source, model, and system_prompt fields,
52
+ suitable for use by read_persona() and review generation.
53
+ """
54
+ agent = load_agent_yaml(home, persona_id)
55
+ if not agent:
56
+ return None
57
+ return {
58
+ "id": persona_id,
59
+ "source": "agent_yaml",
60
+ "model": agent.get("model"),
61
+ "system_prompt": agent.get("system"),
62
+ "tools": agent.get("tools", []),
63
+ "metadata": agent.get("metadata", {}),
64
+ }