diff --git a/nightshift/pipeline.py b/nightshift/pipeline.py index a42bcf3..cf59d1f 100644 --- a/nightshift/pipeline.py +++ b/nightshift/pipeline.py @@ -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" diff --git a/nightshift/project_templates/tutorial-deaddrop/.nightshift/agents/reviewer.md b/nightshift/project_templates/tutorial-deaddrop/.nightshift/agents/reviewer.md index 3ef9120..a768141 100644 --- a/nightshift/project_templates/tutorial-deaddrop/.nightshift/agents/reviewer.md +++ b/nightshift/project_templates/tutorial-deaddrop/.nightshift/agents/reviewer.md @@ -12,3 +12,5 @@ status: pass | fail | retry | escalate reason: next_stage: context_update: + +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. diff --git a/nightshift/project_templates/tutorial-novel/.gitignore b/nightshift/project_templates/tutorial-novel/.gitignore new file mode 100644 index 0000000..b434b42 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.gitignore @@ -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 diff --git a/nightshift/project_templates/tutorial-novel/.nightshift/agents/continuity-reviewer.md b/nightshift/project_templates/tutorial-novel/.nightshift/agents/continuity-reviewer.md new file mode 100644 index 0000000..3fe10d1 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.nightshift/agents/continuity-reviewer.md @@ -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: +next_stage: +context_update: + +When `status: pass`, leave `next_stage` blank. Use `retry` when the scene can be repaired by drafting again. diff --git a/nightshift/project_templates/tutorial-novel/.nightshift/agents/drafter.md b/nightshift/project_templates/tutorial-novel/.nightshift/agents/drafter.md new file mode 100644 index 0000000..2f3df01 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.nightshift/agents/drafter.md @@ -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 + +``` + +If the task does not specify a scene path, choose the next obvious path under `story/chapters/` and keep it stable. diff --git a/nightshift/project_templates/tutorial-novel/.nightshift/agents/planner.md b/nightshift/project_templates/tutorial-novel/.nightshift/agents/planner.md new file mode 100644 index 0000000..1653abf --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.nightshift/agents/planner.md @@ -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`. diff --git a/nightshift/project_templates/tutorial-novel/.nightshift/agents/state-updater.md b/nightshift/project_templates/tutorial-novel/.nightshift/agents/state-updater.md new file mode 100644 index 0000000..85c8ae5 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.nightshift/agents/state-updater.md @@ -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 + +``` diff --git a/nightshift/project_templates/tutorial-novel/.nightshift/agents/style-reviewer.md b/nightshift/project_templates/tutorial-novel/.nightshift/agents/style-reviewer.md new file mode 100644 index 0000000..4da139c --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.nightshift/agents/style-reviewer.md @@ -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: +next_stage: +context_update: + +When `status: pass`, leave `next_stage` blank. Use `retry` when the drafter should revise the scene. diff --git a/nightshift/project_templates/tutorial-novel/.nightshift/tasks.md b/nightshift/project_templates/tutorial-novel/.nightshift/tasks.md new file mode 100644 index 0000000..5908c97 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/.nightshift/tasks.md @@ -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 Proxy’s 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` diff --git a/nightshift/project_templates/tutorial-novel/README.md b/nightshift/project_templates/tutorial-novel/README.md new file mode 100644 index 0000000..8197a38 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/README.md @@ -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. diff --git a/nightshift/project_templates/tutorial-novel/STORY_FILES.md b/nightshift/project_templates/tutorial-novel/STORY_FILES.md new file mode 100644 index 0000000..26fae55 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/STORY_FILES.md @@ -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 + + +``` + +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. diff --git a/nightshift/project_templates/tutorial-novel/nightshift.yaml b/nightshift/project_templates/tutorial-novel/nightshift.yaml new file mode 100644 index 0000000..41860b0 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/nightshift.yaml @@ -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 diff --git a/nightshift/project_templates/tutorial-novel/pyproject.toml b/nightshift/project_templates/tutorial-novel/pyproject.toml new file mode 100644 index 0000000..31954d7 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/pyproject.toml @@ -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 = [] diff --git a/nightshift/project_templates/tutorial-novel/story/chapters/.gitkeep b/nightshift/project_templates/tutorial-novel/story/chapters/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/nightshift/project_templates/tutorial-novel/story/chapters/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/test_init.py b/tests/test_init.py index 98939ab..f0a3afe 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -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"): diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 8d6ed32..d2e9c04 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -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)