Unify the Loaf work model on one recursive Issue entity - #164
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 issueCLI 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.renderemits a paste-ready PR body;frontierlists unblocked pick-up-next work.Derived readiness.
issue checkderives 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) replaceloaf change check.Worktree lifecycle.
issue startcreates the branch and worktree bound to the issue;issue stoptears it down. Branch and PR ownership are derived, not declared.Retroactive releases.
release suggestreads 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 cuttags and records members as fact. Forwardtarget_releasedeclarations, 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 pulladopts a Linear issue,issue pushwrites the local render and status up,issue reconcilesurfaces conflicts. Seven status types are the interlingua; per-project mapping is configuration. A project with no tracker loses nothing.Skills rewrite.
shapeabsorbs pitch narrowing and breakdown decomposition, and prepares the issue in place — fog graduates into decision children instead of evaporating.shipis the sole quality gate.releasebecomes the retroactive suggest-and-cut track.breakdownis deleted;explore,idea, andtriagedispositions land on issues.implement,wrap,orchestration, andhousekeepingrebind from Change paths to issue IDs.Retirements.
loaf changeandloaf specare gone from the CLI;docs/changes/is frozen as history, byte-for-byte unchanged by this branch.Definition of done (root acceptance, verified)
loaf changeandloaf specare gone — both returnunknown commandissue pull/push/reconcileloaf release suggestproposes a cut from landed work alone, no forward declarations — verified against this branchdocs/changes/is byte-for-byte unchanged and never written again — branch diff over the corpus is emptydocs/changesreferences incontent/skills/Verification
go test ./...green across all four packageshttps://claude.ai/code/session_01MvHVSG47FfKyvhspXiv1zA