You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
IMPORTANT: Do not begin until BOTH #2 and #3 PRs are merged. When complete, open a PR and wait for merge before any content work begins.
Goal
Add a save_journey() helper to _tools/content_writer.py so generator scripts (and chat authoring sessions) have a consistent, validated way to write journey JSON files.
What to build
1. Add save_journey() to _tools/content_writer.py
Pattern-match the existing save_chapter() function (which the team uses for all chapter content).
defsave_journey(journey_type: str, data: dict) ->Path:
"""Validate and write a journey JSON file to the correct subdirectory. Args: journey_type: one of 'thematic', 'concept', 'person' data: the journey dict — must include 'id' and conform to journey schema Returns: Path to the written file. Raises: ValueError: if journey_type is invalid, data is malformed, or the ID conflicts with an existing journey. """valid_types= {'thematic', 'concept', 'person'}
ifjourney_typenotinvalid_types:
raiseValueError(f"journey_type must be one of {valid_types}, got '{journey_type}'")
ifdata.get('journey_type') !=journey_type:
raiseValueError(
f"data['journey_type']='{data.get('journey_type')}' does not match "f"argument journey_type='{journey_type}'"
)
journey_id=data.get('id')
ifnotjourney_idornotre.match(r'^[a-z0-9][a-z0-9-]*$', journey_id):
raiseValueError(f"Invalid journey id: '{journey_id}' — must be lowercase alphanumeric + hyphens")
# Ensure required fields present (delegate full validation to schema_validator later)forrequiredin ('title', 'description', 'stops'):
ifnotdata.get(required):
raiseValueError(f"Required field missing or empty: '{required}'")
ifnotisinstance(data['stops'], list) orlen(data['stops']) ==0:
raiseValueError("'stops' must be a non-empty list")
# Validate stop orderingfori, stopinenumerate(data['stops'], start=1):
ifstop.get('stop_order') !=i:
raiseValueError(f"Stop at index {i-1} has stop_order={stop.get('stop_order')}, expected {i}")
# Validate bridge_to_next pattern: non-null on all but last, null on lastlast_idx=len(data['stops']) -1fori, stopinenumerate(data['stops']):
bridge=stop.get('bridge_to_next')
ifi==last_idx:
ifbridgeisnotNoneandbridge!='':
raiseValueError(f"Final stop (order {i+1}) must have bridge_to_next = null")
else:
ifnotbridgeornotbridge.strip():
raiseValueError(f"Stop at order {i+1} requires non-empty bridge_to_next")
# Write the fileout_dir=ROOT/'content'/'meta'/'journeys'/journey_typeout_dir.mkdir(parents=True, exist_ok=True)
out_path=out_dir/f'{journey_id}.json'# Refuse to overwrite without explicit opt-in? — for now, overwrite to support iterative authoringwithopen(out_path, 'w', encoding='utf-8') asf:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write('\n')
returnout_path
Imports required at top of file (add if missing):
importreimportjsonfrompathlibimportPath
And ensure ROOT points to the repo root (match existing pattern in content_writer.py).
2. Ensure function is exportable
The function should be importable from _tools/ generator scripts:
fromcontent_writerimportsave_journey
If content_writer.py has an explicit __all__ list, add 'save_journey'.
Acceptance criteria
save_journey() added to _tools/content_writer.py
Function validates: journey_type match, id format, required fields, stop ordering, bridge_to_next pattern
Function writes to content/meta/journeys/{type}/{id}.json
A minimal test script verifies the function:
Create a valid journey dict, call save_journey, verify file written
Call with invalid journey_type, verify ValueError raised
Call with non-sequential stop_order, verify ValueError raised
Call with bridge on final stop, verify ValueError raised
Delete test file after (no committed test fixtures)
python _tools/schema_validator.py still passes after save_journey writes a test file (round-trip check)
Parent epic: #1379
Track: Code
Phase: 1 — Foundation
Dependency protocol
Blocked by: #2 (pipeline), #3 (validator)
Blocks: #5 (migration scripts), all content authoring (#11, #12, #13-22)
IMPORTANT: Do not begin until BOTH #2 and #3 PRs are merged. When complete, open a PR and wait for merge before any content work begins.
Goal
Add a
save_journey()helper to_tools/content_writer.pyso generator scripts (and chat authoring sessions) have a consistent, validated way to write journey JSON files.What to build
1. Add
save_journey()to_tools/content_writer.pyPattern-match the existing
save_chapter()function (which the team uses for all chapter content).Imports required at top of file (add if missing):
And ensure
ROOTpoints to the repo root (match existing pattern incontent_writer.py).2. Ensure function is exportable
The function should be importable from
_tools/generator scripts:If
content_writer.pyhas an explicit__all__list, add'save_journey'.Acceptance criteria
save_journey()added to_tools/content_writer.pycontent/meta/journeys/{type}/{id}.jsonpython _tools/schema_validator.pystill passes after save_journey writes a test file (round-trip check)Files to modify
_tools/content_writer.pyWorkflow