Repository navigation
Releases: hk9890/task-manager
Release list
task-manager v0.13.0
- Action required for
go installusers and importers ofsdk/tasks:taskmgrandsdk/tasksneed Go 1.27 or newer. They needed Go 1.26. Bothgo.modfiles now saygo 1.27.0. They saidgo 1.26.0. Any Go 1.27.x builds them.go get github.com/hk9890/task-manager/sdk@v0.13.0raises thegoline of your owngo.modto 1.27.0. With Go 1.26, upgrade Go, or letGOTOOLCHAIN=autodownload a Go 1.27 toolchain. - The released
taskmgrbinaries are built with Go 1.27.2. They were built with Go 1.26.0. Under Go 1.26.0,govulncheckreported one called vulnerability in the standard library: GO-2026-4602 (CVE-2026-27139) inos. It lets a directory listing escape anos.Root.taskmgrdoes not useos.Root. Under Go 1.27.2,govulncheckreports none. - Action required for SDK callers that encode or decode
Config,GlobalConfigorPackageRefwith their own YAML library: the YAML methods of these types now take and returngo.yaml.in/yaml/v3nodes. They usedgopkg.in/yaml.v3, which is archived. This applies to(*Config).UnmarshalYAML,(*GlobalConfig).UnmarshalYAML,(*PackageRef).UnmarshalYAMLandPackageRef.MarshalYAML. A well-formed config still decodes and encodes withgopkg.in/yaml.v3, but that decoder does not callUnmarshalYAMLand records no defect of the file. Ause:value that is not a list fails, ause:entry that is not a mapping fails, a bare-entry is dropped and the next write deletes it, andhooks:is ignored. The encoder still callsMarshalYAML, and it writes a malformeduse:entry as the fields of the node, for examplekind: 8, in place of the entry. Change the import togo.yaml.in/yaml/v3, or read and write the config files throughStore,LoadGlobalConfig,UpdateGlobalConfigandSaveGlobalConfig. The SDK module now requiresgo.yaml.in/yaml/v3v3.0.5 in place ofgopkg.in/yaml.v3v3.0.1. See The config types and their YAML library in the SDK spec. - A comment body that starts with a blank line keeps that line. Before, the line was dropped when the comment was written to disk, so
taskmgr comment add,taskmgr comment edit,AddCommentandEditCommentstored the body without it, and later returned it without it. Comments stored before v0.13.0 do not get the line back. The store format does not change, and v0.12.x reads the new files correctly. - Action required for anyone who parses the release SBOMs: the SBOM files are now CycloneDX 1.7 JSON. They were SPDX 2.3 JSON, although the README and the release docs said CycloneDX. The file names stay the same, one
<archive>.sbom.jsonfor each archive. Change your parser from SPDX to CycloneDX. The SBOMs of earlier releases do not change.
task-manager v0.12.1
- A central store directory that cannot be read stops only the commands
that resolve to that store. One such directory, for example a store at
mode 000, stopped every command in every directory with no local.tasks/:
each printedread central store <dir>: … permission deniedand exited 1,
also in the project of a healthy store. A command in the project of a
healthy store now opens that store. A command in a directory that no store
is registered for printsno .tasks directory found — run 'taskmgr init' to create one. A command in the project of the unreadable store, or one that
names it with--store-name, prints the read failure as before.
taskmgr store listshows the store with the healthbroken. tasks.Resolvereturns the read failure of a central store only when
that store owns the working directory or is named by
ResolveOptions.StoreName. For every other directory it returned that
failure too, so a front end that resolves at start-up could not reach
tasks.Stores()to show the broken entry. It now opens the store that owns
the directory, or returnsErrNoStorewhen none does. The entry that owns
a directory still answers for it:Resolvedoes not fall through to the
store of a shorter ancestor.
task-manager v0.12.0
taskmgr store listlists every store when it cannot read one store
directory. It printed the read failure, exited 1 and showed no row, so the
names of the healthy stores were lost. The row of that store now has the
healthbroken, and the command exits 0. The cause is the keydetailin
--json, which is omitted on every other row, and a<store>: <detail>
line below the table otherwise. The row is neverdangling.tasks.Stores()returns every registry entry, and the new field
StoreEntry.Detailsays why a store directory could not be read. Such an
entry has the healthStoreBroken.Detailis empty for every other entry,
a broken store that only lacks itsconfig.yamlincluded.- Action required for a script or an SDK consumer that finds an unreadable
store through the exit code oftaskmgr store listor the error of
tasks.Stores(): the command exits 0 and the call returns no error for
it. Both fail only when the registry cannot be loaded. An unreadable store
is a row with the healthbrokenand the keydetail, and in the SDK an
entry withStoreBrokenand aDetailthat is not empty. Test for that in
its place. - A refused
taskmgr dep addortaskmgr rel addsays when the fault is
already in the store, and names the command that removes it. It printed
the bare message, for exampleblocked_by: referenced issue "<value>" does not exist, which names a value the caller did not pass. The message is now
blocked_by: stored value "<value>" is not a valid issue ID; remove it with 'taskmgr dep rm'. The same form reports a stored value thatreferences an issue that does not exist, astored duplicate dependency, astored dependency cycle, and forrel addastored duplicate referenceand a
stored value thatnames the issue itself, with'taskmgr rel rm'. A fault
that the add itself makes keeps its message.AddDepandAddRelated
return the same message in their*ValidationError. taskmgr dep rm,taskmgr rel rmandtaskmgr update --remove-labelare
not refused for the values that stay in the list. A removal was refused
when the list held another fault, for example withrelated: "<value>" is not a valid issue ID. A list with two such values, or more than one over
its bound, had no repair. When the fault was in the list of the other
issue,rel rmexited 1, removed the link on the first issue and left it
on the other. A list that is over its bound, or holds duplicates or values
that are not IDs, is now repaired one removal at a time.dep rm <dependent> -- <value>removes a stored value that is not an ID, as it did
for a list with no other fault. In the SDK,RemoveDep,RemoveRelated
andUpdatedo the same.taskmgr guide progressshowsdep rmandrel rm, and how to repair a
stored edge fault. It showed the two add forms only. It givestaskmgr dep rm <dependent> -- <value>for the value a message quotes andtaskmgr dep rm <x> <y>for one step of a cycle. It says to add a duplicated edge again,
becausermremoves every copy.tasks.GlobalPackageUses()returns theuse:entries of the per-user
config that resolve, each with its directory. A row is aPackageUsewith
the fieldsRefandDir, in list order. The call skips an entry that does
not resolve and loads no package. Use it to match directories:
PackageInfo.Pathoftasks.GlobalPackages()holds thepath:text as
written for an entry that does not resolve.- Action required for an SDK consumer that builds a
tasks.ValidationError
with an unkeyed composite literal: the type has an unexported field now,
sotasks.ValidationError{"field", "message"}no longer builds. Write
tasks.ValidationError{Field: "field", Message: "message"}in its place. - An ID argument that is not an issue ID is not found.
taskmgr close ../<id>printedClosed <id>, exited 0 and left the issue open, and
taskmgr show ../<id>andtaskmgr update ../<id>acted on the issue.
close,show,update,reopen, thecommentcommands and the first
argument of thedepandrelcommands now printissue not found: <value>and exit 1. In the SDK,Get,Detail,Details,Comments,
Update,Close,Reopen, the comment operations and the first argument
ofAddDep,RemoveDep,AddRelatedandRemoveRelatedreturn
ErrNotFoundfor such a value. taskmgr create --fromnames an edge that is neither a ref of the file
nor an issue ID as that. The message is"<value>" is neither a ref of this set nor a valid issue ID. It was"<value>" is not a valid issue ID,
which did not tell the writer of a misspeltrefthat no entry has that
name.Store.CreateBatchreturns the same message in its
*BatchEntryError.taskmgr package repo listandtaskmgr package repo rmcount a package
as used only when ause:entry resolves into the repository. The keys
usedandstill_usedwere matched by package name. Apath:entry of the
same name that leads elsewhere, an entry that does not resolve and a name
that two repositories provide put a package in the list. Apath:entry
that leads into the repository counts.- The warning of
taskmgr package repo rmnames the command that removes
each entry. For apath:entry it istaskmgr package rm --path <path> --global. It namedtaskmgr package rm <name> --globalfor every entry,
which exits 1 for apath:entry. Two entries that resolve to one package
give two warning lines and one name instill_used.
task-manager v0.11.0
taskmgr treeprints the open issues nested under their parents. Each
line carries[ready]or[blocked by <ids>], read from the same views as
taskmgr readyandtaskmgr blocked.taskmgr tree <id>prints one issue
and the open issues below it.--format mermaidprints the tree as Mermaid
flowchart text, and--jsonprints nested objects withready,blocked
andchildren. No command showed the graph: to read an epic took one
taskmgr showfor each child.taskmgr create --from <file>files a set of issues, all or nothing.
The file is a YAML list of 1 to 256 entries, and-reads it from stdin.
An entry takesref,title,type,priority,assignee,labels,
description,parent,blocked_byandrelated. An edge names theref
of another entry or the ID of an existing issue, in any order. A refused
entry writes nothing and is named:entry 3 (ref "schema"): .... An unknown
key, an empty list item and a second YAML document are errors.taskmgr create --from <file> --jsonprints the map from eachrefto
the new ID. It is an array of{ref, id, store, hints, warnings}in file
order. When a hook denies the set, thehook_deniedobject carriesentry,
the 1-based position in the file, andrefin place ofissue_id. Beside
--from, only--creator,--agentand--sessionare accepted.- A
pre-createhook runs for each entry of a set before the first
write. The payload can name a parent or a blocker of the same file that
is not on disk yet. A hook that looks up such an ID getsissue not found,
and a hook that denies on that denies the whole set. taskmgr showtakes several IDs. It prints one detail block for each
ID, in argument order, with a blank line between two blocks. An ID that
does not exist fails the whole command withissue not found: <id>and
prints nothing on stdout. It took one ID.- Action required for a script that reads
taskmgr show <id> --json: the
command prints an array with one element for each ID, also for one ID. It
printed a bare object. Read one issue as.[0]:.[0].titlein place of
.title. taskmgr show --fields <name,...>prints only the named fields. A name
is a top-level key of the--jsonoutput ofshow. JSON carriesidand
the named keys. Human output prints the header line and the lines of the
named fields.--fields descriptionprints the complete body, where human
output cuts a body at 4096 bytes.store,creatorandbody_external
have no human line and need--json.taskmgr commandscarries the shape of the--jsonoutput of each
command. The keyoutputgivestype(objectorarray) andfields,
each with itsname, itstypeandoptional: truewhen the field is
omitted while empty.with_flaglists the shape under a flag that replaces
it:create --from,import --batchandguide --list. The catalog listed
flags only, so a caller without the spec guessed the key names.taskmgr commands <name>...prints only the named entries. A subcommand
is one quoted argument:taskmgr commands show "comment add". An unknown
name is refused with the entries it is a part of, as
unknown command "add": did you mean "comment add" or "dep add" .... The
command took no arguments.taskmgr listandtaskmgr searchanswer a filter field given as a flag
with the-qexpression.taskmgr list --status open --type bugprints
To filter by status and type: taskmgr list -q 'status == "open" && type == "bug"'underunknown flag: --status. The line is your own command, with
every field flag folded into one-q. The command still fails, and no flag
was added. It printed the error and the help block only.Store.Watchsignals a change to a store. It returns a channel that
carries a value when the store changes, from this handle, another process
or an edit by hand. A value means "read again" and carries nothing else.
Changes within 100 ms give one signal, and a signal that is not received
yet absorbs the next one. The channel closes when the context is done or
the store directory is removed or renamed. A consumer had to poll. The SDK
module now requiresgithub.com/fsnotify/fsnotify.Store.CreateBatchcreates a set of issues that reference each other, all
or nothing. ABatchEntryis aCreateInputwith aRef, andParent,
BlockedByandRelatedaccept theRefof another entry. The engine
validates every entry and runs everypre-createhook under one lock,
before the first write. A refused entry returns a*BatchEntryErrorwith
the zero-basedIndex,RefandErr. A set holds at most 256 entries.Store.Graphreturns every issue that is not closed with its ready and
blocked marks, from one read. AGraphNodeholdsIssue,Ready,
BlockedandBlockedBy.Store.Detailsreturns theDetailof several
IDs over one read. A caller ofReadyand thenBlockedmade two reads,
which can disagree when a write lands between them.tasks.QueryFields()andtasks.QuoteQueryValue()are public. The
first returns the comparison fields of the filter language, sorted. The
second quotes a value as a string literal of that language, as
Criteria.Builddoes.- Action required for a caller that passes a
parent,blocked_byor
relatedvalue that is not an issue ID: every write refuses it.
taskmgr create --blocked-by ../at-0042absays
blocked_by: "../at-0042ab" is not a valid issue ID, and
taskmgr update --parentsays the same onparent.taskmgr dep addand
taskmgr rel addsayreferenced issue "<value>" does not exist. The SDK
returns a*ValidationErroron the field of the edge. A value such as
../<open-id>was accepted when it named a file, and any other value got
thedoes not existmessage from every command. Pass the bare ID, and
match the new message for a value that is not one. - Action required for a store that holds a blocker value that is not an
issue ID: the issue is blocked. It was listed bytaskmgr readywhile its
blocker was open.taskmgr blocked --jsonshows the value inblocked_by.
Remove it withtaskmgr dep rm <id> <value>. - Action required for a script that reads
packagesfrom
taskmgr package repo rm --json: the key isstill_used, and the output
is{name, path, still_used}. It names the packages of the removed
repository that the per-user config still uses, and is omitted when there
are none. Readstill_usedin place ofpackages. - A write that makes a parent cycle is refused.
taskmgr update <id> --parent <descendant>fails with
parent: parent cycle: <id> -> <descendant> -> <id>. It was written, and
the issues on the cycle had no root. - An issue with a dangling reference or a cycle already on disk can be
edited. Only a write that changesparent,blocked_byorrelatedis
checked on that field, so the write that removes the bad edge is the
repair.taskmgr updateof such an issue was refused, also a title edit,
withreferenced issue "<id>" does not existordependency cycle: .... - A list no longer fails when another process closes or reopens an issue
during the read. The issue is left out of that result. The read failed
with the error of the file that moved. taskmgr import --batch --jsonprints[]for an input with no
records. It printednull.taskmgr blocked --jsonprints"blocked_by_refs": []for an issue whose
only blocker does not exist. It printednull.
task-manager v0.10.0
Changelog
⚠️ Breaking changes
- 578714b: feat(sdk,cli,docs)!: install packages from repositories (#84) (@hk9890)
- 1d911b4: feat(sdk,cli,docs)!: record the coding agent and session behind a write (#86) (@hk9890)
- b612b49: chore(packages,docs)!: drop the vendored task-writing package (#85) (@hk9890)
task-manager-packages/ is no longer part of this repository. The packages live in
https://github.com/hk9890/task-manager-packages and are installed with
taskmgr package repo add. A name: entry resolves across the installed
repositories, so an existing flat install under <home>/packages/<name> must be
reinstalled; the use: entry itself is unchanged.
The SDK break in #86: AddComment, EditComment and DeleteComment take an
Actor in place of the author string.
Features
- a43124b: feat(packages): a closing topic in task-writing (#82) (@hk9890)
- 845ac93: feat(packages): a decomposing topic in task-writing (#81) (@hk9890)
- 76c11ef: feat(sdk,cli,ci,docs): guide topic summaries, and a release changelog that is not empty (#87) (@hk9890)
#81 and #82 ship from the packages repository, not from this binary: #85 removed the
vendored copy after they landed.
task-manager v0.9.0
Hooks move out of config files and into packages, taskmgr guide is reorganised
into jobs, and sdk/tasks sheds several forwarding APIs. SDK consumers must move
to sdk/v0.9.0.
⚠️ Breaking changes
sdk/tasks API
- Removed
Config.Hooks,GlobalConfig.HooksandHookID. Hooks now live in packages (#72). - Removed
Store.Query,Store.Find,Store.FindPageandFindOptions. Each forwarded toList/ListPagewith no caller outside the tests (#73). - Replaced
Store.SetNowwith theWithClockconstruction option.SetNowwas an unsynchronised field write on a type documented as safe for concurrent use (#73). Store.HookChainno longer returns a package's load error. It returns the hooks that did load, asPackagesandGuideTopicsalready do (#77).- Added
PackageRef,PackageInfo,HookInfo,ErrPackageMissing,Store.Packages,Store.HookChain,Store.InspectPackageandInspectGlobalPackage(#72), andGuideTopic(#74).
Configuration
- An absolute
path:entry in ause:list is refused. Install the package under the taskmgr home and name it withname:(#77). - A config file still carrying the withdrawn
hooks:key now fails every mutation with a message naming the key, instead of being ignored (#77). - Two
use:entries resolving to one package name no longer error. The later one is reportedshadowed(#77).
taskmgr guide
- The guide is organised into jobs.
taskmgr guidewith no argument prints the job list and nothing else (#79). - The topic ids
intro,conventionsandmore(#74), andmodel,loop,body,queryandoutput(#79), no longer resolve. An unknown topic now suggests the job that holds it, and--listremains the complete roster. - A package's
guide:entry gains an optionalinto:, which places a fragment inside a built-in job (#79).
Features
- Move hooks from config files into packages (#72)
- An addressable guide, and prose contributed by packages (#74)
- A guide in jobs, and packages that add to one (#79)
Fixes
Docs
- Bind the six body rules with a completion criterion (#75)
Install
go install github.com/hk9890/task-manager/cmd/taskmgr@v0.9.0
Or download an archive below. To verify (needs cosign 3.x):
sha256sum -c checksums.txt
cosign verify-blob checksums.txt \
--bundle checksums.txt.bundle \
--certificate-identity-regexp '^https://github\.com/hk9890/task-manager/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Full changelog: v0.8.0...v0.9.0
task-manager v0.8.0
Upgrading from v0.7.1. The SDK module is released alongside as sdk/v0.8.0.
New: taskmgr config
Configuration is now editable from the command line. Two files are addressable: the
resolved store's .tasks/config.yaml, and your per-user ~/.taskmgr/config.yaml with
--global, which needs no store and works in any directory.
taskmgr config keys # every supported key, both scopes
taskmgr config list # current values, with the file's path
taskmgr config get hook_timeout
taskmgr config set hook_timeout 5m
taskmgr config unset hook_timeoutHooks are managed with taskmgr config hook, which owns the hooks block:
taskmgr config hook add --event pre-close --run make --run test
taskmgr config hook list # in run order, with each effective id
taskmgr config hook rm pre-close#0Global hooks. A hooks block in the per-user config applies to every store on the
machine and runs before the store's own. The per-user file does not travel with a
repository, so it fits personal ergonomics — a reminder, a local lint — and not a rule
the data depends on. Inherited hooks carry a global: prefix on their id wherever they
appear, so a refusal always tells you which file to edit. A store cannot switch them off.
JSON surface: store and health
For anyone driving taskmgr --json across several stores:
issueDTOand thecreateresult carrystore, the central-registry name of the
store the issue came from. Omitted for a local store, which has no registry entry.store listrows carryhealth:ok,dangling(the entry is registered, its
directory is gone) orbroken(a directory with noconfig.yaml). Classification is
onestatper entry, so a listing and a resolution never disagree, and a store
switcher can hide the rows that will not open instead of failing one per selection.
Fixes
- Concurrent
taskmgr configwrites no longer discard each other. Twelve at once
used to leave one edit and report success twelve times. The read now happens inside
the file lock, and per-user writes take one at all (~/.taskmgr/.config.lock). - A malformed hook can be removed again. Two bad entries in the per-user config used
to fail every write on the machine and refuseconfig hook rmfor both ids, leaving
a hand edit as the only way out. A write now validates only what it introduces. - A central store stays writable when its project directory is deleted. A hook now
runs in the store directory when the project root is gone, instead of failing the
spawn and reporting a binary that plainly exists as "could not execute". store listno longer calls an intact storedangling. Astatthat fails for
any reason other than "not found" is reported as itself; only a missing directory is
dangling. Selecting a dangling store by name also gets its own message, since the
repair differs from a broken one.taskmgr -C . initderives the ID prefix from the directory's real name rather than
falling back totask— which mattered, because the prefix cannot be changed later.taskmgr config --global listworks, not onlytaskmgr config list --global.- A denied or failed hook writes its log record to the invocation's stderr.
Changes worth checking before you upgrade
- A declared hook
idmay no longer contain#. The character is reserved for the
defaulted<event>#<index>id, so the two sources can never collide. If an existing
config.yamldeclares one —id: pre-close#1— every write in that store fails
until it is renamed. Edit the id in the file, or remove the entry with
taskmgr config hook rmand add it back with a valid id. taskmgr config set <key> ""now exits 1 and points atconfig unset. It used to
perform the unset and report success, so a script passing an unset shell variable
silently reverted the key.taskmgr store listexits 1 when it cannot read a store directory, instead of
listing it asdangling.
For integrators — sdk/v0.8.0
go get github.com/hk9890/task-manager/sdk@v0.8.0Breaking
Stores() no longer takes ResolveOptions.
entries, err := tasks.Stores(opts) // sdk/v0.7.0
entries, err := tasks.Stores() // sdk/v0.8.0The registry is global, so a working directory and a store-name override selected
nothing; the parameter was accepted and discarded, which made --dir and --store-name
look as though they filtered store list. This is the only signature change, and it is
why the tag is a minor rather than a patch.
Stores() can now return an error where it previously returned rows. A stat that
fails on a store directory is no longer flattened into StoreDangling. Handle the error;
do not treat a failed listing as an empty one.
Resolution reports a dangling named store differently. Resolve with a StoreName
whose directory is gone returns a message about a missing directory rather than the
"not a finished store (no config.yaml)" text. The sentinels are unchanged — only code
matching on message text is affected.
Added
UpdateConfig, UpdateGlobalConfig, LoadGlobalConfig, SaveGlobalConfig,
GlobalConfigPath, GlobalConfig, HookID, DerivePrefix, StoreHealth,
StoreEntry.Health, Store.Name, Store.Config, Store.SetConfig.
InitWithVFS gained variadic options, which existing calls keep compiling through.
Behaviour to be aware of
- Prefer
UpdateConfig/UpdateGlobalConfigoverSetConfig/SaveGlobalConfig.
The pair taking a mutation function does the read, the change and the write inside one
hold of the lock. The setters replace the file from a snapshot read outside it, so they
are last-writer-wins by construction and are documented as such. Store.Config()now returns a deep copy, each hook'sRunargv included. It
previously shared those slices with the compiled hooks, so editing the returned value
changed the gate the next mutation executed.- A hook's working directory falls back to the store directory when the project root
no longer exists.TASKMGR_STOREalways names the data directory. - Writing the per-user config creates
<home>/.config.lock, a separate advisory lock
from the central root's.lock.
Verifying the download
The release carries per-archive SBOMs and a keyless cosign signature over the manifest.
Needs cosign 3.x.
sha256sum -c checksums.txt
cosign verify-blob checksums.txt \
--bundle checksums.txt.bundle \
--certificate-identity-regexp '^https://github\.com/hk9890/task-manager/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comtask-manager v0.7.1
Changelog
Everything since v0.6.2. v0.7.0 was tagged but never released — its release
build failed before publishing and a tag cannot be reused, so its changes ship
here.
Breaking changes
--store-pathandTASKMGR_DIRare removed. Use-Cto point resolution at a
directory the store tracks, or--store-namefor a central store by registry
name (#50).
Features
taskmgr store move— promote a local store to central, rename a central store,
or re-point a registry entry. The store moves whole,config.yamlincluded, so
its prefix and hooks survive and existing IDs stay valid (#50).- Per-issue type doc, and large bodies overflow to a content sidecar (#51).
- SDK:
Init/InitCentralenforce a maximum prefix length (#48).
Fixes
- Close 15 defects found reviewing
v0.6.1..HEAD(#52). - Monitoring: correct the hook decision vocabulary, cover
io_error, kill on
timeout (#49).
Docs
- Repair drift found by the documentation audit (#46).
SDK module: unchanged since sdk/v0.7.0 — go get github.com/hk9890/task-manager/sdk@v0.7.0.
Full diff: v0.6.2...v0.7.1