@demig0d2/skills 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/cli.js +313 -0
- package/package.json +44 -0
- package/skills/book-writer/SKILL.md +1396 -0
- package/skills/book-writer/references/kdp_specs.md +139 -0
- package/skills/book-writer/scripts/kdp_check.py +255 -0
- package/skills/book-writer/scripts/toc_extract.py +151 -0
- package/skills/book-writer/scripts/word_count.py +196 -0
- package/skills/chapter-auditor/SKILL.md +231 -0
- package/skills/chapter-auditor/scripts/score_report.py +237 -0
- package/skills/concept-expander/SKILL.md +170 -0
- package/skills/concept-expander/scripts/validate_concept.py +255 -0
- package/skills/continuity-tracker/SKILL.md +251 -0
- package/skills/continuity-tracker/references/log_schema.md +149 -0
- package/skills/continuity-tracker/scripts/conflict_check.py +179 -0
- package/skills/continuity-tracker/scripts/log_manager.py +258 -0
- package/skills/humanizer/SKILL.md +632 -0
- package/skills/humanizer/references/patterns_quick_ref.md +71 -0
- package/skills/humanizer/scripts/dna_scan.py +168 -0
- package/skills/humanizer/scripts/scan_ai_patterns.py +279 -0
- package/skills/overhaul/SKILL.md +697 -0
- package/skills/overhaul/references/upgrade_checklist.md +81 -0
- package/skills/overhaul/scripts/changelog_gen.py +183 -0
- package/skills/overhaul/scripts/skill_parser.py +265 -0
- package/skills/overhaul/scripts/version_bump.py +128 -0
- package/skills/research-aggregator/SKILL.md +194 -0
- package/skills/research-aggregator/references/thinkers_reference.md +104 -0
- package/skills/research-aggregator/scripts/bank_formatter.py +206 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Continuity Log — Schema Reference
|
|
2
|
+
|
|
3
|
+
The continuity log is a JSON file (`continuity_log.json`) maintained
|
|
4
|
+
alongside the manuscript. This document describes every field.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Full Schema
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"book_title": "string — the book's working title",
|
|
13
|
+
"created": "ISO datetime — when the log was initialized",
|
|
14
|
+
"last_updated": "ISO datetime — last update timestamp",
|
|
15
|
+
"last_chapter": "integer — last chapter number completed",
|
|
16
|
+
|
|
17
|
+
"commitments": [
|
|
18
|
+
"string — book-level promises established in front matter/intro"
|
|
19
|
+
],
|
|
20
|
+
|
|
21
|
+
"established_facts": [
|
|
22
|
+
"string — factual details about the author's story, must stay consistent"
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
"metaphors": {
|
|
26
|
+
"by_chapter": {
|
|
27
|
+
"1": ["metaphor used in chapter 1", "another metaphor"],
|
|
28
|
+
"2": ["metaphor used in chapter 2"]
|
|
29
|
+
},
|
|
30
|
+
"available": ["strong images not yet used"],
|
|
31
|
+
"retired": ["all metaphors used — do not repeat"]
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"insights": {
|
|
35
|
+
"1": "Core insight fully delivered in chapter 1",
|
|
36
|
+
"2": "Core insight delivered in chapter 2"
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
"tone_decisions": {
|
|
40
|
+
"register": "string — e.g., intimate/confessional in My Story",
|
|
41
|
+
"reader_address": "string — e.g., direct 'you' in Reflection",
|
|
42
|
+
"author_position": "string — e.g., always in-process, never above",
|
|
43
|
+
"emotional_ceiling": "string — e.g., darkest content in Ch.1"
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"structural_patterns": {
|
|
47
|
+
"section_format": "string — e.g., My Story + My Reflection",
|
|
48
|
+
"avg_chapter_words": "integer — running average",
|
|
49
|
+
"opening_style": "string — e.g., immersive scene",
|
|
50
|
+
"closing_style": "string — e.g., single reframe sentence",
|
|
51
|
+
"chapter_word_counts": [2400, 2200, 2600]
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
"open_threads": [
|
|
55
|
+
{
|
|
56
|
+
"thread": "string — the thread description",
|
|
57
|
+
"introduced_in": "string — chapter number",
|
|
58
|
+
"closed": false,
|
|
59
|
+
"added": "ISO datetime"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
"closed_threads": [
|
|
64
|
+
{
|
|
65
|
+
"thread": "string",
|
|
66
|
+
"introduced_in": "string",
|
|
67
|
+
"closed": true,
|
|
68
|
+
"closed_in": "string — chapter where resolved",
|
|
69
|
+
"added": "ISO datetime"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
|
|
73
|
+
"chapter_summaries": {
|
|
74
|
+
"1": {
|
|
75
|
+
"title": "Chapter 1 title",
|
|
76
|
+
"summary": "One-paragraph summary of what was covered"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Field Guidelines
|
|
85
|
+
|
|
86
|
+
### `commitments`
|
|
87
|
+
Book-level promises that must never be contradicted. Examples:
|
|
88
|
+
- "Author frames himself as someone who lived this, not an expert"
|
|
89
|
+
- "Book promises no quick fixes — lasting transformation only"
|
|
90
|
+
- "Author is always in-process, never looking back from arrival"
|
|
91
|
+
|
|
92
|
+
### `established_facts`
|
|
93
|
+
All narrative details stated in the manuscript that must remain consistent:
|
|
94
|
+
- "Father passed away during author's degree years"
|
|
95
|
+
- "Lived in a tiny penthouse apartment alone"
|
|
96
|
+
- "Walked miles to save money on transport"
|
|
97
|
+
- "Cooked rice and lentils, stretched groceries"
|
|
98
|
+
|
|
99
|
+
### `metaphors.retired`
|
|
100
|
+
Once a metaphor is used anywhere in the manuscript, it goes here.
|
|
101
|
+
The conflict checker (`conflict_check.py`) scans new chapters against this list.
|
|
102
|
+
|
|
103
|
+
### `insights`
|
|
104
|
+
One core insight per chapter. Track to avoid restating:
|
|
105
|
+
- Ch.1: "Loneliness as mirror of self-relationship"
|
|
106
|
+
- Ch.2: "Chasing connection from fear, not love, creates neediness"
|
|
107
|
+
- Ch.3: "Aloneness and loneliness are different — one is chosen"
|
|
108
|
+
|
|
109
|
+
### `open_threads`
|
|
110
|
+
Thematic or narrative threads introduced that haven't been resolved:
|
|
111
|
+
- "Ch.2 mentioned 'the small proof that my life has value' — needs deeper exploration"
|
|
112
|
+
- "Ch.3 established reader's 'wrong attempts' pattern — needs to pay off in Ch.7"
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## CLI Quick Reference
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Initialize
|
|
120
|
+
python log_manager.py init "My Book Title"
|
|
121
|
+
|
|
122
|
+
# Add chapter
|
|
123
|
+
python log_manager.py add-chapter 1 "Why It Hurts So Much"
|
|
124
|
+
|
|
125
|
+
# Add established fact
|
|
126
|
+
python log_manager.py add-fact "Author's father passed during degree years"
|
|
127
|
+
|
|
128
|
+
# Add insight delivered
|
|
129
|
+
python log_manager.py add-insight 1 "Loneliness as mirror of self-relationship"
|
|
130
|
+
|
|
131
|
+
# Add metaphor (auto-retires it)
|
|
132
|
+
python log_manager.py add-metaphor 1 "deafening scream inside my head"
|
|
133
|
+
|
|
134
|
+
# Add open thread
|
|
135
|
+
python log_manager.py add-thread "Ch.2 mentioned small proof of self-value — needs depth"
|
|
136
|
+
|
|
137
|
+
# Close a thread (by index)
|
|
138
|
+
python log_manager.py threads # list open threads
|
|
139
|
+
python log_manager.py close-thread 0 # close thread at index 0
|
|
140
|
+
|
|
141
|
+
# View full log
|
|
142
|
+
python log_manager.py show
|
|
143
|
+
|
|
144
|
+
# Summary only
|
|
145
|
+
python log_manager.py summary
|
|
146
|
+
|
|
147
|
+
# Conflict check before finalizing a chapter
|
|
148
|
+
python conflict_check.py chapter_5.md --log continuity_log.json
|
|
149
|
+
```
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
conflict_check.py — Check a chapter for continuity conflicts against the log
|
|
4
|
+
|
|
5
|
+
Scans a chapter text file against the established facts, retired metaphors,
|
|
6
|
+
and delivered insights in the continuity log. Surfaces potential conflicts.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python conflict_check.py <chapter_file> [--log log.json]
|
|
10
|
+
python conflict_check.py <chapter_file> [--log log.json] --json
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import re
|
|
15
|
+
import json
|
|
16
|
+
import argparse
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
DEFAULT_LOG = "continuity_log.json"
|
|
20
|
+
|
|
21
|
+
# ─── Conflict Detection Rules ─────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
# Known fact patterns that might be stated inconsistently
|
|
24
|
+
NUMERICAL_PATTERN = re.compile(r'\b(\d+)\s+(years?|months?|days?|chapters?|books?|pages?)\b', re.IGNORECASE)
|
|
25
|
+
NAME_PATTERN = re.compile(r'\b[A-Z][a-z]+ [A-Z][a-z]+\b')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_log(log_path: str) -> dict:
|
|
29
|
+
path = Path(log_path)
|
|
30
|
+
if not path.exists():
|
|
31
|
+
return None
|
|
32
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def check_retired_metaphors(chapter_text: str, retired: list) -> list:
|
|
36
|
+
"""Find retired metaphors used again in this chapter."""
|
|
37
|
+
conflicts = []
|
|
38
|
+
chapter_lower = chapter_text.lower()
|
|
39
|
+
for metaphor in retired:
|
|
40
|
+
# Check key phrases from the metaphor
|
|
41
|
+
key_words = [w for w in metaphor.lower().split() if len(w) > 4]
|
|
42
|
+
matches = sum(1 for w in key_words if w in chapter_lower)
|
|
43
|
+
if matches >= 2:
|
|
44
|
+
conflicts.append({
|
|
45
|
+
"type": "repeated_metaphor",
|
|
46
|
+
"severity": "medium",
|
|
47
|
+
"detail": f'Retired metaphor may be reused: "{metaphor}"',
|
|
48
|
+
"suggestion": "Use a fresh image — this metaphor was already delivered in a prior chapter",
|
|
49
|
+
})
|
|
50
|
+
return conflicts
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_repeated_insights(chapter_text: str, insights: dict) -> list:
|
|
54
|
+
"""Check if an insight already fully delivered is being re-delivered."""
|
|
55
|
+
conflicts = []
|
|
56
|
+
chapter_lower = chapter_text.lower()
|
|
57
|
+
|
|
58
|
+
for ch_num, insight in insights.items():
|
|
59
|
+
# Look for key phrase overlap
|
|
60
|
+
insight_words = [w for w in insight.lower().split() if len(w) > 5]
|
|
61
|
+
if not insight_words:
|
|
62
|
+
continue
|
|
63
|
+
matches = sum(1 for w in insight_words if w in chapter_lower)
|
|
64
|
+
overlap_ratio = matches / len(insight_words) if insight_words else 0
|
|
65
|
+
|
|
66
|
+
if overlap_ratio > 0.5:
|
|
67
|
+
conflicts.append({
|
|
68
|
+
"type": "repeated_insight",
|
|
69
|
+
"severity": "medium",
|
|
70
|
+
"detail": f'Insight from Ch.{ch_num} may be re-delivered: "{insight[:60]}..."',
|
|
71
|
+
"suggestion": "Build on this insight rather than restating it — add a new dimension",
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return conflicts
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def check_fact_consistency(chapter_text: str, established_facts: list) -> list:
|
|
78
|
+
"""Flag when numerical or named facts appear inconsistently."""
|
|
79
|
+
conflicts = []
|
|
80
|
+
|
|
81
|
+
# Extract numbers from chapter
|
|
82
|
+
chapter_numbers = NUMERICAL_PATTERN.findall(chapter_text)
|
|
83
|
+
|
|
84
|
+
for fact in established_facts:
|
|
85
|
+
# Check if fact contains numbers that contradict chapter
|
|
86
|
+
fact_numbers = NUMERICAL_PATTERN.findall(fact)
|
|
87
|
+
for fn_val, fn_unit in fact_numbers:
|
|
88
|
+
for cn_val, cn_unit in chapter_numbers:
|
|
89
|
+
if cn_unit.rstrip("s") == fn_unit.rstrip("s") and cn_val != fn_val:
|
|
90
|
+
conflicts.append({
|
|
91
|
+
"type": "numerical_inconsistency",
|
|
92
|
+
"severity": "high",
|
|
93
|
+
"detail": f'Number mismatch: fact says "{fn_val} {fn_unit}", chapter says "{cn_val} {cn_unit}"',
|
|
94
|
+
"suggestion": f'Verify against established fact: "{fact[:80]}"',
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return conflicts
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def run_conflict_check(chapter_path: str, log_path: str) -> dict:
|
|
101
|
+
chapter_text = Path(chapter_path).read_text(encoding="utf-8")
|
|
102
|
+
log = load_log(log_path)
|
|
103
|
+
|
|
104
|
+
result = {
|
|
105
|
+
"chapter_file": chapter_path,
|
|
106
|
+
"log_file": log_path,
|
|
107
|
+
"conflicts": [],
|
|
108
|
+
"warnings": [],
|
|
109
|
+
"clean": True,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if not log:
|
|
113
|
+
result["warnings"].append("No continuity log found — run log_manager.py init first")
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
# Run checks
|
|
117
|
+
retired = log.get("metaphors", {}).get("retired", [])
|
|
118
|
+
insights = log.get("insights", {})
|
|
119
|
+
facts = log.get("established_facts", [])
|
|
120
|
+
|
|
121
|
+
metaphor_conflicts = check_retired_metaphors(chapter_text, retired)
|
|
122
|
+
insight_conflicts = check_repeated_insights(chapter_text, insights)
|
|
123
|
+
fact_conflicts = check_fact_consistency(chapter_text, facts)
|
|
124
|
+
|
|
125
|
+
all_conflicts = metaphor_conflicts + insight_conflicts + fact_conflicts
|
|
126
|
+
|
|
127
|
+
result["conflicts"] = all_conflicts
|
|
128
|
+
result["clean"] = len(all_conflicts) == 0
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def print_report(result: dict):
|
|
134
|
+
print(f"\n{'═' * 60}")
|
|
135
|
+
print(f" CONTINUITY CONFLICT CHECK")
|
|
136
|
+
print(f" Chapter: {result['chapter_file']}")
|
|
137
|
+
print(f" Log: {result['log_file']}")
|
|
138
|
+
print(f"{'═' * 60}")
|
|
139
|
+
|
|
140
|
+
if result.get("warnings"):
|
|
141
|
+
for w in result["warnings"]:
|
|
142
|
+
print(f"\n ⚠ {w}")
|
|
143
|
+
|
|
144
|
+
if result["clean"]:
|
|
145
|
+
print(f"\n ✓ No conflicts detected. Chapter is consistent with log.\n")
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
high = [c for c in result["conflicts"] if c["severity"] == "high"]
|
|
149
|
+
medium = [c for c in result["conflicts"] if c["severity"] == "medium"]
|
|
150
|
+
|
|
151
|
+
print(f"\n Conflicts: {len(result['conflicts'])} ({len(high)} high, {len(medium)} medium)\n")
|
|
152
|
+
|
|
153
|
+
for i, conflict in enumerate(result["conflicts"], 1):
|
|
154
|
+
icon = "✗" if conflict["severity"] == "high" else "⚠"
|
|
155
|
+
print(f" {icon} [{conflict['severity'].upper()}] {conflict['type'].replace('_', ' ').title()}")
|
|
156
|
+
print(f" {conflict['detail']}")
|
|
157
|
+
print(f" → {conflict['suggestion']}\n")
|
|
158
|
+
|
|
159
|
+
print(f"{'═' * 60}")
|
|
160
|
+
print(f" Resolve conflicts before updating the continuity log.\n")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
parser = argparse.ArgumentParser()
|
|
165
|
+
parser.add_argument("chapter_file")
|
|
166
|
+
parser.add_argument("--log", default=DEFAULT_LOG)
|
|
167
|
+
parser.add_argument("--json", action="store_true")
|
|
168
|
+
args = parser.parse_args()
|
|
169
|
+
|
|
170
|
+
if not Path(args.chapter_file).exists():
|
|
171
|
+
print(f"Error: File not found: {args.chapter_file}", file=sys.stderr)
|
|
172
|
+
sys.exit(1)
|
|
173
|
+
|
|
174
|
+
result = run_conflict_check(args.chapter_file, args.log)
|
|
175
|
+
|
|
176
|
+
if args.json:
|
|
177
|
+
print(json.dumps(result, indent=2))
|
|
178
|
+
else:
|
|
179
|
+
print_report(result)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
log_manager.py — Read, write, and update the continuity log
|
|
4
|
+
|
|
5
|
+
The continuity log is stored as a JSON file alongside the manuscript.
|
|
6
|
+
This script manages all log operations: init, update, read, and summary.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python log_manager.py init <book_title> [--log log.json]
|
|
10
|
+
python log_manager.py show [--log log.json]
|
|
11
|
+
python log_manager.py summary [--log log.json]
|
|
12
|
+
python log_manager.py add-chapter <N> <title> [--log log.json]
|
|
13
|
+
python log_manager.py add-fact <fact> [--log log.json]
|
|
14
|
+
python log_manager.py add-insight <chapter_n> <insight> [--log log.json]
|
|
15
|
+
python log_manager.py add-metaphor <chapter_n> <metaphor> [--log log.json]
|
|
16
|
+
python log_manager.py add-thread <thread> [--log log.json]
|
|
17
|
+
python log_manager.py close-thread <thread_index> [--log log.json]
|
|
18
|
+
python log_manager.py threads [--log log.json]
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
import json
|
|
23
|
+
import argparse
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
|
|
27
|
+
DEFAULT_LOG = "continuity_log.json"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_log(log_path: str) -> dict:
|
|
31
|
+
path = Path(log_path)
|
|
32
|
+
if path.exists():
|
|
33
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def save_log(log: dict, log_path: str):
|
|
38
|
+
Path(log_path).write_text(json.dumps(log, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def init_log(book_title: str, log_path: str) -> dict:
|
|
42
|
+
log = {
|
|
43
|
+
"book_title": book_title,
|
|
44
|
+
"created": datetime.now().isoformat(),
|
|
45
|
+
"last_updated": datetime.now().isoformat(),
|
|
46
|
+
"last_chapter": 0,
|
|
47
|
+
"commitments": [],
|
|
48
|
+
"established_facts": [],
|
|
49
|
+
"metaphors": {
|
|
50
|
+
"by_chapter": {},
|
|
51
|
+
"available": [],
|
|
52
|
+
"retired": [],
|
|
53
|
+
},
|
|
54
|
+
"insights": {},
|
|
55
|
+
"tone_decisions": {
|
|
56
|
+
"register": "",
|
|
57
|
+
"reader_address": "",
|
|
58
|
+
"author_position": "",
|
|
59
|
+
"emotional_ceiling": "",
|
|
60
|
+
},
|
|
61
|
+
"structural_patterns": {
|
|
62
|
+
"section_format": "",
|
|
63
|
+
"avg_chapter_words": 0,
|
|
64
|
+
"opening_style": "",
|
|
65
|
+
"closing_style": "",
|
|
66
|
+
"chapter_word_counts": [],
|
|
67
|
+
},
|
|
68
|
+
"open_threads": [],
|
|
69
|
+
"closed_threads": [],
|
|
70
|
+
"chapter_summaries": {},
|
|
71
|
+
}
|
|
72
|
+
save_log(log, log_path)
|
|
73
|
+
print(f" ✓ Log initialized: {log_path}")
|
|
74
|
+
print(f" Book: {book_title}")
|
|
75
|
+
return log
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def show_log(log: dict):
|
|
79
|
+
print(f"\n{'═' * 60}")
|
|
80
|
+
print(f" CONTINUITY LOG — {log['book_title']}")
|
|
81
|
+
print(f" Last updated after Chapter {log['last_chapter']}")
|
|
82
|
+
print(f"{'═' * 60}")
|
|
83
|
+
|
|
84
|
+
if log["commitments"]:
|
|
85
|
+
print(f"\n BOOK-LEVEL COMMITMENTS ({len(log['commitments'])}):")
|
|
86
|
+
for c in log["commitments"]:
|
|
87
|
+
print(f" • {c}")
|
|
88
|
+
|
|
89
|
+
if log["established_facts"]:
|
|
90
|
+
print(f"\n ESTABLISHED FACTS ({len(log['established_facts'])}):")
|
|
91
|
+
for f in log["established_facts"]:
|
|
92
|
+
print(f" • {f}")
|
|
93
|
+
|
|
94
|
+
if log["metaphors"]["by_chapter"]:
|
|
95
|
+
print(f"\n METAPHORS USED:")
|
|
96
|
+
for ch, mets in sorted(log["metaphors"]["by_chapter"].items(), key=lambda x: int(x[0])):
|
|
97
|
+
print(f" Ch.{ch}: {', '.join(mets)}")
|
|
98
|
+
if log["metaphors"]["retired"]:
|
|
99
|
+
print(f" Retired: {', '.join(log['metaphors']['retired'])}")
|
|
100
|
+
|
|
101
|
+
if log["insights"]:
|
|
102
|
+
print(f"\n INSIGHTS DELIVERED:")
|
|
103
|
+
for ch, insight in sorted(log["insights"].items(), key=lambda x: int(x[0])):
|
|
104
|
+
print(f" Ch.{ch}: {insight}")
|
|
105
|
+
|
|
106
|
+
open_threads = [t for t in log["open_threads"] if not t.get("closed")]
|
|
107
|
+
if open_threads:
|
|
108
|
+
print(f"\n OPEN THREADS ({len(open_threads)}):")
|
|
109
|
+
for i, t in enumerate(open_threads):
|
|
110
|
+
print(f" [{i}] {t['thread']}")
|
|
111
|
+
if t.get("introduced_in"):
|
|
112
|
+
print(f" Introduced: Ch.{t['introduced_in']}")
|
|
113
|
+
|
|
114
|
+
if log["chapter_summaries"]:
|
|
115
|
+
print(f"\n CHAPTER SUMMARIES ({len(log['chapter_summaries'])}):")
|
|
116
|
+
for ch, summary in sorted(log["chapter_summaries"].items(), key=lambda x: int(x[0])):
|
|
117
|
+
title = summary.get("title", f"Chapter {ch}")
|
|
118
|
+
text = summary.get("summary", "")[:80]
|
|
119
|
+
print(f" Ch.{ch} — {title}: {text}...")
|
|
120
|
+
|
|
121
|
+
print(f"\n{'═' * 60}\n")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def summary_log(log: dict):
|
|
125
|
+
open_threads = [t for t in log["open_threads"] if not t.get("closed")]
|
|
126
|
+
print(f"\n {log['book_title']} — Continuity Summary")
|
|
127
|
+
print(f" Chapters completed: {log['last_chapter']}")
|
|
128
|
+
print(f" Facts established: {len(log['established_facts'])}")
|
|
129
|
+
print(f" Insights delivered: {len(log['insights'])}")
|
|
130
|
+
print(f" Open threads: {len(open_threads)}")
|
|
131
|
+
if open_threads:
|
|
132
|
+
print(f" ⚠ Unresolved threads:")
|
|
133
|
+
for t in open_threads:
|
|
134
|
+
print(f" · {t['thread'][:60]}")
|
|
135
|
+
print()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
parser = argparse.ArgumentParser()
|
|
140
|
+
parser.add_argument("command", choices=[
|
|
141
|
+
"init", "show", "summary", "add-chapter", "add-fact",
|
|
142
|
+
"add-insight", "add-metaphor", "add-thread", "close-thread", "threads"
|
|
143
|
+
])
|
|
144
|
+
parser.add_argument("args", nargs="*")
|
|
145
|
+
parser.add_argument("--log", default=DEFAULT_LOG)
|
|
146
|
+
parsed = parser.parse_args()
|
|
147
|
+
|
|
148
|
+
log_path = parsed.log
|
|
149
|
+
cmd = parsed.command
|
|
150
|
+
args = parsed.args
|
|
151
|
+
|
|
152
|
+
if cmd == "init":
|
|
153
|
+
title = " ".join(args) if args else "Untitled"
|
|
154
|
+
init_log(title, log_path)
|
|
155
|
+
|
|
156
|
+
elif cmd in ("show",):
|
|
157
|
+
log = load_log(log_path)
|
|
158
|
+
if not log:
|
|
159
|
+
print(f" No log found at {log_path}. Run: python log_manager.py init <title>")
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
show_log(log)
|
|
162
|
+
|
|
163
|
+
elif cmd == "summary":
|
|
164
|
+
log = load_log(log_path)
|
|
165
|
+
if not log:
|
|
166
|
+
print(f" No log found at {log_path}")
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
summary_log(log)
|
|
169
|
+
|
|
170
|
+
elif cmd == "add-chapter":
|
|
171
|
+
log = load_log(log_path)
|
|
172
|
+
if not log:
|
|
173
|
+
print(" No log found. Run init first.")
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
n = int(args[0]) if args else log["last_chapter"] + 1
|
|
176
|
+
title = " ".join(args[1:]) if len(args) > 1 else f"Chapter {n}"
|
|
177
|
+
log["last_chapter"] = n
|
|
178
|
+
log["last_updated"] = datetime.now().isoformat()
|
|
179
|
+
if str(n) not in log["chapter_summaries"]:
|
|
180
|
+
log["chapter_summaries"][str(n)] = {"title": title, "summary": ""}
|
|
181
|
+
save_log(log, log_path)
|
|
182
|
+
print(f" ✓ Chapter {n}: {title} added to log")
|
|
183
|
+
|
|
184
|
+
elif cmd == "add-fact":
|
|
185
|
+
log = load_log(log_path)
|
|
186
|
+
if not log:
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
fact = " ".join(args)
|
|
189
|
+
log["established_facts"].append(fact)
|
|
190
|
+
log["last_updated"] = datetime.now().isoformat()
|
|
191
|
+
save_log(log, log_path)
|
|
192
|
+
print(f" ✓ Fact added: {fact}")
|
|
193
|
+
|
|
194
|
+
elif cmd == "add-insight":
|
|
195
|
+
log = load_log(log_path)
|
|
196
|
+
if not log:
|
|
197
|
+
sys.exit(1)
|
|
198
|
+
ch = args[0] if args else str(log["last_chapter"])
|
|
199
|
+
insight = " ".join(args[1:]) if len(args) > 1 else ""
|
|
200
|
+
log["insights"][ch] = insight
|
|
201
|
+
log["last_updated"] = datetime.now().isoformat()
|
|
202
|
+
save_log(log, log_path)
|
|
203
|
+
print(f" ✓ Insight for Ch.{ch} added")
|
|
204
|
+
|
|
205
|
+
elif cmd == "add-metaphor":
|
|
206
|
+
log = load_log(log_path)
|
|
207
|
+
if not log:
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
ch = args[0] if args else str(log["last_chapter"])
|
|
210
|
+
metaphor = " ".join(args[1:]) if len(args) > 1 else ""
|
|
211
|
+
if ch not in log["metaphors"]["by_chapter"]:
|
|
212
|
+
log["metaphors"]["by_chapter"][ch] = []
|
|
213
|
+
log["metaphors"]["by_chapter"][ch].append(metaphor)
|
|
214
|
+
log["metaphors"]["retired"].append(metaphor)
|
|
215
|
+
log["last_updated"] = datetime.now().isoformat()
|
|
216
|
+
save_log(log, log_path)
|
|
217
|
+
print(f" ✓ Metaphor added to Ch.{ch} and retired")
|
|
218
|
+
|
|
219
|
+
elif cmd == "add-thread":
|
|
220
|
+
log = load_log(log_path)
|
|
221
|
+
if not log:
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
thread = " ".join(args)
|
|
224
|
+
log["open_threads"].append({
|
|
225
|
+
"thread": thread,
|
|
226
|
+
"introduced_in": str(log["last_chapter"]),
|
|
227
|
+
"closed": False,
|
|
228
|
+
"added": datetime.now().isoformat(),
|
|
229
|
+
})
|
|
230
|
+
log["last_updated"] = datetime.now().isoformat()
|
|
231
|
+
save_log(log, log_path)
|
|
232
|
+
print(f" ✓ Thread added: {thread}")
|
|
233
|
+
|
|
234
|
+
elif cmd == "close-thread":
|
|
235
|
+
log = load_log(log_path)
|
|
236
|
+
if not log:
|
|
237
|
+
sys.exit(1)
|
|
238
|
+
idx = int(args[0]) if args else -1
|
|
239
|
+
open_threads = [t for t in log["open_threads"] if not t.get("closed")]
|
|
240
|
+
if 0 <= idx < len(open_threads):
|
|
241
|
+
open_threads[idx]["closed"] = True
|
|
242
|
+
open_threads[idx]["closed_in"] = str(log["last_chapter"])
|
|
243
|
+
log["closed_threads"].append(open_threads[idx])
|
|
244
|
+
log["last_updated"] = datetime.now().isoformat()
|
|
245
|
+
save_log(log, log_path)
|
|
246
|
+
print(f" ✓ Thread [{idx}] closed: {open_threads[idx]['thread'][:60]}")
|
|
247
|
+
else:
|
|
248
|
+
print(f" ✗ Invalid thread index: {idx}")
|
|
249
|
+
|
|
250
|
+
elif cmd == "threads":
|
|
251
|
+
log = load_log(log_path)
|
|
252
|
+
if not log:
|
|
253
|
+
sys.exit(1)
|
|
254
|
+
open_threads = [t for t in log["open_threads"] if not t.get("closed")]
|
|
255
|
+
print(f"\n Open threads ({len(open_threads)}):\n")
|
|
256
|
+
for i, t in enumerate(open_threads):
|
|
257
|
+
print(f" [{i}] Introduced Ch.{t.get('introduced_in', '?')}: {t['thread']}")
|
|
258
|
+
print()
|