Replaces the labels-as-milestones pattern (carried over from the retired SQLite milestone subsystem) with pql's native initiative/epic hierarchy, which already modelled this: initiative T-745 (Development Cascade) holds the six phases as epics. A ticket is in a phase by being parented under that phase epic — self-maintaining, no label to apply or forget. The `phase:4` label had already drifted (66 tickets under the Phase-4 epic T-750 but only 42 labelled). Fixes: - T-750 (Phase 4) -> in_progress to mark the active phase (sequential per D-166). - Re-parented the 2 strays (T-974 Atlas-to-tile epic, T-1008 economy task) under T-750. - Dropped all 42 phase:4 labels (tombstoned in the changelog). - /whats-next, /ticket, ticket-cli.md, CLAUDE.md now gate on `pql ticket list --under <active-phase-epic> --unblocked` instead of `--label phase:4`. - Fixed the label-action verb in the docs: pql uses `add|rm`, not `add|remove`. - pql-requirements #9 flipped: no milestone entity / labels-as-milestones needed — the initiative/epic tree is the answer. Verified: `pql plan rebuild` reconstructs the mutations; `--under T-750 --unblocked` returns the active phase's ready work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
193 lines
10 KiB
Markdown
193 lines
10 KiB
Markdown
---
|
|
title: "pql Requirements — gaps surfaced by the Settled Reach migration"
|
|
description: "Feature gaps and friction points found while migrating a 1013-ticket + 358-decision project onto pql, for the pql team"
|
|
type: requirements
|
|
status: draft
|
|
created: 2026-06-06
|
|
updated: 2026-06-06
|
|
tags: [pql, requirements, feedback]
|
|
---
|
|
|
|
# pql Requirements — gaps for the pql team
|
|
|
|
Compiled while planning a migration of **The Settled Reach** (a Godot+Rust game) off a
|
|
SQLite-backed ticket CLI + markdown-decisions-sync onto pql. Scale: **1013 tickets**, 481
|
|
dependency edges, 48 labels, 16 history rows; **358 decision records** (237 D / 109 Q / 12 R)
|
|
with 1040 cross-refs; a ~4385-file markdown vault (decisions, docs, 22 workshops, ~3500 wiki pages).
|
|
|
|
pql is a strong fit and most of our system maps cleanly — unsurprisingly, since **pql was
|
|
developed off the lessons of this repo's planning structures** (the SQLite ticket+decision
|
|
schema, the `decisions/` domain split, the kanban/`whats-next` flow). The enums, the DQR tree,
|
|
and the kanban model match almost exactly; pql's deliberate departures (TEXT ids over our
|
|
integer ids, a git-tracked text changelog over our binary-DB-plus-backup ritual, and *no*
|
|
milestone entity) are the productized lessons. So the items below are best read as **feedback to
|
|
a descendant**, not gaps in a foreign tool — and a few are deliberate simplifications we should
|
|
adopt rather than ask pql to re-add. Each lists **why**, a **suggested shape**, and a
|
|
**priority**; viable workarounds are noted.
|
|
|
|
---
|
|
|
|
## 1. Seeded / explicit ticket IDs + id-counter advance — **RESOLVED (non-issue)**
|
|
|
|
**Why it looked critical.** Our 1013 tickets have integer ids referenced as `#440` in thousands of
|
|
immutable git commits, in decision "Ticket: #N" lines, and by PR automation. We need the pql id to
|
|
preserve the number — `T-440 ≡ old #440`. The fear was that, since `pql ticket new` has no `--id`
|
|
flag and auto-assigns `T-N` sequentially, a post-seed native create would mint `T-1` and collide.
|
|
|
|
**Why it's actually fine** (verified against pql 1.6.2). Bulk seeding goes through **direct-INSERT
|
|
into `pql.db` + `pql plan export`**, not `ticket new`. And pql's id counter is derived from
|
|
`max(id)` — exactly the behaviour our own integer-id CLI had. After seeding `T-1…T-1021`, the next
|
|
`pql ticket new` minted **T-1022**, not T-1. No collision, no recycling, no held creation. The
|
|
`--id` gap only bites the (rare) case of hand-creating a *specific* id via the CLI — irrelevant to
|
|
a bulk numbered migration.
|
|
|
|
**Optional nicety (low priority).** A `pql ticket new --id T-1022` flag would still be convenient
|
|
for the occasional manual back-fill, but it is **not** required for a clean migration.
|
|
|
|
---
|
|
|
|
## 2. Duplicate-ID detection in `pql decisions sync` / `validate` — **HIGH**
|
|
|
|
**Why.** Our markdown had a genuine integrity bug: `D-035` defined in two domain files, `R-011`
|
|
defined three times. **`pql decisions sync` silently last-writer-wins-collapsed them** (reported
|
|
`broken: 0`), so the duplication was invisible. Our legacy Python `check-dupes` errors on it. A
|
|
duplicate canonical-ID is almost always a data bug, not an intended merge.
|
|
|
|
**Suggested shape.** `pql decisions validate` (and `sync --strict`) should **warn or error on
|
|
duplicate D/Q/R ids across the vault**, reporting `file:line` for each occurrence.
|
|
|
|
**Workaround.** Keep our Python `check-dupes` wired into pre-commit alongside pql — extra tooling
|
|
we'd like to retire.
|
|
|
|
---
|
|
|
|
## 3. `core.hooksPath` awareness in `pql init` — **HIGH**
|
|
|
|
**Why.** Our repo sets `core.hooksPath = .config/hooks` (so hooks are version-controlled). `pql init`
|
|
installs its pre-commit/post-merge/post-checkout hooks into `.git/hooks/`, which git **ignores** when
|
|
`core.hooksPath` is set. pql's changelog-staging + replay hooks would be **silently dead** — the whole
|
|
durable-versioning model depends on them. This is a quiet, dangerous failure (data appears to work, but
|
|
`.pql/changelog/` is never staged).
|
|
|
|
**Suggested shape.** `pql init`/`pql doctor` should detect `core.hooksPath` and either install into the
|
|
configured dir or emit a loud warning with the manual fold-in snippet.
|
|
|
|
**Workaround.** Manually fold pql's hook logic into `.config/hooks/*` (we will), but `pql doctor`
|
|
should flag the mismatch so others don't get bitten.
|
|
|
|
---
|
|
|
|
## 4. Document the changelog seed format + `hash`/`canonical_version` algorithm — **HIGH**
|
|
|
|
**Why.** `pql plan import --legacy` is documented as retired; the real durable artifact is
|
|
`.pql/changelog/<table>/<YYYY-MM>.sql`. To migrate 1013 tickets we must emit those SQL rows directly.
|
|
We reverse-engineered the UPSERT shape, but the per-row `hash` and `canonical_version` columns +
|
|
the LWW guard (`WHERE excluded.updated_at > … OR (… AND excluded.hash > …)`) are opaque. If our seed
|
|
hashes don't match pql's scheme, later native edits still win by `updated_at` (so correctness holds),
|
|
but the LWW tie-break becomes unpredictable.
|
|
|
|
**Suggested shape.** Document (a) the canonical changelog row format per table, (b) the `hash`
|
|
input + algorithm, (c) how `canonical_version` increments — OR provide a supported one-shot
|
|
`pql plan seed <json|csv>` import that computes them.
|
|
|
|
**Workaround.** Emit our own stable per-row hash; rely on `updated_at` for correctness. Works, but
|
|
under-specified.
|
|
|
|
---
|
|
|
|
## 5. Bare-text reference detection / auto-link for `D-NNN` / `T-NNN` — **MEDIUM**
|
|
|
|
**Why.** Across ~4385 vault files (esp. ~3500 *generated* wiki pages), decision/ticket references are
|
|
mostly **bare text** (`D-117`, `#440`) rather than markdown/wikilinks. `pql search` finds them, but
|
|
`pql backlinks`/`related` only follow real links — so "what references D-117?" is incomplete. Rewriting
|
|
3500+ generated files to add links is impractical (they're regenerated from a pipeline).
|
|
|
|
**Suggested shape.** Optional **reference recognizers** (configurable regex → entity, e.g.
|
|
`D-\d+`→decision, `T-\d+`→ticket) so `backlinks`/`related` treat bare-text mentions as edges without
|
|
requiring link syntax in source.
|
|
|
|
**Workaround.** Adopt a link convention in *authored* files only; accept that generated files are
|
|
search-only.
|
|
|
|
**Related gap — frontmatter list-membership in the DSL.** Our Phase-5 provenance puts the decisions
|
|
each workshop touches in a `decision_refs: [D-010, …]` frontmatter list. pql indexes the list and
|
|
`SELECT fm.decision_refs` round-trips it, but we found no DSL operator that filters by membership:
|
|
`fm.decision_refs ~ 'D-010'` and `… contains …` are lex/parse errors, and `'D-010' in fm.decision_refs`
|
|
parses but matches nothing. So "which workshops touch D-010?" needs a `SELECT` + client-side filter or
|
|
a `pql search`. A documented list-membership operator (or making `in` work against frontmatter arrays)
|
|
would close this.
|
|
|
|
---
|
|
|
|
## 6. WIP-limit enforcement (advisory) on `ticket status … in_progress` — **MEDIUM**
|
|
|
|
**Why.** Our kanban flow enforces a WIP limit (max 5 in_progress); our activation step relied on the
|
|
CLI warning when exceeded. pql has no forced state machine / WIP concept.
|
|
|
|
**Suggested shape.** An optional config (`wip_limit: 5`) that makes `pql ticket status <id> in_progress`
|
|
emit an advisory warning (not a hard block) when the in_progress count would exceed it.
|
|
|
|
**Workaround.** Compute `pql ticket list --status in_progress | count` in our skill and warn there.
|
|
|
|
---
|
|
|
|
## 7. Fan-out / downstream-unblock count on `ticket show` — **MEDIUM**
|
|
|
|
**Why.** Our batch-selection ranks ready tickets partly by **fan-out** — how many downstream tickets a
|
|
ticket unblocks. We currently compute it from the dependency graph. pql has `--unblocked` (great — it
|
|
replaced our manual blocker walk) but no "how many does this unblock" signal.
|
|
|
|
**Suggested shape.** A `blocks_count` / `unblocks_count` field on `pql ticket show`/`list`, or a
|
|
`pql ticket list --orders-by fanout`.
|
|
|
|
**Workaround.** Compute client-side from `ticket show --with-context` dep data.
|
|
|
|
---
|
|
|
|
## 8. Multi-team / array team field — **LOW**
|
|
|
|
**Why.** One ticket carries a comma-separated `team` (`client,server`). pql treats `team` as a single
|
|
string. (1 of 1013 — genuinely low.)
|
|
|
|
**Suggested shape.** Accept an array/comma team, or document the single-team constraint.
|
|
|
|
**Workaround.** Pick the primary team for the one affected ticket.
|
|
|
|
---
|
|
|
|
## 9. No milestone entity needed — the ticket hierarchy IS the milestone — **RESOLVED (no ask)**
|
|
|
|
**Why it looked like a gap.** Our old schema had a milestones subsystem (milestones,
|
|
ticket_milestones, milestone_deps, cascade_phase) — but it was **vestigial** (2 milestones,
|
|
`milestone_deps` empty, 42/1013 linked, cascade ordering never exercised). The migration first
|
|
mapped the active milestone to a **label** (`phase:4`).
|
|
|
|
**What we landed on — and it's better.** The labels-as-milestones pattern immediately drifted
|
|
(66 tickets lived under the Phase-4 epic but only 42 carried the label). The fix wasn't a milestone
|
|
entity *or* a label convention: it was to use pql's **native initiative/epic hierarchy**, which
|
|
already modelled this. The repo has an initiative `T-745` (Development Cascade) with the six phases
|
|
as epics under it; a ticket belongs to a phase by being **parented under that phase epic**
|
|
(`pql ticket list --under <phase-epic>`), and the active phase is the `in_progress` phase epic. This
|
|
is self-maintaining — parent a ticket and it's in the phase; no label to apply, forget, or let rot.
|
|
|
|
**Net for the pql team:** dropping the milestone entity was the *right* call — don't add one back, and
|
|
don't bless labels-as-milestones either. The initiative→epic→story→task tree already expresses phase
|
|
gating cleanly. The only nice-to-have is a documented "active node" convention (status `in_progress`
|
|
on a grouping ticket) and maybe a `--under` done/total rollup. **Not blocking; no entity needed.**
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| # | Requirement | Priority | Have workaround? |
|
|
|---|-------------|----------|------------------|
|
|
| 1 | Seeded/explicit ticket id (`--id` flag) | ~~Critical~~ **Resolved** | N/A — direct-INSERT + max-id counter already works |
|
|
| 2 | Duplicate-ID detection in sync/validate | High | Yes (keep our checker) |
|
|
| 3 | `core.hooksPath` awareness | High | Yes (manual fold-in) |
|
|
| 4 | Documented changelog seed format + hash algo | High | Partial |
|
|
| 5 | Bare-text ref detection for backlinks | Medium | Partial (authored files only) |
|
|
| 6 | WIP-limit advisory | Medium | Yes (skill-side) |
|
|
| 7 | Fan-out / unblocks count | Medium | Yes (client-side) |
|
|
| 8 | Multi-team field | Low | Yes |
|
|
| 9 | Milestone entity / labels-as-milestones | ~~Low~~ **Resolved** | N/A — use the initiative/epic hierarchy |
|