Skip to content

Unify the Loaf work model on one recursive Issue entity - #164

Merged
levifig merged 18 commits into
mainfrom
issue-work-model
Aug 16, 2026
Merged

Unify the Loaf work model on one recursive Issue entity#164
levifig merged 18 commits into
mainfrom
issue-work-model

Conversation

@levifig

@levifig levifig commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Unifies the Loaf work model on one recursive Issue entity. Four work concepts — Change, spec, task, Intent — collapse into two recursive entities: an Issue carries every unit of work at every altitude, and a Release is cut retroactively from what actually landed. 462 files changed, +26,818 / −32,245.

What changed

Schema and identity (migrations 0014–0017). Issues are project-prefixed rows (LOAF-42) with a mutable body, definition-of-done criteria, a parent edge, and status projected from an append-only events table. Local projects mint IDs from a SQLite counter; tracker-backed projects delegate identity to the tracker.

loaf issue CLI spine. new / show / list / tree / frontier / start / stop / edit / status / dod / promote / check / verify / bucket / link / render / export / pull / push / reconcile. Capturing and shaping a diagnosed fix costs two commands and produces no files. render emits a paste-ready PR body; frontier lists unblocked pick-up-next work.

Derived readiness. issue check derives shaped/ready from the row itself: a problem body, at least one DoD criterion, an explicit out-of-scope. With children, coverage (every parent criterion claimed) and containment (every child criterion serves the parent) replace loaf change check.

Worktree lifecycle. issue start creates the branch and worktree bound to the issue; issue stop tears it down. Branch and PR ownership are derived, not declared.

Retroactive releases. release suggest reads what landed since the last tag, groups it by closed issue, derives the bump from evidence, and reports partially-landed parents and unattributed commits. release cut tags and records members as fact. Forward target_release declarations, the release gate, and the receipt machinery are deleted — a retroactive release structurally cannot contain unimplemented work.

Linear adapter (supersedes ADR-011). Identity delegation rides the issue namespace: issue pull adopts a Linear issue, issue push writes the local render and status up, issue reconcile surfaces conflicts. Seven status types are the interlingua; per-project mapping is configuration. A project with no tracker loses nothing.

Skills rewrite. shape absorbs pitch narrowing and breakdown decomposition, and prepares the issue in place — fog graduates into decision children instead of evaporating. ship is the sole quality gate. release becomes the retroactive suggest-and-cut track. breakdown is deleted; explore, idea, and triage dispositions land on issues. implement, wrap, orchestration, and housekeeping rebind from Change paths to issue IDs.

Retirements. loaf change and loaf spec are gone from the CLI; docs/changes/ is frozen as history, byte-for-byte unchanged by this branch.

Definition of done (root acceptance, verified)

  • Issue and Release are the only work and shipping units; loaf change and loaf spec are gone — both return unknown command
  • Capturing and shaping a diagnosed one-line fix costs two commands and produces no files — LOAF-1..39 live in SQLite with bodies, zero files
  • A local project and a Linear-backed project both work, identity from different authorities, no dual IDs — local counter live; Linear delegation via issue pull/push/reconcile
  • loaf release suggest proposes a cut from landed work alone, no forward declarations — verified against this branch
  • docs/changes/ is byte-for-byte unchanged and never written again — branch diff over the corpus is empty
  • The Flow skills operate on issue IDs, never on paths — zero docs/changes references in content/skills/

Verification

  • go test ./... green across all four packages
  • Reviewed during implementation: 34 findings fixed, 1 refuted (codex gpt-5.6-luna xhigh)
  • Dogfooded live: LOAF-1..3 minted fresh, 36 Intents absorbed as LOAF-4..39 with rationale verbatim

https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA

levifig added 18 commits August 15, 2026 03:34
Issues are the new recursive work entity: project-scoped rows with a
same-project parent edge (write-time cycle refusal), delivery/decision
kind, free-prose fog, and title/body mutable at every status. Status is
a projection of the append-only events table — creation writes the
initial triage event, cancelled/duplicate are reachable only through
RemoveIssue (archive plus a relates_to edge to the survivor), and
CheckIssueStatusParity proves the column equals the latest
status_changed event.

Identity is one authority per project in issue_identity: local mints
prefix-numbered aliases (LOAF-1, ...) from a stored counter that is
never derived from MAX() and never rewound — numbers stay reserved
across removal, hard delete, and authority switches; linear/github
authorities mint nothing locally. Aliases ride the existing aliases
table under the issue namespace. issue_criteria stores per-issue
definition-of-done lines (V/H tier, loaf change verify command grammar).

The three tables are registered in the project delete and merge/rekey
registries; legacy rekey now defers foreign keys so composite issue FKs
survive the project-id rewrite. The SQL prefix CHECK and Go validation
agree exactly (ASCII letter-first alphanumeric, embedded NULs rejected
via char/byte length parity). Existing tasks/specs tables are untouched.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
One verb set for the recursive work entity: new, show, list, tree,
frontier, edit, status, dod, promote, bucket, link, render, export.
Body editing reuses the shared body-input path spec and report edit
already ship. Write statuses route through UpdateIssue; cancelled and
duplicate route only through RemoveIssue, so archive and the relates_to
survivor edge cannot be bypassed from the CLI.

Frontier is derived at read time — non-archived triage/backlog/todo
issues excluding anything blocked by a live issue in either edge
direction; nothing is stored. Tree walks are cycle-safe against
corrupted stored data and fail with a deterministic error. Buckets are
advisory bucket:now/next/later tags; no code path reads them as a
constraint. Render emits a paste-ready PR body with a Definition of
Done checklist.

The issue kind joins the entity registry and export surfaces; issue
export carries identity (authority, prefix, next_number) via a pure
read that never materializes a default row. Legacy task, spec, and
intent commands are unchanged apart from a help-line redirect.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
… verify

Readiness derives from three light facts on the issue row — a nonempty
body, at least one DoD criterion, and an explicit out-of-scope
statement — replacing section-presence parsing as the executability
signal. Decision issues are ready on a sharp question, with no plan or
criteria required.

Migration 0015 adds issue_criterion_claims: a child criterion claims a
parent criterion by ID (positions renumber; claims survive), with
project-scoped composite foreign keys so a cross-project claim cannot
satisfy containment. With children present, loaf issue check runs
coverage (every parent criterion claimed — failure names the criterion)
and containment (an orphan child criterion is reported with a
paste-ready, POSIX-quoted one-command remedy that files it as a sibling
backlog issue). Promote copies the criterion into the child and records
the claim in one transaction, so a fully-promoted parent is covered by
construction. Criteria replacement pairs rows by ID and updates in
place, preserving claims across content edits and gapped positions.

loaf issue verify runs V-tier criteria from the repository root through
the expectation grammar (exit N, contains) lifted into a shared home
that change verify now delegates to; verify writes nothing. Readiness
publishes through a ReadinessPublisher seam (ready-for-agent, or
ready-for-human with a stated reason) that no-ops until a tracker
adapter exists. loaf issue new gains --status for write statuses,
recorded through the normal events path. Claims join both export
surfaces and the project lifecycle registries.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
A release is cut from what landed, so it cannot contain unimplemented
work by construction — no gate, no receipts, no forward declaration.
loaf release suggest reads baseline-tag..HEAD, attributes commits to
issues through a three-rung ladder (alias in subject/body with URLs and
code spans stripped, merge-subject branch refs, journal commit mappings
resolved to exactly one commit), rolls attribution up through parent_id,
reports partially-landed parents and unattributed commits as
information, derives the bump from evidence (breaking marker, then a
post-baseline done multi-child parent, then conventional types), and
drafts notes. Suggest and dry-run are pure reads — identity is looked
up without materializing a default row.

loaf release cut tags (or verifies an existing refs/tags ref under
--no-tag), records the release row and its members as facts in one
transaction immediately after the tag, and only then attempts the
GitHub Release — a gh failure or missing binary degrades to a warning
with a paste-ready POSIX-quoted retry command, never lost state.
Recording is idempotent on identical retries and errors on divergent
content, members and notes included. Disagreeing version files refuse
the cut. --includes records prerelease references without re-including
their members.

Migration 0016 adds releases and release_members (project-scoped
composite FKs, append-only membership) wired through the schema mirror,
lifecycle registries, and both export surfaces. The legacy flag-based
loaf release path is untouched.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
loaf issue start creates the issue's branch (issue/<alias>, attaching
on restart so commits survive) and a sibling worktree under
<repo>-wt/<branch-slug>, recording both on the row and moving status to
active in one transaction through the events path. The base is the
nearest started ancestor's branch — children ride their ancestor, two
started siblings get two worktrees off it — falling back to the
repository default, remote-qualified when the default exists only under
refs/remotes. Start refuses an already-started, archived, or removed
issue, walks ancestors cycle-safely, and disambiguates slug collisions
instead of attaching to another issue's branch; a database failure
after worktree creation compensates and names any leftover git state.

loaf issue stop removes the worktree (dirty refuses without --force),
keeps the branch, and clears the row; a manually deleted worktree is
pruned with --expire now so restart is never blocked by a stale
registration. Stop does not touch status — the workspace is not the
work's state. loaf issue list --started shows every recorded worktree
with its branch and a missing marker, making one-agent-one-worktree
checkable before dispatch.

Migration 0017 adds the nullable started_branch and started_worktree
columns, mirrored and carried through list and export surfaces.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
In a Linear-authority project loaf issue new delegates identity: the
adapter creates the tracker issue and the returned key becomes the
alias; the local counter never advances, and an unreachable API fails
plainly toward the offline capture path instead of minting a fallback.
A tracker create that cannot be bound locally reports the orphaned key
with its pull-based recovery command.

Pull adopts an existing issue — with --tree, its sub-issue hierarchy at
any depth, reattaching parent edges on re-pull — mapping Linear state
categories onto the seven-status interlingua. Push writes the issue
render as the description and propagates status only when the local
event is newer; it never sends title. Reconcile surfaces drift instead
of resolving it: title follows the tracker, status conflicts name both
sides and resolve only via --take-local/--take-tracker, and description
drift compares against the same render push writes. The authority split
is structural: the tracker owns identity, title, status, assignment;
Loaf owns body, criteria, claims, and worktrees.

The GraphQL client is stdlib-only with an injectable endpoint and an
env-only bearer token. Per-project config (team, status-name overrides)
lives in backend_mappings keyed by role, so a config change replaces
its row. Readiness publishes ready-for-agent labels team-scoped, with
create races retried. A release cut pushes membership to a Linear
Release when the workspace supports them, warning on every other skip;
local-authority projects never touch the adapter. ADR-011 is superseded.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
A retroactive release cannot contain unimplemented work, so the
question this machinery answered — is the declared scope proven before
the cut? — no longer exists. The five gate and receipt surfaces are
deleted with their tests and dispatch entries: change origin, change
verify, change receipt status, the change release gate, and the release
evidence gate, along with cohort preflight in the legacy release path,
the journal defer --change option, the receipt-pinned capability
contract tests, and the two stale release-after lineage gap warnings.

What the deleted files housed but live paths still need was rehomed,
not lost: the expectation grammar serves loaf issue verify from its
shared home, manual journal origins moved beside the journal command,
and the prepared-tree and release-snapshot helpers keep serving the
flag-based release path. loaf change check keeps validating structure.
Verification authority is the ship workflow — PR review and CI at
merge — and nothing between merge and tag re-checks anything.

The CLI reference regenerates for the surface change. ADRs 022, 023,
024, and 027 are deprecated with the gate; ADR-026's bump semantics
restate as release suggest evidence; ADR-016 is noted as revisited.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
Shape prepares an issue instead of constructing a folder: the fog
register, grilling, blindspot pass, reaction artifacts, and critique
gate all survive, but their output is the row itself — problem body,
definition-of-done criteria, an explicit out-of-scope statement —
validated by loaf issue check, with decomposition as shape's tail via
loaf issue promote and one sizing rule (fits a fresh context window,
verifiable alone; expand–contract is the named exception). The
breakdown skill retires with its templates, as do shape's folder
templates. Pitch narrows to problem discovery and hands narrative to
shape. Ship is restated as the sole quality gate — its rigor is
load-bearing, and the skill says so; the PR body is loaf issue render
output and landing marks the issue done. Release becomes the
retroactive suggest-and-cut track. Explore, idea, and triage keep
capture unchanged while dispositions land as backlog issues — the
tracked and deferred Intent outputs are gone. Implement picks from the
frontier and works one issue per started worktree; wrap, orchestration,
housekeeping, bootstrap, and the incidental cross-references rebind
from paths and task files to issue IDs.

The project guidance banner, README workflow, and reference routing
speak the same model; the vocabulary-convergence and routing-eval
fixtures pin the new language, and the retired wording-parity and
template-drift tests leave with their subjects.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
Regenerated plugins and per-target dist outputs from the rewritten
skill corpus and the current binary: the issue and release track
command surfaces in the reference and agent help, the retired breakdown
skill and folder templates removed from every target, and the issue-
model skill bodies distributed to all five harnesses.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
The two legacy write surfaces come out of the CLI: dispatch, help,
agent help, and reference entries for loaf change and loaf spec, the
Change scaffolder and its templates, the Change list/tasks/state/
evidence/provenance/conversion commands, and the spec command
namespace. Invoking either now fails as an unknown command.

The read model survives the writers: journal context still derives its
active-changes and active-lineage layers from the frozen corpus, the
flag-based release path keeps its prepared-tree helpers, SQLite spec
records stay reachable through loaf trace and loaf state export spec,
and loaf task and loaf intent remain as legacy read surfaces.
Remediation text now points at those read paths instead of the retired
writers.

docs/changes/ is byte-for-byte unchanged and never written again — the
27 Change folders are history, deliberately unconverted. No hooks
enforced Change structure, so loaf check's hook roster is unchanged.
The CLI reference regenerates and distribution artifacts rebuild for
the reduced surface.

Claude-Session: https://claude.ai/code/session_019REzFB3sckbLJuWaa3xikK
The README workflow diagram was the last old-model artifact in the
file: a linear three-box ASCII flow with no recursion, no decision
issues, and no release track. Replaced with a mermaid graph showing
the shape self-loop (loaf issue promote), fog graduating into
decision issues, ship as the sole quality gate, and the release
track reading main backward. A caption states the asymmetry:
forward is planning and re-plannable, backward is factual.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
govulncheck fails CI on four standard-library vulnerabilities
(GO-2026-6218, GO-2026-6090, GO-2026-5972, GO-2026-5026) present in
go1.26.5 and fixed in go1.26.6.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
Freeze task and intent write verbs behind a deprecation redirect to
loaf issue (reads keep working; retirement is gated on the LOAF-42
migration). Retire the legacy flag-based release path — suggest and
cut are the only shipping verbs. Route issue promote through the same
identity authority as issue new so Linear-backed projects mint real
children with backend mappings. Make release cut fail-safe and
idempotent across version writes, tag creation, and release recording.
Derive release suggest baselines from the last tag and refuse dirty or
disagreeing version files. Clear issue stop's started fields before
worktree removal and restore them if removal fails.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
Rewrite the PR template onto loaf issue render and check; move the
post-merge hook checklist from task/spec verbs to issue status, stop,
and journal logging; rewire orchestration, research, implement, and
git-workflow guidance off task/spec/breakdown vocabulary; update the
generated AGENTS.md fence literals to the issue model; drop the
deleted breakdown skill from the content-hygiene owning-links fixture.
Rebuild distribution artifacts for all five targets.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
git tag -s overrides tag.gpgsign config and requires a secret key,
so release cut failed on any machine without one (CI included).
Annotated -a tags let the user's git config decide signing.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
Restore the no-tag baseline fallback so first releases derive their
version from the committed version file at HEAD. Repair cut's
commit-failure recovery to reset both index and worktree instead of
restoring from the already-updated index. Delete the dead ProbeWritable
transaction (RecordRelease already inserts release and members in one
transaction). Purge legacy release flags from the CLI reference and
agent help. Move remaining guidance and templates onto issue
vocabulary and remove the orphaned breakdown sidecars.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
…back

The release skill now describes annotated tags with config-decided
signing, matching the runtime. Cut's failure recovery removes files it
created that are absent at HEAD, so a failed commit in a repo without a
tracked changelog leaves a clean tree. Remaining guidance surfaces move
to issue vocabulary.

Claude-Session: https://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA
@levifig
levifig merged commit 0032852 into main Aug 16, 2026
1 check passed
@levifig
levifig deleted the issue-work-model branch August 16, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant