Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
cb5a176
docs: add local development baseline
Apr 26, 2026
ddb532b
upstream: add wizard resumability
Apr 26, 2026
54bcdf6
Revert "upstream: add wizard resumability"
Apr 26, 2026
1166ceb
upstream: sync v1.0.4 changes
Apr 26, 2026
7cd9f60
docs: add project memory for local fork
Apr 26, 2026
cf77e26
feat: add candidate draft workflow foundation
Apr 27, 2026
1a6aa4d
ci: narrow backend checks to verified p1 workflow
Apr 27, 2026
b1d5b83
ci: trigger workflows on workflow changes
Apr 27, 2026
79dfbd4
docs: refresh project memory after ci setup
Apr 27, 2026
e6cbc13
feat: refresh candidate drafts on branch switch
Apr 27, 2026
87891d6
feat: add continuity overview panel
Apr 27, 2026
11ff312
feat: expand continuity overview signals
Apr 27, 2026
a2f7f85
docs: refresh progress memory
Apr 27, 2026
7644f86
feat: add voice lock workbench panel
Apr 27, 2026
ff67b51
feat: link continuity dropouts to relationships
Apr 27, 2026
4a348a7
feat: link voice lock to sandbox
Apr 27, 2026
7ce0905
feat: add continuity action shortcuts
Apr 27, 2026
731d92a
feat: link continuity alerts to candidate rewrites
Apr 27, 2026
62b790b
feat: clarify candidate draft task labels
Apr 27, 2026
351b768
feat: generate from candidate rewrite tasks
Apr 27, 2026
40cdf31
feat: launch rewrite tasks from chapter page
Apr 28, 2026
d5c8796
fix: distinguish rewrite task results
Apr 28, 2026
b560436
feat: add precision rewrite task workflow
Apr 28, 2026
cba8704
feat: add chapter precision rewrite task entry
Apr 28, 2026
593450e
feat: complete external draft workflow
Apr 28, 2026
7de82ec
feat: add power system guardrails
Apr 28, 2026
dcfdbbb
docs: record latest ci status
Apr 28, 2026
35215a1
feat: add structured continuity tracking
Apr 28, 2026
ffe5870
docs: record structured continuity ci
Apr 28, 2026
9a62dcd
feat: enhance candidate draft review workflow
Apr 28, 2026
5e024c1
docs: record candidate workflow ci
Apr 28, 2026
512623e
feat: add configurable model roles
Apr 28, 2026
967ce59
docs: record model role ci
Apr 28, 2026
3f8212c
feat: complete NovelPro candidate workflow
Apr 28, 2026
bbddd4a
docs: record candidate workflow ci
Apr 28, 2026
9902ed3
feat: integrate obsidian memory into pp flow
Apr 28, 2026
976a9df
feat: make obsidian primary memory monitor
Apr 28, 2026
3c2d759
feat: add ai suggestions for novelpro forms
Apr 28, 2026
5dc823d
feat: surface candidate refinement panel
Apr 28, 2026
43df5c8
feat: expose obsidian memory setup
Apr 28, 2026
3636001
docs: add topic idea incubation design
Apr 29, 2026
ffb069f
docs: add topic idea implementation plan
Apr 29, 2026
e327071
feat: add chapter generation style controls
Apr 29, 2026
800bc72
feat: add topic idea market signal workflow
Apr 29, 2026
48ee678
docs: update topic development memory
Apr 29, 2026
d63cbc0
feat: add topic signal collector daemon
Apr 29, 2026
3cc83d8
docs: record baota deployment
Apr 29, 2026
18bef7d
feat: collect market rank dimensions
Apr 29, 2026
c750729
fix: migrate legacy topic idea columns
Apr 29, 2026
787a364
docs: record full workflow test results
Apr 29, 2026
3951b22
docs: record llm config sync status
Apr 29, 2026
cd89cee
docs: record kimi deepseek llm profiles
Apr 29, 2026
25d0adb
fix: tolerate kimi model list fallback
Apr 29, 2026
fe9e7ee
docs: record live llm topic writing test
Apr 29, 2026
a996aa0
fix: restore global right-side entries
Apr 29, 2026
702b3e2
docs: record whole project deployment check
Apr 29, 2026
ac18e7b
feat: merge NovelPro workbench features
Apr 29, 2026
78e3db3
docs: record novelpro deployment verification
Apr 29, 2026
2789ee9
fix: wire obsidian sync service
Apr 29, 2026
891af22
chore: automate obsidian vault sync
Apr 29, 2026
89e4d00
fix: expose voice drift monitor tab
Apr 29, 2026
51e75c4
feat: refresh anti-ai writing prompts
Apr 29, 2026
1e11cef
fix: clarify prompt editing modal
Apr 29, 2026
3cbd6e8
fix: wrap NovelPro panel tabs
Apr 29, 2026
06493a0
fix: normalize topic enrichment payloads
Apr 29, 2026
c99701a
fix: harden NovelPro verification issues
Apr 29, 2026
a7dacfd
fix: soften NovelPro monitor timeline alerts
Apr 29, 2026
2f3d69d
docs: prepare NovelPro upstream review
Apr 29, 2026
dc411ea
docs: expand topic idea setup guide
Apr 29, 2026
81c0272
docs: plan style bible implementation
Apr 30, 2026
2e3f97c
chore: ignore local worktrees
Apr 30, 2026
d1fd315
feat: add style bible domain model
Apr 30, 2026
320a9d1
feat: persist style bible samples and profiles
Apr 30, 2026
834468e
feat: analyze style bible text samples
Apr 30, 2026
f197599
feat: generate style bible profiles
Apr 30, 2026
2eadc0a
feat: inject style bible into chapter generation
Apr 30, 2026
e580de7
feat: expose style bible in api and workbench
Apr 30, 2026
7fa7cf4
feat: score style bible profile matches
Apr 30, 2026
986be7a
feat: edit style bible technique cards
Apr 30, 2026
b4f951c
feat: use active llm for style bible profiles
Apr 30, 2026
e02c839
chore: log style bible llm extraction fallbacks
Apr 30, 2026
d3c4e26
fix: force json style bible llm extraction
Apr 30, 2026
901382b
feat: choose llm profile for style bible analysis
Apr 30, 2026
d4b5371
feat: upload style bible text samples
Apr 30, 2026
e1737b5
feat: add novel prop ledger
Apr 30, 2026
4f30e2b
feat: suggest prop ledger events from chapters
Apr 30, 2026
06cc449
feat: discover new prop ledger items
Apr 30, 2026
a28416f
feat: naturalize chapter generation output
Apr 30, 2026
3e099eb
feat: add configurable anti-ai writing prompts
Apr 30, 2026
e33d631
feat: add human texture polish pass
Apr 30, 2026
489413c
tune: trigger texture polish for detector risk
Apr 30, 2026
7889d6f
fix: guard anti-ai texture rewrite regressions
Apr 30, 2026
20f2c4f
feat: route writing and analysis llm profiles
Apr 30, 2026
f318731
fix: guard autopilot daemon startup
Apr 30, 2026
51c7b22
docs: design story studio pipeline
May 2, 2026
d30da5f
docs: plan story studio phase one
May 2, 2026
054169e
feat: add story studio and coc writing safeguards
May 2, 2026
ac1a6a0
fix: dedupe repeated chapter expansions
May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ EMBEDDING_USE_GPU=true
# ── Vector Store Configuration ──
VECTOR_STORE_TYPE=chromadb

# ── NovelPro Obsidian Long-Term Memory ──
# 留空时默认写入 data/obsidian-vault;如需接入自己的 Obsidian Vault,填绝对路径并重启后端。
# PLOTPILOT_OBSIDIAN_VAULT=/Users/you/Documents/Obsidian/PlotPilot

# 日志配置
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_FILE=logs/aitext.log
Expand Down
29 changes: 25 additions & 4 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
name: Backend CI

on:
push:
paths:
- "**.py"
- "requirements*.txt"
- "pyproject.toml"
- "pytest.ini"
- ".github/workflows/backend-ci.yml"
pull_request:
paths:
- "**.py"
- "requirements*.txt"
- "pyproject.toml"
- "pytest.ini"
- ".github/workflows/backend-ci.yml"
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run unit tests
run: pytest tests/unit -q --tb=short
- name: Run verified backend workflow tests
run: >
pytest
tests/unit/application/services/test_continuity_overview_service.py
tests/unit/application/services/test_power_system_service.py
tests/integration/interfaces/api/v1/test_continuity_api.py
tests/integration/interfaces/api/v1/test_power_system_api.py
tests/unit/infrastructure/persistence/database/test_sqlite_chapter_candidate_draft_repository.py
tests/unit/application/services/test_chapter_candidate_draft_service.py
tests/unit/application/services/test_chapter_service.py
tests/unit/application/services/test_chronicles_service.py
tests/integration/interfaces/api/v1/test_chapter_candidate_drafts_api.py
-q
--tb=short
env:
# 单测不需要真实 key,用占位值防止启动时报错
ANTHROPIC_API_KEY: "test-placeholder"
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
name: Frontend CI

on:
push:
paths:
- "frontend/**"
- ".github/workflows/frontend-ci.yml"
pull_request:
paths:
- "frontend/**"
- ".github/workflows/frontend-ci.yml"
workflow_dispatch:

jobs:
build:
Expand All @@ -13,9 +19,9 @@ jobs:
working-directory: frontend

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: "20"
cache: "npm"
Expand Down
24 changes: 22 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ __pycache__/
.hypothesis/
.venv/
venv/
/uv.lock
*.egg-info/
.eggs/
dist/
Expand All @@ -39,6 +40,9 @@ frontend/dist/
.playwright-mcp/
.claude/
.trae/
.worktrees/
agent_memory/
LOCAL_DEVELOPMENT.md

# ── 覆盖率 / 测试产物 ──
.coverage
Expand Down Expand Up @@ -88,6 +92,11 @@ scripts/evaluation/results/
data/chromadb/
data/logs/
data/llm_configs.json
data/obsidian-vault/
data/obsidian-vault-backups/
.obsidian-sync.lock/
scripts/sync_obsidian_from_server.sh
scripts/launchd/com.plotpilot.novelpro.obsidian-sync.plist

# ── 日志 ──
logs/
Expand All @@ -114,6 +123,7 @@ base_library.zip

# ── 临时 / 无关文件 ──
=++Contribution Value Roster++=
# Contributors: xibian-YQ, JamesGoslings

# ── PlotPilot 残留 ──
PlotPilot-master/
Expand All @@ -123,8 +133,12 @@ llm_profiles.json
aitext.lock
scripts/aitext.lock

# ── Tauri 桌面壳(仅本机打包用,勿提交;协作者 clone 后无此目录,不影响 npm run dev) ──
frontend/src-tauri/
# ── Tauri 桌面壳 ──
# 提交源码,忽略构建产物
frontend/src-tauri/target/
frontend/src-tauri/Cargo.lock
frontend/src-tauri/scripts/
frontend/src-tauri/tools/

# ── 仅本机 Windows 打包链路(协作者只需 npm run dev + 后端) ──
scripts/build_installer.py
Expand All @@ -150,3 +164,9 @@ mcps/
*.rs.bk
*.orig
*.rej

# ── 本地执行计划 / Agent 过程文档 ──
docs/superpowers/plans/2026-04-27-p1-candidate-drafts.md
docs/superpowers/plans/2026-04-29-topic-idea-incubation.md
docs/superpowers/plans/2026-04-29-topic-idea-report.md
docs/superpowers/specs/2026-04-29-topic-idea-incubation-design.md
5 changes: 5 additions & 0 deletions =++Contribution Value Roster++=
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@
16 wfnysse https://github.com/wfnysse
17 https://github.com/Kobe9312 1Kobe9312
18 https://github.com/zeroranyi zeroranyi
19 bugmaker2 https://github.com/bugmaker2
20 haoziyouxia https://github.com/haoziyouxia
21 LuoFengXiaoXiao https://github.com/LuoFengXiaoXiao
22 droid-Q https://github.com/droid-Q
23 semir0037-source https://github.com/semir0037-source
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- **知识图谱**:自动提取故事三元组,语义检索历史内容。
- **伏笔台账**:追踪并自动闭合叙事钩子。
- **风格分析**:作者声音漂移检测与文体指纹。
- **NovelPro 作者工作台**:选题立项、候选稿、连续性巡检、战力系统、Obsidian 长期记忆与监控中心。详见 [docs/NOVELPRO_README.md](docs/NOVELPRO_README.md)。
- **节拍表与故事结构**:三幕式、章节节拍规划。
- **DDD 四层架构**:`domain` / `application` / `infrastructure` / `interfaces`

Expand Down
5 changes: 3 additions & 2 deletions application/ai/embedding_config_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ def update_config(self, **kwargs) -> EmbeddingConfigModel:
params.append("default") # WHERE id = ?

sql = f"UPDATE embedding_config SET {', '.join(set_clauses)} WHERE id = ?"
db.execute(sql, params)
db.get_connection().commit()
conn = db.get_connection()
conn.execute(sql, tuple(params))
conn.commit()

logger.info("EmbeddingConfigService: 配置已更新,字段: %s", list(kwargs.keys()))
return self.get_config()
Expand Down
2 changes: 1 addition & 1 deletion application/ai/llm_json_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _do_repair(s: str) -> str:

if in_string:
res += '"'
res = res.strip()
res = ''.join(res).strip()
while res.endswith(','):
res = res[:-1].strip()
while stack:
Expand Down
195 changes: 195 additions & 0 deletions application/analyst/services/coc_canon_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""CoC 正典注册表服务。"""
from __future__ import annotations

from typing import Any, Mapping, Optional


class CocCanonService:
"""管理 CoC 正典条目与章节证据。"""

def __init__(self, repository):
self.repository = repository

def get_overview(self, novel_id: str) -> dict[str, Any]:
entries = self.repository.list_entries(novel_id)
events = self.repository.list_events(novel_id, limit=100)
return {
"novel_id": novel_id,
"entries": entries,
"recent_events": events,
"cognition_layers": self.get_cognition_layers(novel_id),
}

def get_cognition_layers(self, novel_id: str) -> dict[str, list[str]]:
entries = [
item
for item in self.repository.list_entries(novel_id)
if str(item.get("status") or "").strip() != "archived"
]
author_truth: list[str] = []
reader_known: list[str] = []
author_truth_snippets: list[str] = []
for item in entries:
title = str(item.get("title") or "").strip() or "未命名条目"
public_facts = str(item.get("public_facts") or "").strip()
hidden_truth = str(item.get("hidden_truth") or "").strip()
if public_facts:
reader_known.append(f"{title}:{public_facts}")
if hidden_truth:
author_truth.append(f"{title}:{hidden_truth}")
if len(hidden_truth) >= 8 and hidden_truth not in author_truth_snippets:
author_truth_snippets.append(hidden_truth[:80])
return {
"author_truth": author_truth[:24],
"reader_known": reader_known[:24],
"author_truth_snippets": author_truth_snippets[:40],
}

def upsert_entry(
self,
*,
novel_id: str,
canon_type: str,
title: str,
public_facts: str = "",
hidden_truth: str = "",
lock_level: str = "soft",
mutable_notes: str = "",
status: str = "active",
entry_id: Optional[str] = None,
) -> dict[str, Any]:
clean_canon_type = canon_type.strip()
clean_title = title.strip()
if not clean_canon_type:
raise ValueError("canon_type is required")
if not clean_title:
raise ValueError("title is required")

existing = self.repository.get_entry_by_id(entry_id) if entry_id else self.repository.get_entry_by_key(
novel_id,
clean_canon_type,
clean_title,
)
if existing is not None and existing.get("novel_id") != novel_id:
raise ValueError("entry does not belong to novel")

incoming = {
"canon_type": clean_canon_type,
"title": clean_title,
"public_facts": public_facts.strip(),
"hidden_truth": hidden_truth.strip(),
"lock_level": self._normalize_lock_level(lock_level),
"mutable_notes": mutable_notes.strip(),
"status": self._normalize_status(status),
}
self.lock_guard_validate_patch(existing, incoming)
return self.repository.upsert_entry(
entry_id=existing["id"] if existing else None,
novel_id=novel_id,
canon_type=incoming["canon_type"],
title=incoming["title"],
public_facts=incoming["public_facts"],
hidden_truth=incoming["hidden_truth"],
lock_level=incoming["lock_level"],
mutable_notes=incoming["mutable_notes"],
status=incoming["status"],
)

def create_event(
self,
*,
novel_id: str,
entry_id: str = "",
title: str = "",
chapter_number: int,
event_type: str = "mention",
evidence: str = "",
notes: str = "",
) -> dict[str, Any]:
clean_entry_id = str(entry_id or "").strip()
clean_title = str(title or "").strip()
chapter = int(chapter_number)
if chapter < 1:
raise ValueError("chapter_number must be greater than 0")
if not clean_entry_id and not clean_title:
raise ValueError("entry_id or title is required")

entry = None
if clean_entry_id:
entry = self.repository.get_entry_by_id(clean_entry_id)
elif clean_title:
entry = self.repository.get_entry_by_title(novel_id, clean_title)
if entry is None:
entry = self.upsert_entry(
novel_id=novel_id,
canon_type="other",
title=clean_title,
public_facts="",
hidden_truth="",
lock_level="soft",
mutable_notes="自动创建:由事件记录补建条目。",
status="draft",
)
if entry is None:
raise ValueError("entry not found")
if entry.get("novel_id") != novel_id:
raise ValueError("entry does not belong to novel")
return self.repository.create_event(
entry_id=str(entry.get("id") or clean_entry_id),
chapter_number=chapter,
event_type=(event_type or "mention").strip() or "mention",
evidence=evidence.strip(),
notes=notes.strip(),
)

@staticmethod
def lock_guard_validate_patch(
existing: Optional[Mapping[str, Any]],
incoming: Mapping[str, Any],
) -> None:
if not existing:
return
if str(existing.get("lock_level") or "").strip() != "absolute":
return

protected_fields = ("public_facts", "hidden_truth", "title", "canon_type")
for field in protected_fields:
old_value = str(existing.get(field) or "")
new_value = str(incoming.get(field) or "")
if old_value != new_value:
raise ValueError(f"absolute lock forbids changing `{field}`")

def build_overlay(self, novel_id: str) -> str:
entries = [
item
for item in self.repository.list_entries(novel_id)
if str(item.get("status") or "active").strip() != "archived"
]
if not entries:
return "【CoC正典(必须保持一致)】\n- 暂无已登记正典。"

lines = ["【CoC正典(必须保持一致)】"]
for item in entries:
lines.append(
f"- [{item.get('canon_type', '')}] {item.get('title', '')}(锁定:{item.get('lock_level', 'soft')})"
)
public_facts = str(item.get("public_facts") or "").strip()
hidden_truth = str(item.get("hidden_truth") or "").strip()
mutable_notes = str(item.get("mutable_notes") or "").strip()
if public_facts:
lines.append(f" 公共事实:{public_facts}")
if hidden_truth:
lines.append(f" 隐藏真相:{hidden_truth}")
if mutable_notes:
lines.append(f" 可变备注:{mutable_notes}")
return "\n".join(lines)

@staticmethod
def _normalize_lock_level(value: str) -> str:
normalized = (value or "").strip().lower()
return normalized if normalized in {"soft", "strict", "absolute"} else "soft"

@staticmethod
def _normalize_status(value: str) -> str:
normalized = (value or "").strip().lower()
return normalized if normalized in {"active", "draft", "archived"} else "active"
Loading