Skip to content

feat(project): give issues a parent link and depth so they can form a tree - #10

Merged
EdmondDantes merged 1 commit into
mainfrom
feat/issue-tree
Jul 20, 2026
Merged

feat(project): give issues a parent link and depth so they can form a tree#10
EdmondDantes merged 1 commit into
mainfrom
feat/issue-tree

Conversation

@EdmondDantes

Copy link
Copy Markdown
Contributor

Groundwork for Decompose: a task too big for one run is split into sub-issues, and Issue carried nothing to express that — no parent, no depth.

What changes

  • issues gains parent_id and depth; Issue gains the matching fields.
  • addIssue($title, $description, $parent) opens a sub-issue. Depth is derived from the parent, never passed in — that is what makes a depth cap enforceable later: a caller cannot understate how deep a decomposition already went. The parent is loaded rather than trusted, so hanging a sub-issue off a non-existent id fails instead of silently creating an orphan.
  • childIssues() — the direct children, soft-deleted ones hidden (a deleted sub-issue must not hold its parent open forever).
  • settleAncestors() — the completion rule: a parent is Done only when every child is. Walks up, stops at the first ancestor still holding open work, and leaves an ancestor a human already Closed alone.

Migration

CREATE TABLE IF NOT EXISTS is a no-op on an existing table, so a db registered before these columns would never gain them and every read would fail. SQLite has no ADD COLUMN IF NOT EXISTS, so the live columns are read via PRAGMA table_info and only the missing ones are added.

Covered by a test that rebuilds the pre-tree table with a row in it and reopens the store, and verified by hand against a real registered project (existing issues read as roots, new sub-issues get depth 1).

Tests

5 added, 219 passing. PHPStan level 8 clean, php-cs-fixer clean.

… tree

A task too big for one run has to be decomposed into sub-issues, and Issue
carried nothing to express that: no parent, no depth. Add both, so the ledger
can hold a ticket tree.

Depth is DERIVED from the parent inside addIssue(), never passed in. That is
what makes a depth cap enforceable later: a caller cannot understate how deep a
decomposition already went, and hanging a sub-issue off an id that does not
exist fails on the parent lookup rather than silently creating an orphan.

settleAncestors() carries the completion rule — a parent is Done only when every
child is. It walks up and stops at the first ancestor still holding open work.
An ancestor a human already Closed is left alone: a finishing child must not
reopen someone else's decision.

Existing project dbs are migrated in place. CREATE TABLE IF NOT EXISTS is a
no-op on a table that already exists, so a db registered before these columns
would never gain them and every read of parent_id/depth would fail; SQLite has
no ADD COLUMN IF NOT EXISTS, so the live columns are read first and only the
missing ones are added. Covered by a test that rebuilds the pre-tree table and
reopens the store, and verified against a real registered project.
@EdmondDantes
EdmondDantes merged commit 6bb4280 into main Jul 20, 2026
2 checks passed
@EdmondDantes
EdmondDantes deleted the feat/issue-tree branch July 20, 2026 07:31
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