# Ctx Progress

> Tracks the WORK truth of a project — where we are, what's next, and what to read first — in a living progress file a fresh session can resume from, including cross-session handoff. Use when updating work or project state in a ctx knowledge base, recording what's done or what's next, opening a progress subtree as it grows past a flat file, handing off to a new session, or auditing a project for progress drift.

- Skill: `motiful/ctx-progress` (Agent Skill)
- Install (CLI): `npx skillmds@latest add motiful/ctx-progress`
- Raw SKILL.md: https://api.skillmd.com/api/skills/motiful/ctx-progress/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- License: MIT
- Author: motiful (https://skillmd.com/u/motiful)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/motiful/ctx-progress

---


# ctx-progress — the LIVING work-truth doc

> The lifetime model and shared conventions live in [`../ctx`](../ctx/SKILL.md); this is the how-to for the LIVING `progress/` folder.

## Execution Procedure

`progress/` is the **supervisor's clipboard**, not a second spec: it holds the WORK truth (where we are / next / read-first) and **POINTS at `spec/` + `decisions/`, never restating a product fact**. The durable "what/why" lives in spec/decisions; progress tracks *doing it against them*. **The ordered `T###` task list lives here** (not in the spec — ctx-spec §14 was retired); each `T###` points back to the `REQ-###` in `spec/` that it implements.

```
track_work_truth(project) → one LIVING current-position a fresh session resumes from

# EP1 — Locate / create the progress root
root = ctx/progress/                      # fixed, exactly ONE (see Hard constraints)
doc  = flat progress.md UNTIL ~150 lines, then graduate to a tree

# EP2 — Update work state (the common case)
on any decided work-state change (active leaf moved · next settled · user lock · subagent report):
    write_into_progress(change)           # sink SAME-TURN; never leave decided state only in chat
    reference(spec, decisions, by="pointer")   # never restate a product fact
    # the ordered T### task list + each task's status live HERE (not in spec); every T### → a spec REQ-###
    # row format: `T### <task title> — status: done | active | blocked → REQ-###` (one line per task;
    # this status vocabulary is per-TASK, distinct from the frontmatter `status` field above, which
    # describes the whole progress doc, not an individual task)

# EP3 — Open a subtree (a node passes ~150 lines)
open_child(); parent = one-line summary + link; never duplicate child content into parent

# EP4 — Pre-reset checkpoint (before /compact, a context reset, or dispatching an independent session)
sink_decided_state()                      # anything decided this session → progress/ (+ spec/decisions if product truth)
verify(active_leaf_current ∧ next_step_written ∧ nothing_decided_only_in_chat)   # progress IS the handoff; no separate prompt artifact

# EP5 — Incoming session (resume)
read(CLAUDE.md → progress root → active node → its linked spec/decisions); summarize; WAIT for confirmation before work

# EP6 — Audit for drift
assert one_active_leaf_per_live_subtree ∧ frontmatter_valid ∧ pointers_not_restated ∧ thresholds_respected → fix before handoff

# Before committing — apply the cross-cutting constraints
apply("../ctx/references/consistency.md")   # single-source · same-change · verify-canonical · gate + conventions

# DoD per work chunk: commit → update progress → archive-when-long  (see Hard constraints)
```

## Where it lives (no location matrix)

In this collection the location is **fixed**: the knowledge root's `ctx/progress/`. For a shippable artifact whose docs must not ship, it is `<name>-ctx/progress/` (that external `<name>-ctx/` folder IS the knowledge root — no nested `ctx/`). There is no per-archetype location matrix to consult — ctx already fixed `ctx/` as the root, so `progress/` is unambiguous.

`progress/` is a LIVING doc: current focus + next + "read first". It is the WORK truth; `spec/` + `decisions/` are the PRODUCT truth. Two living docs, different domains, no SSOT clash — **guarded by the pointer rule below**.

## Single file, or a tree (JBGE)

Start with **one file** `ctx/progress/progress.md`. Graduate to a **tree** only when the flat file would exceed **~150 lines** — never pre-open a tree. (JBGE: a single file is enough until it demonstrably is not; a premature tree is empty ceremony.)

**There are exactly TWO size thresholds** (this is their single canonical statement — companions reference these numbers, they do not restate divergent ones):

| Threshold | Trigger | Action |
|---|---|---|
| **~150 lines** | a flat `progress.md` grows past it | **graduate flat → tree** = open the first child. ("graduate to a tree" and "open a child" are the SAME event, not two thresholds — this is the point that removes the three-numbers illusion.) |
| **~200 lines** | any single node (flat file OR a tree node) grows past it | **archive** the completed portion per the LIVING archive mechanic (see DoD below). |

- **Flat**: `ctx/progress/progress.md` (frontmatter + current position + next + pointers).
- **Tree**: `ctx/progress/README.md` (entry) + `NN-<slug>.md` children (zero-padded, creation-ordered per consistency.md numbering). A child that itself grows gets a sibling `NN-<slug>/` dir with its own `README.md`. Recommended nesting ceiling: **2 levels** below the root; a 3rd level usually means the work should be factored out as its own project.

**Frontmatter on every progress doc** (this is the canonical field set):

```yaml
---
title: <human-readable, one line, no markdown>
created: YYYY-MM-DD        # absolute ISO date, never relative ("yesterday")
updated: YYYY-MM-DD        # bump on every substantive edit
status: draft | active | stale | archived
---
```

Optional when useful: `parent: ../README.md`, `owner`, `supersedes`. Status is one-way in practice: `draft → active → stale → archived`.

## Exactly one active leaf — per live session

**[LOAD-BEARING — hard constraint, see § Hard constraints below]** A well-formed progress tree (or flat file) has **exactly one `active` leaf per live session** — the current position that session resumes from. In the ordinary single-session case that is one active leaf for the whole tree.

When work legitimately runs **in parallel across sessions** — each session on its own child subtree (opened per the "subtask complex enough to spawn its own session" rule below) — the tree holds **one active leaf per live-session subtree, so several at once is normal**, not a propagation failure. The invariant is *within a subtree*: each live-session subtree has exactly one active leaf, and the root surfaces them side by side through the existing parent-pointer mechanism. Two active leaves **inside the same subtree** still mean propagation failed — repair before handing off. This is what lets any given session find *its* "where we are" in one hop.

## Parent → child by POINTER, never copy

The parent carries a one-line summary + a link into the child subtree; the detail lives only in the child. **Never duplicate child content into the parent** — copy-style references always drift; pointer-style cannot. The parent surfaces the child's *status* (that's the one piece of child-derived state an index must carry), not its body.

```markdown
## 02 · Stripe integration — status: active

See [`./02-stripe-integration/`](./02-stripe-integration/README.md) for the subtree.
Current position: webhook signature validation (2/4 e2e tests passing). Next: Stripe Tax for EU.
```

## Pointer, don't restate (the SSOT guard)

**[LOAD-BEARING — hard constraint, see § Hard constraints below]** Progress **references** spec and decisions by pointer; it **never restates a product fact**. "Where we are on the auth flow" belongs in progress; "the auth flow requires PKCE" belongs in `spec/`, and progress links to it. Restating the spec fact in progress creates a second copy that goes stale — the exact rot ctx exists to prevent.

The same discipline extends to `ctx/services.md` when `ctx-serve` is in use: a topology change (new service, moved port) gets a one-line pointer from the active leaf, never a restated topology detail (see `ctx-serve` § serve ⟂ progress).

## Session continuity — progress IS the handoff

There is **no separate handoff artifact**. A well-maintained `progress/` already carries everything a fresh session needs, so continuity is just two moments — a pre-reset checkpoint out, and a guided read in.

**Pre-reset checkpoint (outgoing)** — before a `/compact`, a context reset, or dispatching an independent session, run one gate: **sink any state decided this session** into `progress/` (and settled product truth into `spec/`/`decisions/`), then **verify progress reflects the current position** — active leaf current, next step written, nothing decided sitting only in chat. That is all "handoff" is now: because state hits disk continuously (sink same-turn below), progress is always current, so a reset is safe at any moment. Do **not** generate a standalone handoff-prompt file — if a fact isn't in progress, fix progress, don't paper over it in a prompt. *(The compaction mechanism itself — how a tool summarizes history — is the tool's concern, not this skill's; ctx owns only that the state is externalized. Optional Claude-Code convenience: a `/ctx-compact` command can trigger this checkpoint before you run `/compact` — the checkpoint is tool-agnostic, the command is just a shortcut.)*

**Incoming (resume)** — read in this order, then stop and confirm:

```
1. CLAUDE.md (+ CLAUDE.local.md if present)   → project instructions
2. ctx/progress/README.md (or progress.md)    → the current active node
3. the active-node file                        → the concrete next step
4. files the active node links to (cap ~3)     → upstream spec/decision context
→ summarize goal / current position / next step to the user, WAIT for confirmation. Do NOT start work first.
```

Do not read the whole tree — it preserves history verbatim on purpose; reading all of it burns the context the session needs to work.

## Sink same-turn (decided state hits disk before the turn ends)

Work-state that got **decided this turn** — the active leaf moved, a next step settled, a user verbal lock ("OK do X" / "lock X"), a subagent's status report — must be written into `progress/` **in the same assistant turn it happens**. Never end a turn, dispatch a subagent, or approach compaction with decided state sitting only in chat. The failure mode this prevents: *chat remembers, files don't; compaction comes, the decision is gone.* A quick end-of-turn self-check — "did the active position, a next step, or a user lock change this turn? then the file changed too" — is cheaper than reconstructing lost state next session.

**Two gates, two granularities — the tracking gate must not govern the SOT.** Sink-same-turn is the **tracking** gate: it keeps `progress/` current-to-the-turn, at the leanness the rest of this skill already requires — *pointer, don't restate*; the minimal *current position + next + read-first* shape (see the worked example); reasoning, options, and detail live in `reports/` and git, never in progress. It does **NOT** govern the **SOT** gate (`spec/` + `decisions/`). A durable product truth or an ADR crosses that gate only once the conclusion has been **surfaced in a report and survived the owner's review** — the ctx-report keep/drop distillation, where the owner's silence on a *reviewed* point is assent, but an un-surfaced design the agent synthesized this turn is not (the owner never saw it). So: sink *tracking* every turn; sink to the *SOT* only what has passed review.

## Definition of Done (per unit of work)

1. **Commit** the code/doc change.
2. **Update `progress/`** in the same commit: bump `updated`, move the active leaf, ripple the parent's status/pointer line.
3. **Archive when long** — when a progress file/subtree is complete or a node reaches the **~200-line archive threshold** (distinct from the ~150-line graduate-flat→tree threshold), retire it per the **LIVING lifetime-class mechanic** in **[`../ctx/references/consistency.md` § Archive mechanics](../ctx/references/consistency.md)**: localized staleness → edit in place (git keeps the old text); a *foundational* shift → move the whole old doc/subtree to `progress/archive/` under the canonical archive filename and write one clean new doc reusing the canonical name. Do not restate that mechanic or the filename format here — both are defined once there.

## Worked example — a minimal `progress.md`

```markdown
---
title: Acme Auth — Progress
created: 2026-06-20
updated: 2026-07-03
status: active
---

# Auth — where we are

Current position: PKCE token exchange wired; refresh-token rotation is the open piece.
Next: implement rotation + its EARS acceptance test.

Spec: [`../spec/auth.md`](../spec/auth.md) · Why PKCE: [`../decisions/0007-pkce.md`](../decisions/0007-pkce.md)
```

That is the whole shape at flat scale: frontmatter · current position · next · pointers to the SOT (no restated product facts). Grow it into a tree only when it crosses ~150 lines.

## The three lifetime classes

Not restated here — see **[`../ctx/SKILL.md` § The model](../ctx/SKILL.md)**. `progress/` is **LIVING**: edited in place, always current, never carrying a stale section inline.

## Stop condition (JBGE)

The smallest progress surface that lets a fresh session continue correctly. One flat file is the default; a tree is earned, not scaffolded ahead of need. If handoff needs 10+ files or private chat history to continue, the progress doc has failed — reshape it, don't pad the prompt.

## Hard constraints (progress hygiene)

> The MUST/NEVER lines below are the **domain** constraints for progress — binding, load-before-act; they fire when a `progress/` file is touched. The **cross-cutting** rules (single-source, same-change, verify-canonical, the gate) live in [`../ctx/references/consistency.md`](../ctx/references/consistency.md) and apply on top. Applies to every file in a `progress/` tree, a flat `progress.md`, or a `<project>-ctx/progress/`; NOT to skill `references/`, `scripts/`, design docs, or code.

### Location & single root

- **MUST** maintain exactly **one** progress root per project (the knowledge root's `progress/`). Two roots cause drift.
- **NEVER** place progress files under any `.claude/`, `.agents/`, or agent-workspace directory — those are for config, not project state.
- **NEVER** maintain two progress files in one project with mutual cross-references. When a project legitimately spans repos (a shippable artifact + its `<name>-ctx` store), **MUST** declare "Source of Truth" at the top of the authoritative root; other repos edit the SoT only.

### Frontmatter & the pointer rule

- **MUST** give every progress / research / tree-entry doc YAML frontmatter with `title`, `created`, `updated`, `status`; `status` ∈ {`draft`,`active`,`stale`,`archived`} (no synonyms); update `updated` on every substantive edit.
- **MUST** use absolute ISO dates (`YYYY-MM-DD`). **NEVER** relative phrasing (`yesterday`, `Thursday`), **NEVER** filesystem `mtime` as a substitute (clobbered by clone / auto-save / indexers).
- **MUST** have progress **reference** spec/decisions **by pointer** (summary line + link). **NEVER restate a product fact in progress** — a restated fact drifts from `spec/`. (This is the LOAD-BEARING SSOT guard above.)

### Tree graduation & archive DoD

> The two size thresholds — **~150 lines** (flat → open a child) and **~200 lines** (archive a completed node) — are defined once in **§ Single file, or a tree** above. These MUST lines enforce those two numbers; they do not introduce a third.

- **MUST** open a child subtree before a progress file exceeds **~150 lines** (or when a subtask is complex enough to spawn its own session). Reference children by **pointer**; **NEVER** duplicate child content into the parent. Use relative paths in parent/child/archive links. Update the parent summary line in the same commit a child's `status` transitions. Nesting ≤ 2 levels below the root.
- **NEVER** apply an `NN-` prefix to skill `references/`/`scripts/`/`design/` (functional modules, not timelines). **NEVER** rename files to reflect priority — use `status` + the parent summary line.
- **Definition of Done for a work chunk = commit → update progress → archive-when-long.** **MUST** archive completed phases when a file exceeds **~200 lines**, preserving archived text **verbatim** (move, not delete; canonical archive filename per `../ctx/references/consistency.md § Archive mechanics`). Archive a section only when all its sub-tasks are complete. **NEVER** summarize/paraphrase/rewrite when archiving; **NEVER** delete completed history.

