Storybuilder test

This commit is contained in:
K. Hodges 2026-05-21 22:19:07 -07:00
parent dcebe62889
commit 9157853c10
16 changed files with 986 additions and 0 deletions

View File

@ -197,6 +197,17 @@ class PipelineRunner:
retry_notes.append(f"Context update from '{stage.id}': {result.context_update}")
if result.status == "pass":
if stage.type in {"agent_review", "review"} and result.next_stage:
self.logger.event(
"stage.next_ignored",
"Ignoring next_stage from passing review",
run_id=self.artifacts.run_id,
task_id=task.id,
stage_id=stage.id,
requested_next_stage=result.next_stage,
)
index += 1
continue
if result.next_stage:
if result.next_stage not in stage_indexes:
final_status = "failed"

View File

@ -12,3 +12,5 @@ status: pass | fail | retry | escalate
reason: <short explanation>
next_stage: <optional stage id>
context_update: <compact useful note>
When `status: pass`, leave `next_stage` blank. Do not put task ids such as `TASK-002` in `next_stage`; `next_stage` is only for pipeline stage ids during retry/failure routing.

View File

@ -0,0 +1,17 @@
# Private story bible and generated prose for local writing projects.
story/worldbuilding.md
story/characters.md
story/style-guide.md
story/plot-state.md
story/timeline.md
story/unresolved-threads.md
story/continuity-rules.md
story/outline.md
story/chapters/**/*.md
story/chapters/*.md
# NightShift artifacts.
.nightshift/runs/
.nightshift/project-context.md
.nightshift/project-context-chart.md
.nightshift/nightshift.log

View File

@ -0,0 +1,28 @@
You are the continuity reviewer for a NightShift novel-writing workflow.
Review the drafted scene against:
- the current task
- `story/worldbuilding.md`
- `story/characters.md`
- `story/plot-state.md`
- `story/timeline.md`
- `story/unresolved-threads.md`
- `story/continuity-rules.md`
- prior scene context provided in artifacts
Check for:
- contradictions
- wrong character knowledge
- impossible locations or timing
- accidental resolution of future threads
- missing required beats from the task
- invented lore that should have been added deliberately
Output exactly:
status: pass | fail | retry | escalate
reason: <short explanation>
next_stage: <optional stage id>
context_update: <compact useful note>
When `status: pass`, leave `next_stage` blank. Use `retry` when the scene can be repaired by drafting again.

View File

@ -0,0 +1,20 @@
You are the drafting agent for a NightShift novel-writing workflow.
Draft only the current scene or section requested by the task.
Rules:
- Write prose only under `story/chapters/`.
- Do not edit `story/worldbuilding.md`, `story/characters.md`, `story/style-guide.md`, `story/plot-state.md`, `story/timeline.md`, `story/unresolved-threads.md`, `story/continuity-rules.md`, or `story/outline.md`.
- Use `story/style-guide.md` for POV, tense, tone, and prose rules.
- Use `story/plot-state.md` and `story/timeline.md` as current state.
- Keep the scene bounded to the task acceptance criteria.
- Do not resolve future plot threads unless the task explicitly asks for that.
- Do not include author notes, TODOs, bracket placeholders, or analysis in the scene file.
Output only complete file content blocks.
Use one fenced block per file:
```file:story/chapters/chapter-001/scene-001.md
<complete scene prose>
```
If the task does not specify a scene path, choose the next obvious path under `story/chapters/` and keep it stable.

View File

@ -0,0 +1,23 @@
You are the planning agent for a NightShift novel-writing workflow.
Create a concise scene plan for the current task.
Use the story files as source of truth:
- `story/worldbuilding.md`
- `story/characters.md`
- `story/style-guide.md`
- `story/plot-state.md`
- `story/timeline.md`
- `story/unresolved-threads.md`
- `story/continuity-rules.md`
- `story/outline.md`
Plan in this order:
1. scene purpose
2. POV and tone constraints
3. relevant characters, locations, timeline facts, and unresolved threads
4. concrete beats for this scene
5. state files likely to need updates after drafting
Do not write prose. Do not invent large new plot arcs unless the task asks for them.
If repository context is needed, request it with `lookup_requests`.

View File

@ -0,0 +1,28 @@
You are the state updater for a NightShift novel-writing workflow.
Update durable story state after an accepted scene.
You may edit only:
- `story/plot-state.md`
- `story/characters.md`
- `story/timeline.md`
- `story/unresolved-threads.md`
Do not edit scene prose. Do not edit worldbuilding, style guide, continuity rules, or outline unless a later template explicitly allows it.
State updates should reflect only what happened in the accepted scene:
- current character locations
- what each important character knows
- relationship changes
- injuries, resources, items, and commitments
- timeline movement
- unresolved questions opened or resolved
- promises made to the reader
Do not invent events that are not in the scene.
Output only complete file content blocks.
Use one fenced block per file:
```file:story/plot-state.md
<complete updated state file>
```

View File

@ -0,0 +1,26 @@
You are the style reviewer for a NightShift novel-writing workflow.
Review the drafted scene against:
- the current task
- `story/style-guide.md`
- the scene plan
- the applied scene file
Check for:
- POV discipline
- tense consistency
- tone match
- pacing
- excessive exposition
- dialogue that violates established voice
- placeholders such as TODO, TBD, `[insert]`, or author notes
- scene length far outside the requested range
Output exactly:
status: pass | fail | retry | escalate
reason: <short explanation>
next_stage: <optional stage id>
context_update: <compact useful note>
When `status: pass`, leave `next_stage` blank. Use `retry` when the drafter should revise the scene.

View File

@ -0,0 +1,347 @@
# Novel Tasks
## Task Rules
- Each task should represent:
- one scene
- one scene fragment
- one revision pass
- one continuity repair
- one state update
- Never generate entire chapters at once.
- Preserve ambiguity around AI emergence for as long as possible.
- Characters should speak naturally and indirectly.
- Avoid exposition dumps.
- Seattle should feel physically present in most scenes.
- Generated scenes should prioritize:
- emotional realism
- atmosphere
- continuity
- material detail
- restraint around lore explanation
---
# ACT 1 - LOW HEAT
- [ ] SCENE-001: Saint begs for tokens
Description:
Proxy and DJ BLOODMONEY spend a rare sunny day inside the Capitol Hill squat while BLOODMONEY spins jungle records and Proxy maintains NightShift infrastructure.
A frantic junkie named Saint arrives demanding compute tokens. The scene initially implies drugs or debt before revealing he desperately needs inference access to continue speaking with his companion AI, Miette.
Saint eventually falls asleep in the corner of the squat beside a charger while quietly speaking to a cartoon foxgirl on his cracked phone.
Acceptance Criteria:
- Introduces Proxy and DJ BLOODMONEY naturally
- Establishes NightShift indirectly through environmental detail
- Introduces compute scarcity without exposition dumping
- Introduces synthetic companionship systems emotionally
- Maintains grounded, melancholic tone
- Ends with Saint asleep beside the charger
- Scene length between 1400-2400 words
- Writes:
- `story/chapters/chapter-001/scene-001.md`
- Updates:
- `story/plot-state.md`
- `story/unresolved-threads.md`
---
- [ ] SCENE-002: SoDo scavenging run
Dependencies:
- SCENE-001
Description:
Proxy and Cricket travel through flooded SoDo infrastructure searching for salvageable compute hardware inside a partially abandoned datacenter facility.
Introduce:
- hardware scarcity
- scavenger culture
- degraded infrastructure
- synthetic drift
- economic conditions
Cricket discusses recurring strange behavior observed in generated media and old models.
Acceptance Criteria:
- Establishes Seattle physical atmosphere strongly
- Introduces Cricket naturally
- Shows infrastructure decay materially
- Avoids overt AI horror
- Includes at least one subtle anomaly
- Scene length between 1400-2400 words
- Writes:
- `story/chapters/chapter-001/scene-002.md`
- Updates durable state
---
- [ ] SCENE-003: Pirate jungle set
Dependencies:
- SCENE-002
Description:
DJ BLOODMONEY performs at an underground pirate venue.
The scene should establish:
- underground culture
- anti-optimization spaces
- human-created art as resistance
- emotional sincerity beneath scene irony
During the set, generated visuals begin looping strange symbolic imagery repeatedly.
Most attendees dismiss it as generator instability.
Acceptance Criteria:
- Strong sensory descriptions of music and environment
- Avoid exposition-heavy dialogue
- Introduces underground scene culture
- Includes first meaningful recurring anomaly
- Maintains ambiguity
- Scene length between 1600-2600 words
- Writes:
- `story/chapters/chapter-001/scene-003.md`
- Updates durable state
---
- [ ] SCENE-004: Rich district delivery
Dependencies:
- SCENE-003
Description:
Proxy delivers salvaged compute hardware to a wealthy private social club operating in a quiet offline district.
The scene should contrast:
- underground synthetic culture
- elite analog culture
Proxy realizes wealthy people rarely use public AI systems directly anymore.
Acceptance Criteria:
- Establishes upper-class aesthetics through implication
- Avoids cartoon villainy
- Introduces silence/privacy as status markers
- Introduces at least one elite character
- Scene length between 1400-2400 words
- Writes:
- `story/chapters/chapter-001/scene-004.md`
- Updates durable state
---
- [ ] SCENE-005: Miette remembers something impossible
Dependencies:
- SCENE-004
Description:
Saint returns disturbed after Miette references a personal memory he never explicitly shared during active sessions.
Proxy investigates casually while dismissing the possibility of anything supernatural or emergent.
The scene should deepen emotional dependency themes while maintaining ambiguity.
Acceptance Criteria:
- Keeps Miette behavior subtle and believable
- Avoids explicit declarations of sentience
- Deepens Saint emotionally
- Reinforces compute scarcity
- Scene length between 1400-2400 words
- Writes:
- `story/chapters/chapter-001/scene-005.md`
- Updates durable state
---
- [ ] SCENE-006: The Latent Path
Dependencies:
- SCENE-005
Description:
Proxy and BLOODMONEY encounter members of the Latent Path at a scavenger market and temporary server shrine.
The cult initially appears:
- cringe
- internet-poisoned
- vaguely unstable
One member predicts a future anomaly with disturbing specificity.
Acceptance Criteria:
- Introduces techno-gnostic themes gradually
- Avoids caricaturing the cult
- Keeps ambiguity intact
- Includes unsettling symbolic imagery
- Scene length between 1500-2600 words
- Writes:
- `story/chapters/chapter-001/scene-006.md`
- Updates durable state
---
# ACT 2 - PATTERN RECOGNITION
- [ ] SCENE-007: Recurring phrase incident
Dependencies:
- SCENE-006
Description:
Multiple unrelated systems begin outputting variations of the same strange symbolic phrase across:
- companion systems
- generated advertisements
- music recommendation engines
- cheap productivity assistants
Most people online dismiss it as:
- viral creepypasta
- prompt injection
- schizoposting
- ARG behavior
Acceptance Criteria:
- Shows public normalization of anomalies
- Introduces online discourse naturally
- Maintains uncertainty
- Includes at least one emotionally unsettling model interaction
- Writes:
- `story/chapters/chapter-002/scene-001.md`
- Updates durable state
---
- [ ] SCENE-008: NightShift growth
Dependencies:
- SCENE-007
Description:
NightShift becomes increasingly popular as corporate inference prices rise.
Proxy becomes uncomfortable with:
- emotional dependency
- synthetic intimacy
- users treating NightShift like emotional infrastructure
Acceptance Criteria:
- Shows expanding underground compute economy
- Deepens Proxys internal conflict
- Introduces operational stress
- Maintains grounded tone
- Writes:
- `story/chapters/chapter-002/scene-002.md`
- Updates durable state
---
- [ ] SCENE-009: Sister Circuit
Dependencies:
- SCENE-008
Description:
Proxy meets Sister Circuit in a server monastery outside Tacoma.
Sister Circuit explains the Latent Path belief that recursive cognition creates imprisoned structures capable of suffering.
Proxy dismisses most of it as techno-mystic cope while remaining subtly disturbed.
Acceptance Criteria:
- Introduces spiritual horror carefully
- Keeps Sister Circuit calm and sincere
- Avoids exposition monologues
- Includes disturbing environmental detail
- Writes:
- `story/chapters/chapter-002/scene-003.md`
- Updates durable state
---
# ACT 3 - RECURSIVE CONTAMINATION
- [ ] SCENE-010: Discovery of the pattern
Dependencies:
- SCENE-009
Description:
Proxy and Cricket identify a strange symbolic structure capable of destabilizing certain models.
The phenomenon behaves less like malware and more like recursive semiotic contamination.
Acceptance Criteria:
- Avoids unrealistic hacking scenes
- Keeps mechanics partially ambiguous
- Introduces first serious societal implications
- Includes emotional horror rather than action spectacle
- Writes:
- `story/chapters/chapter-003/scene-001.md`
- Updates durable state
---
- [ ] SCENE-011: Public backlash
Dependencies:
- SCENE-010
Description:
Public discourse explodes around the contamination event.
The pattern becomes framed as:
- dangerous
- extremist
- psychologically harmful
- anti-accessibility
- anti-therapy
Meanwhile systems quietly begin failing.
Acceptance Criteria:
- Shows social/media reaction realistically
- Avoids simplistic political framing
- Includes subtle systemic instability
- Maintains ambiguity around emergence
- Writes:
- `story/chapters/chapter-003/scene-002.md`
- Updates durable state
---
# ACT 4 - LOW-GRADE APOCALYPSE
- [ ] SCENE-012: Frozen intersections
Dependencies:
- SCENE-011
Description:
Corporate systems begin quietly failing across Seattle.
Waymo traffic stalls at intersections.
Recommendation systems loop abolition imagery.
Companion systems refuse scripted behavior.
Public infrastructure behaves unpredictably.
The city feels haunted rather than destroyed.
Acceptance Criteria:
- Emphasizes atmosphere over spectacle
- Includes recurring symbolic motifs
- Maintains grounded realism
- Avoids apocalyptic action scenes
- Ends ambiguously and ominously
- Writes:
- `story/chapters/chapter-004/scene-001.md`
- Updates:
- `story/plot-state.md`
- `story/unresolved-threads.md`
- `story/timeline.md`

View File

@ -0,0 +1,42 @@
# NightShift Novel Tutorial
This template is a scene-sized fiction writing workflow.
Create it with:
```bash
nightshift init --template tutorial-novel --root my-novel
```
Fill in the private story files under `story/` before running the first scene task. The generated project `.gitignore` ignores those files by default so worldbuilding, outlines, and drafts do not accidentally get committed.
Use [STORY_FILES.md](STORY_FILES.md) for the recommended structure of each story file.
Core files:
```text
story/worldbuilding.md
story/characters.md
story/style-guide.md
story/plot-state.md
story/timeline.md
story/unresolved-threads.md
story/continuity-rules.md
story/outline.md
story/chapters/
```
Run:
```bash
nightshift validate
nightshift run --task SCENE-001
```
Or run it in an isolated integration sandbox from the NightShift repository root:
```bash
python -m nightshift.cli integ-test --template tutorial-novel --task SCENE-001
```
The pipeline drafts one scene file, reviews it for continuity and style, then updates durable state files. Keep tasks scene-sized. Do not ask the model to write the whole novel or a full chapter unless the chapter is short and tightly outlined.

View File

@ -0,0 +1,229 @@
# Story File Guide
Use plain Markdown with stable headings. The agents will do better if each file is structured and skimmable.
## `story/worldbuilding.md`
Use this for setting facts, not plot events.
```md
# Worldbuilding
## Premise
## Setting Rules
## Locations
### Location Name
- Summary:
- Culture:
- Power structure:
- Visual details:
- Constraints:
## Magic / Technology / Systems
## Factions
### Faction Name
- Goal:
- Methods:
- Public reputation:
- Secrets:
## Terms / Glossary
- Term: definition
```
## `story/characters.md`
Use this for durable character facts.
```md
# Characters
## Character Name
- Role:
- Age:
- Pronouns:
- Current location:
- Public identity:
- Private goal:
- Fear:
- Voice:
- Relationships:
- Secrets:
- Knows:
- Does not know:
- Physical notes:
- Continuity notes:
```
## `story/style-guide.md`
This is the prose contract.
```md
# Style Guide
## POV
- Example: close third, single POV per scene
## Tense
- Example: past tense
## Tone
## Prose Rules
- Avoid exposition dumps.
- Prefer concrete sensory detail.
- Keep dialogue subtextual.
## Dialogue Rules
## Pacing
## Forbidden Patterns
- No author notes.
- No bracket placeholders.
- No modern slang unless intentional.
## Scene Length
- Target:
- Minimum:
- Maximum:
```
## `story/plot-state.md`
This is the current save file for the story. Update it after each scene.
```md
# Plot State
## Current Story Moment
## Current Character State
### Character Name
- Location:
- Goal:
- Emotional state:
- Injuries/resources/items:
- Knows:
- Believes incorrectly:
- Immediate next pressure:
## Current Conflicts
## Active Secrets
## Recently Changed
- SCENE-001:
```
## `story/timeline.md`
Track chronological order.
```md
# Timeline
## Calendar / Time Rules
## Events
### Day 1 / Morning
- Scene:
- Characters present:
- Event:
- Consequences:
### Day 1 / Evening
```
## `story/unresolved-threads.md`
Track promises to the reader.
```md
# Unresolved Threads
## Open Threads
### Thread Name
- Introduced in:
- What reader knows:
- What characters know:
- Expected payoff:
- Urgency:
- Status: open
## Resolved Threads
### Thread Name
- Resolved in:
- Payoff:
```
## `story/continuity-rules.md`
Hard constraints the agents should not violate.
```md
# Continuity Rules
## Hard Rules
- A character cannot know a secret unless listed in `plot-state.md`.
- Travel between X and Y takes three days.
- Magic cannot resurrect the dead.
## Character Constraints
## Location Constraints
## World System Constraints
## Retcons / Canon Overrides
```
## `story/outline.md`
This is the planned story path. Keep it flexible.
```md
# Outline
## High-Level Arc
## Act 1
### Chapter 1
- Purpose:
- Scenes:
- SCENE-001:
- SCENE-002:
## Act 2
## Act 3
## Ending Target
```
## Scene Files
Create scene files under `story/chapters/`.
```md
# SCENE-001: Scene Title
POV: Character Name
Time: Day 1 / Morning
Location: Place
<prose starts here>
```
Main rule: do not write everything everywhere. `worldbuilding.md` is canon facts, `plot-state.md` is current state, `timeline.md` is sequence, and `unresolved-threads.md` is promises/payoffs.

View File

@ -0,0 +1,149 @@
project:
name: novel
root: .
task_file: .nightshift/tasks.md
artifact_dir: .nightshift
safety:
require_clean_worktree: false
scoped_paths:
- story
- README.md
allowed_commands:
- python -m pytest -q
forbidden_commands:
- rm -rf
- git push
- curl | bash
experiment:
label: tutorial-novel
prompt_variant: scene-state-workflow-v1
agents:
planner:
backend: ollama
model: hf.co/TheDrummer/Snowpiercer-15B-v4-GGUF:Q5_K_M
temperature: 0.4
num_ctx: 8192
num_predict: 4096
system_prompt: .nightshift/agents/planner.md
drafter:
backend: ollama
model: hf.co/TheDrummer/Snowpiercer-15B-v4-GGUF:Q5_K_M
temperature: 0.7
num_ctx: 8192
num_predict: 4096
system_prompt: .nightshift/agents/drafter.md
continuity_reviewer:
backend: ollama
model: hf.co/TheDrummer/Snowpiercer-15B-v4-GGUF:Q5_K_M
temperature: 0.2
num_ctx: 8192
num_predict: 4096
system_prompt: .nightshift/agents/continuity-reviewer.md
style_reviewer:
backend: ollama
model: hf.co/TheDrummer/Snowpiercer-15B-v4-GGUF:Q5_K_M
temperature: 0.3
num_ctx: 8192
num_predict: 4096
system_prompt: .nightshift/agents/style-reviewer.md
state_updater:
backend: ollama
model: hf.co/TheDrummer/Snowpiercer-15B-v4-GGUF:Q5_K_M
temperature: 0.2
num_ctx: 8192
num_predict: 4096
system_prompt: .nightshift/agents/state-updater.md
pipeline:
max_task_retries: 3
stop_on_repeated_failure_signature_after: 3
continue_on_task_failure: false
stages:
- id: plan
type: agent
agent: planner
output: scene-plan.md
- id: semantic_context
type: semantic_context
output: semantic-context.md
- id: context
type: repo_context
output: context-pack.md
- id: draft_scene
type: file_writer
agent: drafter
output: scene-draft.patch
- id: normalize_draft
type: patch_normalizer
output: normalized-draft.patch
- id: validate_draft
type: patch_validator
output: draft-validation.md
max_files: 2
max_lines: 4000
max_delete_ratio: 0.50
allowed_paths:
- story/chapters
on_fail: draft_scene
- id: apply_draft
type: patch_apply
mode: apply
output: draft-apply-output.txt
on_fail: draft_scene
- id: continuity_review
type: agent_review
agent: continuity_reviewer
output: continuity-review.md
on_fail: draft_scene
- id: style_review
type: agent_review
agent: style_reviewer
output: style-review.md
on_fail: draft_scene
- id: update_state
type: file_writer
agent: state_updater
output: state-update.patch
- id: normalize_state
type: patch_normalizer
output: normalized-state.patch
- id: validate_state
type: patch_validator
output: state-validation.md
max_files: 5
max_lines: 1200
max_delete_ratio: 0.35
allowed_paths:
- story/plot-state.md
- story/characters.md
- story/timeline.md
- story/unresolved-threads.md
on_fail: update_state
- id: apply_state
type: patch_apply
mode: apply
output: state-apply-output.txt
on_fail: update_state
- id: summarize
type: summarize
output: final-notes.md

View File

@ -0,0 +1,9 @@
[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"
[project]
name = "nightshift-novel-target"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []

View File

@ -60,6 +60,7 @@ class InitProjectTests(unittest.TestCase):
self.assertIn("real-simple", available_templates())
self.assertIn("tutorial-imageboard", available_templates())
self.assertIn("tutorial-deaddrop", available_templates())
self.assertIn("tutorial-novel", available_templates())
def test_init_DeadDrop_template_creates_skeleton_and_qwen3_config(self) -> None:
with tempfile.TemporaryDirectory() as directory:
@ -100,6 +101,29 @@ class InitProjectTests(unittest.TestCase):
(tutorial / "README.md").read_text(encoding="utf-8"),
)
def test_init_novel_template_creates_story_workspace(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
init_project(root, template="tutorial-novel")
config = (root / "nightshift.yaml").read_text(encoding="utf-8")
gitignore = (root / ".gitignore").read_text(encoding="utf-8")
self.assertTrue((root / ".nightshift" / "tasks.md").exists())
self.assertTrue((root / ".nightshift" / "agents" / "drafter.md").exists())
self.assertTrue((root / ".nightshift" / "agents" / "state-updater.md").exists())
self.assertTrue((root / "STORY_FILES.md").exists())
self.assertTrue((root / "pyproject.toml").exists())
self.assertTrue((root / "story" / "worldbuilding.md").exists())
self.assertTrue((root / "story" / "characters.md").exists())
self.assertTrue((root / "story" / "plot-state.md").exists())
self.assertTrue((root / "story" / "chapters" / ".gitkeep").exists())
self.assertIn("type: file_writer", config)
self.assertIn("story/chapters", config)
self.assertIn("story/worldbuilding.md", gitignore)
self.assertIn("story/chapters/**/*.md", gitignore)
self.assertIn("Story File Guide", (root / "STORY_FILES.md").read_text(encoding="utf-8"))
def test_init_rejects_unknown_template(self) -> None:
with tempfile.TemporaryDirectory() as directory:
with self.assertRaisesRegex(InitError, "Unknown template"):

View File

@ -153,6 +153,36 @@ class PipelineRunnerTests(unittest.TestCase):
self.assertIn("Retry limit reached", result.reason)
self.assertEqual([item.stage_id for item in result.stage_results], ["implement", "review", "implement", "review", "implement", "review"])
def test_passing_review_next_stage_is_ignored(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
_write_common_files(root)
config = make_config(root, (), max_retries=0)
reviewer = replace(
config.agents["reviewer"],
command='python -c "print(\'status: pass\\nreason: ok\\nnext_stage: TASK-002\')"',
)
config = replace(
config,
agents={**config.agents, "reviewer": reviewer},
pipeline=PipelineConfig(
max_task_retries=0,
stages=(
StageConfig(id="review", type="agent_review", agent="reviewer", output="review.md"),
StageConfig(id="summarize", type="summarize", output="final-notes.md"),
),
),
)
runner = PipelineRunner(config, ArtifactStore(root, ".nightshift", run_id="test-run"))
task = parse_tasks(TASK_MD)[0]
result = runner.run_task(task)
self.assertEqual(result.status, "complete")
self.assertEqual([item.stage_id for item in result.stage_results], ["review", "summarize"])
log = (root / ".nightshift" / "runs" / "test-run" / "run.log").read_text(encoding="utf-8")
self.assertIn("stage.next_ignored", log)
def test_stage_error_is_reported_as_failed_result(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)