# OpenLife v1.17 — Changelog

**Branch:** `main`
**Predecessor:** v1.16.0 (tagged 2026-05-22)
**Released:** 2026-05-22

v1.17 ships the **Autonomous Story Loop** — the missing piece that
turns story-driven discipline into a single-command experience. Before
v1.17:

```bash
# User-driven (manual)
@builder implementa a story 1.1
# ...20-60min of conversation
# user marks [x] checkboxes
# user transitions status manually
```

After v1.17:

```bash
# Loop-driven (autonomous)
openlife story execute 1.1
# Story closes itself — branches, commits locally, awaits push
```

Track B Phase 2 of the v1.15→v1.18 SaaS Forge arc. Track B Phase 3
(Ship + Deploy) follows in v1.18.

## What landed by phase

### Phase C1 — Foundations: GitOps + ResponseStructureExtractor

| Component | Surface |
|---|---|
| `src/orchestrator/util/GitOps.ts` | `getCurrentBranch / getDirtyFiles / isClean / isOnProtectedBranch / createBranch / stageFiles / commit` + `branchNameFromStory()` slugify helper. Hard refuses `push` operations at args level. |
| `src/orchestrator/util/ResponseStructureExtractor.ts` | Pure regex utilities: `extractFilePaths / extractCodeBlocks / extractACsChecked / extractTestCmd / parseAll`. No LLM dependency; safe for sync use. |
| Tests | 8 GitOps scenarios + 5 ResponseStructure scenarios — all green. |

### Phase C2 — PersonaRouter + HostInvoker adapters

| Component | Surface |
|---|---|
| `src/orchestrator/story/PersonaRouter.ts` | `pickPersona(story)` maps story title prefix to persona sequence: `[BUG]/[FIX]→builder+sentinel`, `[FEATURE]→genesis+architect+builder+sentinel`, `[DESIGN]→prism+builder+sentinel`, `[DATA]→mesh+builder+sentinel`, etc. |
| `src/orchestrator/story/HostInvoker.ts` | Pluggable adapter registry. `HostInvoker.invoke(prompt, opts)` resolves an adapter from `opts.provider` or `OPENLIFE_HOST_ADAPTER` env. |
| `adapters/ClaudeAdapter.ts` | Spawns `claude -p` via stdin, captures stdout, kills on timeout. |
| `adapters/GeminiAdapter.ts` | Spawns `gemini` with same contract; tries JSON-envelope extraction. |
| `adapters/CodexAdapter.ts` | Spawns `codex exec`; reuses `TaskExecutor.extractCodexFinalMessage`. |
| `adapters/MockAdapter.ts` | Deterministic test double driven by `OPENLIFE_MOCK_ADAPTER_SCRIPT` (JSON string or path). |
| Tests | 8 PersonaRouter + 5 HostInvoker scenarios — green. |

### Phase C3 — StoryStateTracker + HandoffArtifact

| Component | Surface |
|---|---|
| `src/orchestrator/story/StoryStateTracker.ts` | `checkAC(path, idx) / appendDevAgentRecord / appendFileList / transitionStatus / countOpenACs / countTotalACs`. All writes via `AtomicWriter.writeStringAtomic`. |
| `src/orchestrator/story/HandoffArtifact.ts` | `buildHandoffArtifact(input) → HandoffRecord` + `serializeHandoff/parseHandoff`. Three-tier trim strategy keeps YAML ≤2000 chars (~500 tokens) under adversarial inputs. |
| Tests | 5 HandoffArtifact + 4 StoryStateTracker scenarios — green. |

### Phase C4 — StoryExecutor loop + CLI

| Component | Surface |
|---|---|
| `src/orchestrator/story/StoryExecutor.ts` | Main loop. Loads story → branch guard → persona sequence (via PersonaRouter) → iterate (compose prompt → invoke host → parse → mutate story → run tests → commit if AC progress) → transition + QA gate. Max-iters cap (default 10, env override, ceiling 50). No-progress watchdog (3 strikes). |
| `src/cli/StoryExecuteCommand.ts` | `openlife story execute <id>` with flags: `--max-iters`, `--persona`, `--stop-after`, `--force`, `--dry-run-tests`, `--plan-only`, `--adapter`, `--json`. |
| Tests | 5 StoryExecutor end-to-end scenarios (plan-only, happy path, story-not-found, dirty-tree-refused, no-progress-halt) — all green using MockAdapter. |

### Phase C5 — Docs + ship

Docs: `docs/v1.17-changelog.md` (this file). `docs/autonomous-loop.md`
deferred to v1.17.1 (executor stable, can document later iterating on
real user feedback).

## Safety model — non-negotiable boundaries

- **No `git push` from the loop.** GitOps refuses push at args level.
  Push is `@vortex` exclusive per `.openlife/method/agents/vortex.md:50`.
  Pass to `openlife ship` (v1.18) when ready.
- **Branch protection.** Detects `main / master / develop / production`
  and forces creation of `story-<id>-<slug>` before any commit.
- **Dirty working tree refused** by default. Pass `--force` to override.
- **Max-iters cap.** Default 10, env `OPENLIFE_STORY_MAX_ITERS` can
  lower, hardcoded ceiling 50 — never higher regardless of env.
- **No-progress watchdog.** 3 consecutive iterations without an AC
  marked → halt + emit `no_progress_after_3_iters` reason.
- **Adapter timeout.** 5min default per invocation; SIGTERM child
  on expiry, SIGKILL after 2s grace.
- **Atomic story file mutation.** All `*.story.md` updates via
  `AtomicWriter.writeStringAtomic`. No partial writes survive a crash.
- **Test cmd whitelist.** Only `npm/yarn/pnpm/bun/pytest/jest/vitest/
  mocha/cargo/go/deno` invocations allowed. Anything else returns
  `false` without spawning.
- **Role-gated status transitions.** Delegates to
  `StoryLifecycle.canTransition` — dev cannot transition to Done
  (qa/po only).

## Test coverage

7 new test files, 40 scenarios. All green. Verified zero regression
in 9 adjacent v1.12-v1.15 test files (story-parser, story-registry,
story-lifecycle, context-composer, scaffold-engine, design-*, watchdog,
atomic-writer).

| Test file | Scenarios |
|---|---|
| `test_git_ops.ts` | 8 |
| `test_response_structure_extractor.ts` | 5 |
| `test_persona_router.ts` | 8 |
| `test_host_invoker.ts` | 5 |
| `test_handoff_artifact.ts` | 5 |
| `test_story_state_tracker.ts` | 4 |
| `test_story_executor.ts` | 5 |

Run via `npm run test:story-loop` (chains all 7).

## CLI surface

```bash
# Standard
openlife story execute 1.1                    # full autonomous, default 10 max-iters
openlife story execute 1.1 --plan-only        # plan + persona pick, no mutation
openlife story execute 1.1 --max-iters=5      # safety cap
openlife story execute 1.1 --persona=builder  # force single persona
openlife story execute 1.1 --stop-after=builder  # truncate sequence; skip QA gate
openlife story execute 1.1 --dry-run-tests    # mutate but skip test runs
openlife story execute 1.1 --force            # allow dirty working tree
openlife story execute 1.1 --adapter=mock     # test with MockAdapter
openlife story execute 1.1 --json             # structured output

# CI usage
OPENLIFE_HOST_ADAPTER=mock openlife story execute 1.1 --max-iters=3
```

## Out of scope (deferred)

- **`openlife story execute --all-ready`** — flag accepted by CLI but
  loops serially. Parallel execution (queue with backpressure, file
  locks per story) is v1.17.1.
- **AI-driven test generation** — current loop runs existing tests; if
  a story's AC has no test, sentinel flags but doesn't auto-write.
  v1.17.2 maybe.
- **Story dependency resolution** — `dependsOn[]` field in schema
  exists but the loop ignores it. v1.18+.
- **Code-block direct application** — adapters reporting code blocks
  in their response are currently relied upon to ALSO write the files
  (Claude Code does this natively via its Read/Write tools). Future
  v1.17.x can extract `extractCodeBlocks()` output and write directly
  when filePath hint is present.
- **Telegram `/core story execute`** — daemon doesn't autonomously run
  loops yet. v1.18 bridge integration.
- **Live SSE progress streaming** — events emitted, no consumer in
  `openlife bridge listen` yet.
- **SvelteKit template (v1.16.1)** — separately scheduled.

## Tarball impact

```
v1.16.0 → v1.17.0
package size:    1.9MB → 1.95MB  (+50KB)
unpacked size:   7.4MB → 7.6MB   (+200KB)
total files:     1199  → 1218    (+19)
```

The +19 files are 10 new TS source + 7 new test + 2 new docs.

## Upgrade

```bash
openlife update --global

# Verify
openlife --version                  # 1.17.0
openlife story execute --help       # 8 flags including --plan-only
```

## File summary

**New (11):**
- `src/orchestrator/util/GitOps.ts`
- `src/orchestrator/util/ResponseStructureExtractor.ts`
- `src/orchestrator/story/PersonaRouter.ts`
- `src/orchestrator/story/HostInvoker.ts`
- `src/orchestrator/story/adapters/{Claude,Gemini,Codex,Mock}Adapter.ts`
- `src/orchestrator/story/StoryStateTracker.ts`
- `src/orchestrator/story/HandoffArtifact.ts`
- `src/orchestrator/story/StoryExecutor.ts`
- `src/cli/StoryExecuteCommand.ts`

**Tests (7):** see Test coverage section.

**Modified (3):**
- `src/cli/StoryCommand.ts` — registers `execute` subcommand
- `package.json` — 8 new test scripts + chain into `test:all`,
  version 1.16.0 → 1.17.0, `docs/v1.17-changelog.md` added to files[]
- `CHANGELOG.md` — release entry

## Commits

To be assembled at ship time.

## Roadmap continuation

- **v1.18.0** — `openlife ship` (vortex-delegated git push + PR
  creation) + `openlife deploy --provider=vercel|railway` adapters +
  SaaS skills (clerk-wire, stripe-billing-wire, supabase-rls-wire,
  vercel-deploy-wire). Closes the v1.15→v1.18 SaaS Forge arc.
- **v1.16.1** — SvelteKit-Postgres-Lucia template + `scaffold custom
  --from=<git-url>` (independent track).
- **Issue #9** — Cross-machine bridge (Tailscale + cloud memory + mTLS),
  independent of the v1.17 work.
