Files
settled-reach/pql-requirements.md
T
jpmschweitzerandClaude Opus 4.8 83bb18b385 docs(meta): reframe pql-requirements as feedback to a descendant tool
pql was developed off the lessons of this repo's own planning structures, so the
"requirements" are feedback to a descendant, not gaps in a foreign tool — and some
items are deliberate simplifications to adopt rather than ask pql to re-add.

- Item #1 (seeded ids) downgraded Critical -> Resolved: Phase 2 proved direct-INSERT
  seeding + pql's max-id counter (inherited from our own CLI) mints T-1022 after
  T-1021, so there is no recycling/collision. The --id flag is a low-priority nicety.
- Item #9 (milestones): pql's omission of the milestone entity validates our
  vestigial finding; labels-as-milestones is the right call and we adopt it.
- Intro reframed around the lineage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:35:00 +02:00

9.5 KiB

title, description, type, status, created, updated, tags
title description type status created updated tags
pql Requirements — gaps surfaced by the Settled Reach migration Feature gaps and friction points found while migrating a 1013-ticket + 358-decision project onto pql, for the pql team requirements draft 2026-06-06 2026-06-06
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 / validateHIGH

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 initHIGH

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.


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.


6. WIP-limit enforcement (advisory) on ticket status … in_progressMEDIUM

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 showMEDIUM

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. Bless a "labels-as-milestones" pattern (or an optional milestone entity) — LOW / nice-to-have

Why. Our schema has a milestones subsystem (milestones, ticket_milestones, milestone_deps, cascade_phase) — but in practice it's vestigial: 2 milestones (1 active, 1 completed-with-0-tickets), milestone_deps empty, 42/1013 tickets linked, cascade ordering never used. We're mapping the one active milestone to a label (phase:4) and dropping the rest. This works today, so it is not blocking.

Suggested shape. Either bless labels-as-milestones as the documented pattern (with helper queries / rollups), or offer an optional lightweight milestone entity (M:N ticket links, status, ordering, done/total rollups) for projects that actually use phase gating. We'd only need the latter if we later adopt real cross-phase dependency gating.

Lineage note. pql dropping the milestone entity that this very repo carried is almost certainly deliberate — it productized the lesson that our milestone subsystem was vestigial. So this is less a gap than a confirmation: labels-as-milestones is the right call, and our migration adopts it (active milestone → phase:4, the completed one → milestone:process-rewire). Documenting the pattern (point 1 above) is the only ask.


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 Labels-as-milestones blessing / optional entity Low Yes (labels)