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.
- ghost_reader-0.0.1/LICENSE +21 -0
- ghost_reader-0.0.1/PKG-INFO +221 -0
- ghost_reader-0.0.1/README.md +193 -0
- ghost_reader-0.0.1/pyproject.toml +60 -0
- ghost_reader-0.0.1/setup.cfg +4 -0
- ghost_reader-0.0.1/setup.py +21 -0
- ghost_reader-0.0.1/src/ghost_reader/.release-version +1 -0
- ghost_reader-0.0.1/src/ghost_reader/__init__.py +3 -0
- ghost_reader-0.0.1/src/ghost_reader/agent_loader.py +64 -0
- ghost_reader-0.0.1/src/ghost_reader/cli.py +1124 -0
- ghost_reader-0.0.1/src/ghost_reader/constants.py +75 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/__init__.py +1 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/personas/__init__.py +1 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/personas/dex.yaml +30 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/personas/elena.yaml +30 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/personas/mara.yaml +30 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/personas/pip.yaml +30 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/personas/rook.yaml +30 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/templates/__init__.py +1 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/templates/blog-review.html +384 -0
- ghost_reader-0.0.1/src/ghost_reader/defaults/templates/report.html +1293 -0
- ghost_reader-0.0.1/src/ghost_reader/dialogue.py +283 -0
- ghost_reader-0.0.1/src/ghost_reader/errors.py +2 -0
- ghost_reader-0.0.1/src/ghost_reader/feedback_store.py +56 -0
- ghost_reader-0.0.1/src/ghost_reader/io.py +59 -0
- ghost_reader-0.0.1/src/ghost_reader/models.py +227 -0
- ghost_reader-0.0.1/src/ghost_reader/paths.py +68 -0
- ghost_reader-0.0.1/src/ghost_reader/project.py +277 -0
- ghost_reader-0.0.1/src/ghost_reader/release.py +56 -0
- ghost_reader-0.0.1/src/ghost_reader/report.py +264 -0
- ghost_reader-0.0.1/src/ghost_reader/reviews.py +89 -0
- ghost_reader-0.0.1/src/ghost_reader/revision.py +165 -0
- ghost_reader-0.0.1/src/ghost_reader/round.py +155 -0
- ghost_reader-0.0.1/src/ghost_reader/server.py +281 -0
- ghost_reader-0.0.1/src/ghost_reader/sync.py +112 -0
- ghost_reader-0.0.1/src/ghost_reader/telemetry.py +111 -0
- ghost_reader-0.0.1/src/ghost_reader/time.py +20 -0
- ghost_reader-0.0.1/src/ghost_reader/validators.py +66 -0
- ghost_reader-0.0.1/src/ghost_reader/verify.py +255 -0
- ghost_reader-0.0.1/src/ghost_reader.egg-info/PKG-INFO +221 -0
- ghost_reader-0.0.1/src/ghost_reader.egg-info/SOURCES.txt +49 -0
- ghost_reader-0.0.1/src/ghost_reader.egg-info/dependency_links.txt +1 -0
- ghost_reader-0.0.1/src/ghost_reader.egg-info/entry_points.txt +2 -0
- ghost_reader-0.0.1/src/ghost_reader.egg-info/requires.txt +6 -0
- ghost_reader-0.0.1/src/ghost_reader.egg-info/top_level.txt +1 -0
- ghost_reader-0.0.1/tests/test_cli_workflow.py +1144 -0
- ghost_reader-0.0.1/tests/test_e2e_workflow.py +372 -0
- ghost_reader-0.0.1/tests/test_feedback_store.py +144 -0
- ghost_reader-0.0.1/tests/test_persona_consistency.py +251 -0
- ghost_reader-0.0.1/tests/test_review_quality.py +275 -0
- 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
|
+
[](https://pypi.org/project/ghost-reader/)
|
|
34
|
+
[](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
|
+
[](https://pypi.org/project/ghost-reader/)
|
|
6
|
+
[](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,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 @@
|
|
|
1
|
+
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
|
+
}
|