Releases: opum-ai/quest-cli
Release list
Quest CLI 0.11.0
The first release under constitution Article 3 (opum-ai/opum-agent
docs/reference/opum-project-constitution.md, ratified 2026-09-27): quest and
lore ship as a pair at one version number. lore 0.11.0 skips 0.10.0 to meet
it. Both are staged under the release-candidate dist-tag and qualified
together by opum-cli-e2e from registry installs. Only then does latest
move, quest first. It is minor rather than patch for two reasons. quest init
and quest agents gain opum-quest plugin detection and update. And a bare
quest agents --check in a Claude-only or Gemini-only project now exits 6
where it used to exit 0 (QCLI-373, below). Nothing else changed in any
command, flag or envelope.
Added
quest initandquest agentsdetect the opum-quest marketplace plugin
(QCLI-371, with QCLI-378 to QCLI-384 closing parity with lore-cli). When the
Claude or Codex target is selected, they readclaude plugin list --jsonor
codex plugin list --jsonand report the plugin as installed, disabled, not
installed or not detectable, with a remedy. Neither ever installs or enables
it.quest agents --update-instructionswith an explicit--targetupdates
an installed plugin and converges on the marketplace, and the skill is drawn
from the marketplace source so the two copies cannot drift apart. The rules
match lore-cli's:- Claude scopes rank managed > local > project > user > synced. Between two
rows of one scope, the deeperprojectPathdecides (QCLI-379). A managed
row decides and is never updated (QCLI-381). - A scope that cannot be named as a plain token gets no command at all:
the update is reported not run, and the remedy is prose (QCLI-383). A
token must start with a letter or digit (QCLI-382). - Every not-run report carries an
updateDetail, and the Codex wording
follows how far the update got (QCLI-384). - Runtime text is stripped of ANSI, control and bidi/invisible format
characters before any output mode.--scopereaches a remedy or an
argv only as a plain token. Plugin output is read up to 1 MiB (QCLI-380,
QCLI-382). - The list deadline (15s) ends the runtime's whole process group, so a
grandchild holding the pipes cannot keepquestalive (QCLI-378).
QUEST_AGENT_PLUGINS_TIMEOUT_MSandQUEST_AGENT_PLUGINS_UPDATE_TIMEOUT_MS
override the list and per-step update deadlines.
- Claude scopes rank managed > local > project > user > synced. Between two
Changed
-
A bare
quest agents --checkno longer reports on the wrong file
(QCLI-373). With no--targetit still checks the Codex block, because the
Codex managed block tells CI to run it that way. But in a project whose
Quest block lives only inCLAUDE.mdorGEMINI.md, it now exits 6 and
names the--targetto use, instead of exiting 0 about anAGENTS.mdthat
was never the point. -
Publishing refuses without an opum-cli-e2e qualification receipt, and
publishes the qualified bytes (QCLI-366, QCLI-368). The publisher reads
receipts/quest/<version>.jsonfrom opum-cli-e2e'smainand refuses,
dry run included, unless it binds this version, commit, qualification run
and all seven candidate-bundle tarballs. The seven bundle tarballs are then
published byte for byte instead of a repack of the working tree. After
publishing, npm'sdist.integritymust match every bundle file.
.gitattributespins the platform packages' LICENSE and package.json to LF,
which the Windows runners' checkout had turned into CRLF. Release tooling
only. -
Publishing refuses unless lore is at the same version (QCLI-386,
constitution Article 3 clause 6). Both publishers read@opum-ai/lore's
package.jsononopum-ai/lore-cli'smain, dry run included, and refuse
on a mismatch or on any failure to read it.
scripts/qualification/version-parity.mjs --requireruns the same check on
its own. Release tooling only. -
A release is staged under the
release-candidatedist-tag, andlatest
moves in a separate step (QCLI-385, constitution Article 3 clause 5). Both
publishers now pass--tag release-candidateon everynpm publish, so
publishing leaveslatestwhere it was.scripts/promote-release.mjsmoves
latestafter opum-cli-e2e has qualified the staged lore/quest pair from
registry installs. It refuses unless all seven packages are staged at the
version, and it records every priorlatestbefore moving any tag. It moves
the platform packages first and the wrapper last. A failure part way through
restores the tags that run moved, and--rollback <record>restores all of
them. Release tooling only; no command, flag, envelope or exit code of
questchanged. -
Moving
latestrequires opum-cli-e2e's verdict on the staged pair
(QCLI-388).scripts/promote-release.mjsreads
receipts/pair/<version>.jsonfrom opum-cli-e2e'smainand refuses unless
it isQUALIFIED, names quest and lore at the same version, and was taken
from registry installs. Every recordeddistIntegritymust also match what
npm serves when the promotion runs. A missing or unreadable receipt refuses.
Rolling back is never gated. Release tooling only.
Fixed
task edit --if-revisionreports a stale edit as a conflict (QCLI-374).
A stale revision is now refused with exit 5 before the patch is folded. A
concurrent removal of the same value used to surface as an exit-6
validation miss rather than the conflict it is. Callers that retry on exit
5 now retry.scripts/publish-release.mjsno longer reports a locked Keychain entry as
a missing token (QCLI-349). It used to treat everysecurityfailure the
same way and print "no stored token found", which points at the wrong fix. A
non-interactive read that fails with exit 36 (errSecInteractionNotAllowed)
now says the entry exists and namessecurity unlock-keychain. Exit 44 still
says to create and store a token. Any other failure is reported as "could not
be checked". A real publish with the entry locked and no--otprefuses with
the unlock remedy. This is release tooling only; no command, flag, envelope
or exit code ofquestchanged.- The publisher's success line names what it actually verified (QCLI-350).
"@opum-ai/quest published and verified (N checks)" described
checks of the six platform packages against the receipt, not of the
wrapper. On 0.9.0 it printed while an anonymous read of the wrapper still
returned 404. The script now checks that the wrapper resolves for a consumer,
using the same read that gates the platform packages, before it prints
anything. The line then lists each object with the check that verified it.
A package the registry does not show yet is described as one of three
states (slow, staged, never landed) instead of two. The wrapper is still
withheld whenever any platform package does not resolve. Release tooling
only. - The post-publish verification is now testable end to end (QCLI-304). It
moved out ofmain()intoverifyPublishedRelease, with every read
injectable except the wrapper's anonymous consumer read. A test now makes the
0.7.1 failure happen through that real read: the wrapper's write succeeded
but the public packument does not list the version yet. It shows that no
success line prints until a plain read lists the version. The runbook
publish section now says what "verified" means. Release tooling only. - A version bump now fails fast if
bun.lockstill pins the old platform
versions (QCLI-292). CI'sbun install --frozen-lockfiledoes not notice
at bump time, because the new version isn't on the registry yet. It failed
only after publish, on an unrelated pull request (the 0.6.2 breakage). The
newlockfile_pinssource gate (bun run check:lockfile) compares the six
pins topackage.jsonand namesbun installas the fix. Release tooling
only.
Quest CLI 0.10.0
This release is minor rather than patch for one reason: the managed-block
change below makes quest agents --check report drift (exit 6) against every
existing block until quest agents --update-instructions rewrites it. No command,
flag, envelope or exit code changed. Apart from the two version constants, the
only source file changed since 0.9.0 is src/application/agents/agent-instructions.ts
(QCLI-365).
Changed
- The managed agent-instructions block now routes a reader to the guides
instead of back to itself (QCLI-362, from opum-agent's OPAG-378 audit).
It used to callquest instructions --json"the current versioned
protocol", but that command returns this same block. The block now names
quest instructions --listand when to read each guide (overview,
task-creation,task-execution,task-finalization,workspace), along
withquest searchandquest help <command>. It spells out the actor
flags for a person and for a delegated agent (--actor,--actor-kind,
--accountable-human), where it used to say only that a declaration is
required. The Backlog.md cutover recipe is gone from the block; the
workspaceguide still carries it.quest agents --checkreports every
existing block asdrift(exit 6) until it is regenerated with
quest agents --update-instructions. That is a real content change, not a
version-only difference, so it is not exempt.
Quest CLI 0.9.0
Ships the 0.8.0 section below along with everything here: 0.8.0 was frozen and
tagged but never published, so its content reaches consumers for the first
time under this number.
Why the published number skips 0.8.0. 0.8.0 was frozen, tagged at
9d91fc35 and never published; 15 further commits then landed on dev,
including the Added entry below and four fixes. Re-pointing the tag was
refused rather than weighed: quest-web runs a live link gate against this
repository's tags, and opum-marketplace re-resolves tag to commit to tree
against a recorded baseline, so a moved tag falsifies every record citing it.
The precedent for a tagged-but-unpublished version is 0.6.1 to 0.6.2 -- ship
the content under the next unclaimed number. This is breaking-plus-feature
relative to published 0.7.1, so the next unclaimed number is a minor one.
This section exists because QCLI-286 came true. That task -- open, and
filed as a warning -- predicted that a rolled-forward version section can
silently swallow later fixes. It did: five consumer-observable changes sat on
dev with an empty Unreleased heading above a frozen ## 0.8.0, and
nothing caught it. It was found by walking git log v0.8.0..dev during
release preparation, which is exactly the manual step QCLI-286 asks to be
replaced by a check.
Added
unresolvedAtCompletionis persisted on the task record and queryable
across completed tasks (QCLI-336). QCLI-252 made it a response-only
signal, so the fact that a task closed with unchecked acceptance criteria
survived only in the output of the command that closed it.task complete
now computes it in the domain and writes it into the record, the zod schema
validates it, andtask view/task list --jsonsurface it.TaskInput
excludes it from creation: it is a fact a completion establishes, never one
a caller asserts.
Fixed
-
overviewcounted only.quest/tasks/, so completing a task removed it
from the totals instead of moving it (QCLI-339). It now counts every
retention location, and carries an additivebyLocationnaming how the
total divides, so a total that looks wrong can be read rather than guessed
at. -
overviewmilestone counts silently excluded archived milestones
(QCLI-340), the same unlisted-population shape as the entry above. Archived
is now reported alongside open and closed. QCLI-140 is preserved rather than
reopened: a retired milestone still counts as neither open nor closed work,
but the population it belongs to is now visible in the output instead of
only in a code comment. -
The
overviewguide documented an envelope the CLI does not emit
(QCLI-342). It described{schemaVersion, kind, data, principal}, omitting
contractVersion-- the key at index 1 -- and did not say that the record
lives underdata. An agent following it and projecting top-level fields
got all-null with exit 0 and empty stderr. The guide now names the envelope
exactly as emitted, separates success from error, states the additive-key
rule, and is pinned by a test that compares it against real CLI output
rather than against a copy of the prose. -
task create --id <taken>reported a write conflict and told the caller
to retry, against a condition no retry can resolve (QCLI-346). The id was
held by an existing record -- in the reported case an ARCHIVED one, which
task listdoes not show and which leftdoctorreporting healthy, so
every signal a caller could reach said the workspace was fine. It is now
validationon exit 6, names the id, and names the path holding it.task_already_existscovers two cases with opposite remedies and both were
mapped to the retryable one. An auto-allocated id losing a race to a
concurrent writer is a genuine conflict and retrying recomputes a fresh id;
an id supplied with--idis fixed, so the same retry loops forever. Only
the explicit case changed -- the auto-allocated path keepsconflictand
its retry hint. Reported by opum-cli-e2e, who followed the old hint three
times before finding the cause by listing.quest/archive/on a hunch. -
task editrejected--implementation-notes, the nametask create
uses for the same field (QCLI-344). It is now accepted as an alias of
--notes. Passing both is a usage error rather than a silent preference for
one, and a rejected flag now states that the rest of the command was not
applied -- previously a caller could not tell from the message whether a
multi-flag edit had partially landed.
Quest CLI 0.8.0 (tagged, never published)
Version frozen 2026-09-17, tagged and never published. Its content ships
under 0.9.0 instead, together with the work that landed after the freeze; see
that entry for why the number moved. The v0.8.0 tag is left pointing at
9d91fc35 and is not re-pointed.
Breaks lockstep with @opum-ai/lore, forced by QCLI-328's rule that a
breaking Changed entry needs at least a minor bump, and the entry directly
below is exactly that.
Corrected 2026-09-18 (QCLI-345). This paragraph originally read
"lore-cli's dev remains at 0.7.0 and nothing in lore changes". That was
false when it was written, and it is corrected here rather than quietly
dropped because it was the stated justification for shipping quest-only.
Asked to measure their own range, lore-cli reported dev at 64 commits past
tag v0.7.0, carrying a new lore types command, a new profile.strict_types
config key, and committed-schema drift detection inside lore check.
How it got in is worth more than the correction: the claim came from reading
lore's dev package.json (0.7.0) as though it were a running version. It is
not, and the conventions are opposite in the two repositories -- lore bumps
at release time, so that field names the LAST shipped version; quest bumps at
freeze time, so ours names the NEXT, unpublished one. Reading a sibling's
field with this repository's convention in mind produces a confident wrong
answer. A version field's meaning is a per-repo convention; the commit range
is the only probe that means the same thing in both.
The date above is when this version was frozen and the bump landed, not
when it reached npm; publication is a separate, manually authorized step,
and nothing in this file should be read as evidence that it happened.
Changed (breaking)
-
A removal that matches nothing now fails loud (exit 6) instead of
returningtask.updated/milestone.updatedwith an unchanged list. Nine
flags across two commands:task edit's--remove-label,--remove-plan,
--remove-note,--remove-comment,--remove-assignee,
--remove-reference,--remove-modified-fileand--remove-dependency,
plusmilestone edit --remove-task. The error names the record id and
echoes every unmatched value JSON-delimited, and NOTHING is removed -- not
even the values in the same flag that did match.Quest shipped two removal vocabularies with opposite miss behaviour in the
same command.--remove-ac 9on a two-item list exits 6;--remove-note 1
exited 0 and removed nothing, because removal matches on exact TEXT. An
agent that had learned the loud one reasonably tried the same shape on the
quiet one and was told its edit succeeded. Reported by opum-cli-e2e via
opum-agent as one flag; reproducing it found eight, then nine across two
commands, becausemilestone edit --remove-taskhas its own merge and never
touches the shared helper.The payload could not have carried this instead, and that is the argument
that decided the shape. The natural defensive check on the returned record
-- "is the value I asked to remove absent from it?" -- returns TRUE on the
whitespace case, because a value differing by a trailing space was never in
the list under any outcome. It confirms the failure AS a success. A defect
that defeats the diligent caller and the careless one identically is worse
than one that only catches the careless, so an additiveunmatchedcount
would have left the hard case exactly as silent for everyone not reading a
field they have no reason to expect. Hence the JSON delimiters: a trailing
space sits inside the quotes and a tab renders as\t.Both downstream consumers chose loud independently, and lore-cli did so
against its own convenience: a silent no-op on the only path they reach (a
read-then-remove race) is worse for them, because lore would then report
"removed" for an edit that removed nothing, propagating the defect into
their report rather than stopping at Quest's boundary. They asked for the
record id in the message for the same reason -- their per-task error field
carries one line into a multi-task report.The ordinal-addressed family (
--remove-ac/--check-ac/--uncheck-acand
the--*-dodequivalents) is unchanged and keeps its own message. Accepting
an ordinal on the by-value flags was considered and DROPPED, not deferred: a
value that is both a valid ordinal and valid item text would have two
meanings, so a note whose text is literally"1"would get a removal that
silently addressed a different item -- a wrong object returning success,
strictly worse than the no-op it replaced.Who this can break, stated with its bound rather than as a clean bill of
health. A caller that passes removal values speculatively -- a
remove-if-present idiom, or an idempotent retry that re-sends an edit whose
removal already applied -- now gets exit 6 where it got exit 0. A consumer
sweep across the fleet checkouts on this machine found exactly one
cross-repo caller, lore-cli'ssrc/adapters/quest.tsemitting
--remove-label, and both of its call sites compute the removal from a
fresh read and short-circuit when the label is already absent, so neither is
speculative; lore-cli confirmed from their own side that no third producer
and no retry path exists. That finding is bounded to the fleet checkouts
on this machine and is NOT a claim about consumers generally. It cannot
see removals assembled into atask edit-batchops file outside those
repositories, and it cannot see any consumer outside them -- the mbpm2
project included, who are a real integration consumer of the published CLIs
and have not been asked (QCLI-297).What the short-circuit does NOT cover, volunteered by lore-cli against
their own interest. Both of those guarded call sites are a
read-modify-write with no--if-revision, which is their open LCLI-522. If
the label disappears between lore's read and lore's write, quest previously
returnedtask.updatedwith an unchanged list and the race stayed silent;
under this release it becomes exit 6. So this change converts a silent
lore race into a loud failure -- an improvement, and neither side asked to
hold the release for it, but it means lore 0.7.0 paired with this version
has a failure mode that neither lore 0.7.0 + quest 0.7.1 nor a matched newer
pair will necessarily show, because it needs concurrency to surface. A
serial qualification run cannot see it; opum-cli-e2e has been asked to
exercise that path concurrently. The pair being qualified matters here, not
just the two version numbers.
Added
-
Every
task.listenvelope carries an additive top-levelscope
({branch, otherRefsRead, unseenTaskIds?}), so a listing names the object
it answered about instead of leaving the reader to assume it covered the
repository.quest task listreads the checked-out ref's.quest/and
nothing else, while the rule that sends a session to it -- confirm nothing
is still open before reporting clear -- asks about the repository. The two
diverge for exactly the tasks most likely to be forgotten, the ones still on
an unmerged branch, and the failure direction is the bad one: an empty list
reads as the check PASSING rather than as the check NOT RUNNING. Reported by
opum-doc after an empty In Progress listing ondevmissed a task that was
In Progress with an open pull request; they caught it only because an
independent source disagreed with the empty list, and neither the exit code
nor the output would ever have shown it.branchis named on every listing. The cross-ref set difference that fills
unseenTaskIdsruns only when the listing came back empty -- the only case
where naming what was not read changes the reader's conclusion -- and
otherRefsReadsays whether it ran. Three limits, all deliberate: it
reports EXISTENCE only and never status, so a named id may already be Done
on the ref that carries it (DEC-6); it readsrefs/headsand
refs/remotesand never fetches, so rungit fetch --prunefirst if local
refs may be stale; and it is a local-git detector, which means it shares a
blind spot with any other local-ref sweep and does not replace asking
GitHub (QCLI-316).Detect it by key presence, not by version, and keep doing so after the
next release. The success envelope is an open key set, soscopeis
exactly the additive change a consumer is required to tolerate, and
"scope" in envelopeis the semantically correct probe rather than a
fallback. A version gate cannot work here in any case:devdeclares 0.7.1
and so does the published build that lacks the key (QCLI-296). -
agents.skill_sourceaccepts a third value,"none", meaning this
workspace generates no quest skill file and none ships from anywhere else.
Set it withquest init --skill-source none, or on an existing workspace
withquest init --reconfigure --skill-source none. It behaves identically
to"plugin"on every file operation -- absence is the healthy state, a
leftover.claude/skills/quest/SKILL.mdreports as drift, and--force
removes it only when byte-exact -- and differs only in what it asserts.That difference is the entire point. Reported by the mbpm2 project, a
Codex-only consumer, as an integration blocker:quest agents --update-instructions --target codexwrites AGENTS.md and the
Claude-provider skill file, because the skill file is target-independent by
design and governed byagents.skill_source. The only way to stop it was
--skill-source plugin, which asserts the opum-quest Claude Code plugin
ships the skill -- a plugin they do not have and are not using. The opt-out
required declaring something false."none"says only that no skill file is
generated, and its drift message names no plugin.The
--target codexpath is unchan...
Quest CLI 0.7.1
Version frozen 2026-09-15. Breaks lockstep with @opum-ai/lore, once and
deliberately, as 0.6.2 did: lore stays at 0.7.0 and nothing in lore
changes. This is a hotfix for a defect every fleet workspace that paused a
task before 0.7.0 is exposed to, ruled ahead of everything else the same day
it was reported, and holding it for the next paired release would have left
those records unreachable for no reason a consumer benefits from. The one
entry below is additive on the read side (doctor gains an issue code) and
restores a transition on the write side (task start from the retired
literal); nothing else moves. The date above is when this version was
frozen and the bump landed, not when it reached npm; publication is a
separate, manually authorized step, and nothing in this file should be read
as evidence that it happened.
Fixed
-
A record parked at the pre-0.7.0 default paused status
"Blocked"could
not leave it by any command after upgrading:task startandtask pause
refused the transition,task edit --statusandtask completereported
an unconfigured status,task demotehad nowhere to go, andquest doctor
reported the workspace healthy. QCLI-287 renamed the default to"Paused"
and every lifecycle check compares the record's status to the configured
value by exact string, so an already-parked record became an off-flow
status on upgrade; 0.7.0's changelog warned about the literal and the
best-placed consumer still missed it, so a warning was not the fix.quest task start <id>is now the sanctioned exit from the retired
literal, exactly as it was in 0.6.x, when and only when the workspace does
not configure"Blocked"itself (on its ladder or as its paused status);
task pausethen parks the record at"Paused". Nothing is migrated
silently.quest doctorgains atask_status_off_flowissue naming every
active task whose status is on neither the ladder nor the paused slot, with
the repair command in itshint, so a stranded record is a red doctor
rather than a healthy one. Reported by lore-web (LWEB-80), also hit by
lore-cli (LCLI-333) (QCLI-302).For a stranded workspace, the repair is:
quest doctor --json # names the task id and status quest task start <id> --actor <name> --actor-kind human --json
-
The release publish no longer puts
@opum-ai/queston the registry until a
read confirms all six platform packages actually resolve for a consumer.
It previously ordered its writes and treated that as the guarantee; write
order does not produce visibility order, and during the 0.7.0 publish the
two disagreed in four of six positions while one package sat non-public for
twelve minutes past the wrapper. Because platform packages are
optionalDependencies, an install inside that window SUCCEEDS and leaves no
binary -- so the failure was silent from the consumer's side and reported as
success from the publisher's (QCLI-299).Nothing about an installed release changes; this is release tooling. The
gate reads over plain HTTPS with no credential, which is a different client
from the publishing one, and holds a 30s settle margin over the 9-20s
publisher-early lag measured by opum-cli-e2e. -
A timed-out publish verification no longer asserts "this is registry
read-after-write lag, not a failed release". That sentence was true of six
packages and wrong about the one that decided whether 0.7.0 shipped, and it
told the operator to stop investigating at the moment investigating was the
whole job. It now reports each package's state read from the registry --
public, staged, or undetermined -- and names the operator action for a
staged one, including that the release token cannot clear it. The
npm unpublishwarning stays: that half was protecting against a genuinely
destructive action (QCLI-299).
Added
.github/workflows/lore-check.yml: CI now runslore checkon every pull
request intodev(and on push todevandmain, so the context can later
become a required check without deadlocking the fast-forward promotion). The
operating block has made a greenlore checkthe definition of done for a
docs change all along; nothing here enforced it. Unlike the fleet reference
job, this one installs no published@opum-ai/quest: lore's quest adapter
shells out to whateverquestis on PATH, and this repository authors quest,
so the job runs lore against a shim overbun run src/cli/main.tsfrom the
same checkout and asserts that is what PATH resolved to (QCLI-301).
Quest CLI 0.7.0
Version frozen 2026-09-15, in lockstep with @opum-ai/lore 0.7.0 -- the
pairing convention every release has held since 0.5.0, resumed after 0.6.2
broke it once deliberately. The date above is when this version was frozen
and the bump landed, not when it reached npm; publication is a separate,
manually authorized step, and nothing in this file should be read as evidence
that it happened.
Minor, not patch, and one entry below deserves reading before you upgrade
rather than after. quest task pause now parks a task at "Paused" instead
of "Blocked", so anything matching on the literal string "Blocked" to
find paused work will stop matching. No transition, flag or envelope shape
changed to make that true -- the configured value did. Everything else here
is additive: a new envelope field and two new optional flags, each inert when
not asked for.
Observed after release, 2026-09-15, reported by opum-cli-e2e from their
0.7.0 qualification matrix (their PR #154): that warning was published and
the best-placed consumer still missed it. Their 60-project-lifecycle suite
pinned the literal "Blocked" and six rows failed against a conformant quest,
while their 40-cross-product suite read the value live and absorbed the
same release without moving a row. Their fix binds the suite to what
task status-flow declares. A changelog warning is not a gate; a consumer
that binds to the CLI's own declaration does not need one.
Added
-
Every success envelope now carries
contractVersion: 1, a new field
distinct fromschemaVersion(which stays the outer envelope-wrapper
version).contractVersionis a single global counter, not a per-command
one: it moves whenever any command'sdatapayload shape changes in a way
an existing decoder would misread, so a consumer has one field to check
rather than needing to track ~30 commands independently (QCLI-289).This exists because 0.6.2's
QCLI-264/QCLI-265envelope-shape unification
(data.taskbecoming baredata, among others) shipped withschemaVersion
unchanged at1on both sides of the break -- reported downstream by host
mbpm2, whose own integration suite passed 0.6.2 validation and then broke
anyway, because nothing distinguished the old shape from the new one.
That break predates this field and stays undetectable by it:
contractVersionstarts at1in the release that introduces it rather
than being backdated to claim a transition that had no signal at the time.
Anything relying on the pre-contractVersionshape needs to keep doing
what it already does today (a defensive read of either shape); this field
only protects against the next shape change, not the last one.What
contractVersiondoes NOT cover. Stated here because a field that
says what it covers and not what it omits has the same defect it was added
to fix -- and because 0.7.0 itself contains two changes it does not signal.
Verified by probing every envelope family the 0.7.0 binary emits, not read
off the source:- Error envelopes carry no
contractVersion, by design. An error
envelope is{error_type, message, hint?, input?, principal}and carries
no envelope metadata at all -- noschemaVersionand nokindeither --
so its absence here is the existing design, not an oversight. Do not
infer "contractVersionabsent ⇒ pre-0.7.0 shape" from an error envelope;
that rule holds only for success envelopes. Errors are discriminated by
the frozen exit-code taxonomy (§3) instead, which is stable and needs no
version field. This is deliberate and will not be revisited quietly: the
error envelope is specified as a closed key set, so adding a key to it is
a breaking change for any consumer validating it strictly -- and at least
one does, which is precisely why the field was added to success envelopes
only. - Configured-value changes are not payload-shape changes. The counter
moves when adatapayload's shape changes in a way an existing decoder
would misread. It does not move when a value inside an unchanged shape
changes. 0.7.0 contains exactly such a change:quest task pausenow
parks a task at"Paused"instead of"Blocked", so a consumer matching
the literal string"Blocked"stops matching whilecontractVersion
correctly stays1. ReadcontractVersionas "can my decoder still parse
this?", never as "did anything I depend on change?" -- the second question
is what a changelog is for, and this entry is the answer for this release.
- Error envelopes carry no
-
quest task view <id> --max-notes NcapsimplementationNotesto the most
recentNentries, adding anotesOmittedcount (present whenever
--max-notesis supplied, including0, never otherwise). Additive and
opt-in -- omitted,task viewis byte-for-byte unchanged (QCLI-276, one
piece of DEC-3, a shape agreed jointly with@opum-ai/lore's own
lore contextbudget work). The rest of DEC-3's originally-scoped surface
(a general--fieldsselector,--since/cursor note selection, retrieving
one note by a stable id, field-level omission metadata) is deliberately
deferred, not dropped -- tracked as QCLI-291. -
quest task edit <id> --if-revision <rev>(and a per-itemifRevisionon
task edit-batch) lets a caller supply the revision it read earlier and
have the edit refused, before any state change, if the record has since
moved -- the same exit-5 conflict a concurrent write race already produces.
quest task view <id> --jsonnow returns that revision (additive field) so
a caller has something to capture. Omitted,task editis unaffected
(QCLI-277).
Fixed
-
A write conflict's
actualRevision-- the one piece of data a caller needs
to retry without a second read -- was silently discarded for every task
write conflict, not only the new--if-revisioncase above: the internal
helper that unwraps a mutation result threw a bare error with no payload.
The documented retry protocol ("re-read the latest state and perform your
own bounded retry") was correct advice that the CLI's own diagnostic didn't
carry the means to follow. Now named in the diagnostic'sinputon every
conflict, same exit code and message (QCLI-277). -
quest agents --check --require-installedexited0for a managed
instruction block whose content is current but whose embedded Quest CLI
version string is stale (state: version-only) -- correct, deliberate
behavior since QCLI-228, and already documented that way in this CLI's own
guide and help text. The one place that still lied about it: the
block's own embedded CI-hint sentence, written into every consumer's
CLAUDE.md/AGENTS.md, which said only "current instructions exit 0, ...
missing, drifted, or malformed managed instructions exit 6" with no mention
of the version-only case -- so a reader (lore-cli, concretely, whose
CLAUDE.md said Quest CLI 0.4.0 against an installed 0.6.0 for two minor
versions) reasonably concluded CI caught version drift when it deliberately
does not. The sentence now names the exemption explicitly; the exit code
itself was never the defect (QCLI-284, DEC-4). -
quest task pause <id>parked a task at status"Blocked"by default --
a false signal read fleet-wide as "needs intervention," even when nothing
was blocking the task. The default paused status is now"Paused",
structurally unchanged (still separate from theTo Do/In Progress/Done
ladder, still reachable only viapause/start) -- a naming fix, not a
new transition (QCLI-287).
Quest CLI 0.6.2
Breaks lockstep with @opum-ai/lore, once, deliberately -- the pairing
convention every release has held since 0.5.0 otherwise. v0.6.1 was
tagged but never published: every publish attempt failed a registry E404
before writing anything (actions/setup-node exporting a placeholder
NODE_AUTH_TOKEN that defeated OIDC trusted publishing), a CI-only defect
with no content change of its own. Rather than move an already-pushed tag
(a cross-repo tag-stability concern for quest-web's own CI), this release
carries the same content 0.6.1 would have under the next unclaimed version
number. Unlike 0.6.0's "minor, not patch" note, this is not a claim
that the release is safe to take without reading further: the
### Changed (breaking) entries below are real envelope-shape breaks
(QCLI-264, QCLI-265). Lore's own 0.6.1 is an uncomplicated patch; quest's
content is not, and normally would have shared the number anyway so the
pair stays coordinated -- this release is the one exception. Read the
breaking section before upgrading.
Changed
- The root
@opum-ai/questpackage's published tarball now includes
README.mdalongsidebin/quest.cjsandLICENSE, so the npm registry
page renders it. It enteredpackage.json'sfileslist as part of the
2026-09-10 repository recreation (OPAG-70) without a specific review of
its own; ratified here as a deliberate, ordinary choice for a published
CLI package rather than reverted to match the pre-recreation three-file
shape. Confirmed by opum-cli-e2e's pair-qualification suite against both
a local pack and the live registry tarball; its expected-file-list
baseline (baselines/v0.6.2-pair) now reflects this shape as current,
not as a regression.
Changed (breaking)
-
Every single-record mutating command now carries the written record
directly in the envelope'sdata, the shapetask createandtask edit
already used.task complete,task archive,task pause,task start,
task demote,draft create,draft promoteanddraft archive
previously nested it underdata.task/data.draft, alongside the
repositoryrevisionhash and a secondkind: "success"that shadowed the
envelope's own semantic kind. A caller reading one group could not read the
other: a^status:match over--plainmatched an edit and silently missed
a complete. Read the record atdataon those commands; drop any
.data.task/.data.drafthop (QCLI-264)."Single-record" is the whole claim, and the qualifier is load-bearing rather
than pedantic:task edit-batchis a batch report, not a record, and is
unchanged. Its records stay atdata.items[].taskand it keeps its declared
data.revision: it is the workspace revision the batch committed at, which
is what a caller needs to do its own bounded retry after a conflict, and
test/qcli122-third-pass.test.tshas pinned it since QCLI-122. It is kept
on that reasoning alone -- no external consumer reads it for control flow.
It is the one payload in the CLI that is not a record, and it is meant to
stay that way. -
Planning mutations and draft reads now carry the record in
datatoo, so
"where is the record" has one answer across the whole CLI.
milestone create|edit|archive|deleteanddecision create|edit|delete
returned{record, result}, whereresultwas the same write receipt
QCLI-264 unwrapped elsewhere; they now return the record.draft viewand
draft listreturned the repository's{draft, location}pair -- the only
reads in the CLI where the record was not the payload -- and now carry the
draft's own fields withlocationinline, exactly astask viewand
task listalready carriedpath. Readdata.id, notdata.record.idor
data.draft.id;locationkeeps its name and its values (drafts,
archive/drafts), so--include-archivedcallers filter on it as before
(QCLI-265). -
quest help <command>printssummaryandusageabove the fields and
flags lists instead of below them, and each flag now carries its value
shape:--label <string, repeatable>,--acceptance-criteria <json-array>,
--comments <json-objects>, and a boolean flag claims no value at all.
Repeatability is per command, so--typereads as one scalar on
task createand as a repeatable filter ontask list.usagealready documented the positional form before this change -- on line
54 of 56, under ~50 lines of alphabetized names, so a pager, ahead, or a
context-budgeted agent never reached it. The fix is placement (QCLI-266). -
quest manifest --jsongainsparametersper command:positional
(name, required, value) andflags(value kind, and whether the flag is
repeated per item).fieldslists domain field names and cannot say that
titleis positional rather than--title, so a caller generating a
command line from the manifest -- which is what a manifest is for -- wrote
--titleand got a usage error. Additive: every existing key,fields
included, is unchanged (QCLI-266).Both surfaces and the argv parser now read one table, so what help reports,
what the manifest declares, and what the CLI accepts cannot drift apart.
Fixed
- A write conflict on
task complete/archive/pause/start/demoteand
thedraftmutations is now the documentedconflictfailure (exit 5)
rather than a success envelope, exit 0, carryingkind: "conflict"inside
data. Only the unwrapped commands ever had this;task create/edit
already classified it (QCLI-264). quest init --plainno longer prints the literal wordundefinedfor a
field it never selected (agentSkillSource,instructionsByTarget).
Display-only: the identical--jsoninvocation always omitted both keys
correctly, and the underlying writes were correct.src/cli/render.ts's
object renderer kept a key whose value is JSundefined-- unlike
JSON.stringify, which drops it -- and rendered it as the literal string.
Fixed at the shared renderer, which protects every command that assigns an
optional field straight into a data object, not justquest init(QCLI-272).--check-ac/--uncheck-ac/--remove-ac(and the--*-dodequivalents)
addressed the wrong checklist item: every display surface numbers
acceptance criteria and definition-of-done items starting at 1, but these
flags read that same number as the 0-basedindex----check-ac 3on an
item printed as position 3 silently checked the fourth item instead. Every
envelope now carries an additive, presentation-matching 1-basedposition
alongside the unchanged 0-basedindex; the flags take the printed
positionverbatim, and help/agent-guide text no longer describes a
numbered list that did not exist (QCLI-269).- The shared usage-error path (
only()insrc/cli/main.ts, 37 call sites)
discarded which flag was rejected, so a caller who supplied every required
flag correctly plus one unrecognized one was still told a reference or
actor was missing --task complete --final-summary ...was the reported
case, since--final-summarywas not yet an accepted flag ontask completeat all. Both are fixed:task completenow accepts
--final-summary, applied in the same write as the terminal transition,
and 21 of the 37 call sites now name the actual unrecognized flag instead
of a generic reference/actor sentence. The remaining 15 (themigration backlog,milestone/decision, anddraftaction dispatchers) use a
positive-gate-per-action shape that cannot take this fix without a larger
restructure and still report the old generic message -- tracked separately
rather than left silently inconsistent (QCLI-270; the remaining 15 sites
are QCLI-282). - A task or draft record present under more than one
.quest/storage
location -- the shape a partialgit addproduces -- took down every
quest taskcommand with a bare, unactionable error. The error now names
every colliding id and its exact file paths, plus sanctionedrm/git rm
recovery guidance, instead of a message with nothing to act on (QCLI-261). migration backlog status/rollbackreported a not-yet-previewed digest
aserror_type: "validation"(exit 6), not"not_found"(exit 3),
contrary to the Opum result contract's not-found convention -- a caller
branching onerror_typeto distinguish "malformed input" from "no such
record" could not do so reliably. Reclassified tonot_found; a
differently-shaped digest-mismatch case onapply()staysvalidation
deliberately, recorded as DEC-2 (QCLI-257).release.yml's post-publish registry-verification step hard-failed the
whole release if any of the seven npm packages was not yet visible within
a flat 5-minute window -- and had already false-failed a successful
publish twice (0.4.0, 0.5.0), both times onquest-win32-arm64alone
propagating slower than the other six platforms. Widened to 12 attempts
with exponential backoff (15s/30s/60s, capped), about 2.75-3x the old
window, while the common fast case is unchanged; the step's output now
also names which of two failure classes occurred (QCLI-247).
Known limitations
- 0.6.2 ships without a provenance attestation, because provenance
requires the CI OIDC path, which is currently dead. GitHub's org-wide
immutable-subject-claim policy rejects the subject-claim shape npm Trusted
Publishing expects (unrelated to the 2026-09-10 repository recreation
below), so 0.6.2 was published manually instead; a manual publish cannot
produce an attestation. Expected, not evidence of tampering. - Versions published before 2026-09-10 carry permanently dangling
provenance links (the repository recreation destroyed the commits/build
runs they point to) and cannot be repaired -- npm forbids republishing a
version. Affects 0.3.0 through 0.6.0 and the never-publishedv0.6.1
tag. Se...
Quest CLI 0.6.1 (tagged, never published)
Tagged at 1fa0fef and never published. Every publish attempt failed a
registry E404 before writing anything: actions/setup-node exported a
placeholder NODE_AUTH_TOKEN that defeated OIDC trusted publishing. That was
a CI-only defect with no content change of its own. No package reached npm
under this number. The content it would have carried shipped as 0.6.2 (see
that entry). The v0.6.1 tag stays where it is and is not re-pointed,
because quest-web's CI cites this repository's tags.
v0.6.0
@opum-ai/quest 0.6.0, published to npm and installable as @opum-ai/quest@0.6.0.
Provenance of this tag — read before citing it
This tag does not point at the commit that produced the published 0.6.0 artifact. It is kept and documented rather than moved, because the commit it should point at no longer exists and cannot be recovered.
| claim | value |
|---|---|
this tag (refs/tags/v0.6.0^{}) peels to |
a9fbc78 — "Initial commit: fresh history after repository deletion/recreation (OPAG-70)", dated 2026-09-10 |
npm records gitHead for 0.6.0 as |
ce551cf4 |
does ce551cf4 exist here? |
No. GET /repos/opum-ai/quest-cli/commits/ce551cf4 returns HTTP 422, "No commit found for SHA" |
This repository was deleted and recreated with fresh history on 2026-09-10. Its entire commit graph begins at a9fbc78. The pre-recreation history, including the commit that built 0.6.0, is not present. v0.5.0 is absent for the same reason — the history it pointed into stopped existing; it was not deleted or retagged deliberately.
The package on npm carries no attestations or signatures, so gitHead is the only provenance it records, and it dangles.
What to cite instead
Prefer the npm version over this tag. @opum-ai/quest@0.6.0 is immutable; these tags demonstrably are not. If you need a stable public reference to this release, use the registry.
What happens next
Releases after this one are tagged from history that exists, and the release pipeline asserts that the tagged commit is present and matches the published package's gitHead before publishing — so a release cannot silently acquire a provenance claim the repository contradicts. Tracked as QCLI-267.