Files
clide/docs/pql-improvements.md
T
Jeroen SchweitzerandClaude Opus 4.8 02bc4803fc docs: note the pql README-sync bug in pql-improvements
Adds issue #7 — pql decisions sync regenerates governance/README.md and
relists resolved questions as open (dropping the Resolved section), which
also yields a persistent false-dirty diff.

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

8.5 KiB

pql — improvement notes from a live session

Feedback for postmeridiem/pql, gathered while using pql as the planning backend for the clide repo. Everything below was observed first-hand, not inferred from docs.

Environment

  • pql 1.5.0, commit ab191fb, schema_version 1 (binary), DB schema_version 2.
  • Consumer: a single-dev repo using pql for decisions + tickets, with pql's installed git hooks (.pql/hooks/post-checkout, post-merge, post-rewrite, pre-commit) and a git-tracked .pql/changelog/ + .pql/pql-plan.json.

The core is genuinely good — structural queries, the decision/ticket model, exit codes, and the changelog-as-source-of-truth idea all worked. The notes below are where it bit us or surprised us, roughly in priority order.


1. CRITICAL — ticket mutations aren't persisted, and rebuild-on-checkout silently destroys them

Observed. pql ticket new (and ticket status / ticket block) write only to .pql/pql.db, which is gitignored. Nothing writes through to the git-tracked .pql/changelog/ automatically. Separately, pql's installed post-checkout and post-merge hooks run pql plan rebuild, which replays the committed changelog and overwrites pql.db.

The two facts combine into silent data loss:

  1. Created an 18-ticket tree (an initiative + 4 epics + tasks, with blockers and decision refs) via a series of pql ticket new calls. Verified present in pql ticket list.
  2. A routine git checkout fired post-checkout → pql plan rebuild → pql.db was rebuilt from the changelog, which still ended at the previous max ticket.
  3. All 18 tickets were gone. No warning, no prompt.

Impact. Total, silent loss of un-exported planning work on an ordinary git operation. The user did nothing wrong — they switched branches.

Repro.

pql ticket new task "scratch ticket"   # lands in pql.db only
git checkout -b somebranch             # post-checkout runs `pql plan rebuild`
pql ticket list | grep "scratch"       # gone

Suggested fixes (any one closes the hole; (a) is the principled one):

  • (a) Write-through. Have ticket/decision mutations append to the changelog at mutation time, making the changelog the log of record and pql.db a derived cache. Then rebuild is always safe.
  • (b) Divergence guard. pql plan rebuild (and the hooks that call it) should detect when pql.db holds rows not represented in the changelog and refuse or loudly warn instead of overwriting — e.g. "pql.db has 18 un-exported rows; run pql plan export first, or pass --force."
  • (c) Non-destructive rebuild. Merge the changelog into pql.db rather than replacing it.

At an absolute minimum, the post-checkout/post-merge hooks should not discard local state without a word.


2. pql plan export writes the changelog, but docs/help say it writes a JSON snapshot

Observed. pql plan export wrote:

{"files_written":[".pql/changelog/ticket_deps/2026-06.sql",
                  ".pql/changelog/tickets/2026-06.sql"],"rows_written":23}

It did not touch .pql/pql-plan.json, which stayed frozen at an older date across every mutation and export this session. But the --help text and the bundled skill both describe export as "Snapshot planning state to JSON (default: pql-plan.json)" and recommend committing pql-plan.json.

Impact. Users (and the bundled skill) commit pql-plan.json believing it is the durable artifact. It is stale. The actually-durable artifact is the changelog. This directly compounds #1 — people think they've versioned their planning state when they haven't.

Suggested fix. Make --help, docs, and the embedded skill match real behavior. Clarify the role of pql-plan.json vs .pql/changelog/: is the JSON snapshot deprecated in favor of the changelog? If both exist, document which one pql plan import / pql plan rebuild prefer.


3. Schema upgrade is a hard break requiring multi-step manual recovery

Observed. After a pql binary upgrade, every planning command failed:

$ pql decisions sync
planning: decision_refs.created_at missing — pql.db is from an earlier schema.
$ pql plan status
planning: decisions.created_at missing — pql.db is from an earlier schema.

(exit 69). Recovery was manual and multi-step: delete pql.db, pql plan rebuild, then pql decisions sync.

Impact. All planning surfaces are dead until the user performs a manual recovery they have to read out of the error text. Easy to get wrong (the error also offers a --legacy pql-plan.json path — see #4).

Suggested fix. Auto-migrate the schema on open (additive column adds are cheap), or ship a single pql plan repair / pql migrate command that performs the safe delete-rebuild-sync sequence (with a backup of the old DB).


4. Schema-error recovery hint can point at the stale snapshot

Observed. The schema error (see #3) lists recovery options including pql plan import --legacy .pql/pql-plan.json. Given #2 (that file is stale), a user following that branch restores an out-of-date snapshot. The correct path was the changelog rebuild.

Suggested fix. In the recovery list, prefer/recommend pql plan rebuild when .pql/changelog/ is present, and annotate the pql-plan.json option as potentially stale.


5. Scripting ergonomics — no id-only output from pql ticket new

Observed. Building a ticket tree means capturing each created ticket's id to use as the next call's --parent/blocker. pql ticket new emits the full ticket JSON, so a creation script must parse .id out of every call.

Suggested fix. A --quiet / --id-only (or -o id) flag that prints just the new T-NNN, so tree-creation scripts stay simple. (Determinism is already a strength: re-running the same new calls in order against a clean baseline reproduces the same ids — which is what let us recover from #1.)


6. Minor — bundled skill assumes binary features the installed version lacks

Observed. The bundled pql skill documents pql ticket show --tree, --leaf, --unblocked, --under, and pql ticket append (annotated "pql ≥ 1.6.0"). The installed binary reports 1.5.0 as current (pql doctor / pql skill status), and rejects them:

$ pql ticket show T-65 --tree
unknown flag: --tree           # exit 64

Impact. A consumer following the skill hits unknown-flag errors and has to discover the 1.5.0 fallbacks (--with-children, --with-blockers) by trial.

Suggested fix. Keep the embedded skill's documented surface in lockstep with the binary it ships alongside, or have pql skill install stamp/gate version-specific sections so the skill never advertises flags the paired binary doesn't have.


7. Question-index regeneration drops the "Resolved questions" section

Observed. The repo keeps a human-readable index of decisions/questions in governance/README.md, regenerated by pql decisions sync (run by the installed hooks). After a sync, the regenerated index moves every resolved question back into the main (open) list, in numeric order, and deletes the ## Resolved questions heading entirely. Concretely, Q-6, Q-19, Q-21, Q-22 are all status: resolved per pql decisions show, yet the regenerated README lists them as if open.

Impact. Two problems: (a) the index mislabels resolved questions as open, so the doc lies about project state; and (b) because a hook regenerates it on every sync/commit, it produces a persistent false-dirty diff — git restore it and the next pql operation brings it back, so the working tree never stays clean.

Repro.

pql decisions show Q-6   # -> status: resolved
pql decisions sync       # regenerates governance/README.md
git diff governance/README.md
# Q-6/19/21/22 moved into the open list; "## Resolved questions" section gone

Suggested fix. The index generator should key each question's section off its status (open vs resolved vs withdrawn) and keep resolved/withdrawn entries under their own heading — i.e. round-trip the same structure it parses, so a sync is idempotent and doesn't reclassify resolved questions.


Summary

The one that matters most is #1 — a routine git checkout can silently erase planning work because mutations live only in a gitignored DB and the pql-installed hooks rebuild that DB destructively. #2 makes it worse by pointing users at the wrong file to commit. Fixing write-through (or a divergence guard) plus aligning the export docs would remove a whole class of "where did my tickets go" incidents.