feat(projects): add --summary, remap --description to the long field - #69
Open
torarnv wants to merge 4 commits into
Open
feat(projects): add --summary, remap --description to the long field#69torarnv wants to merge 4 commits into
torarnv wants to merge 4 commits into
Conversation
`issues dependencies` read a `[robot] Metadata` block embedded in the issue description. Nothing has written that block since 63fc213, first released in v1.5.0, moved dependency writes to Linear's native issueRelationCreate mutation; that commit touched eight files and internal/cli/issues.go was not among them. The reader has not been modified since the initial commit. So the command did not fail. It printed `none` — for every issue, including issues with genuine native relations set through the Linear UI. `none` reads as an answer, and the answer it reads as is "this issue is unblocked". Anyone trusting it concludes work is ready to start when it is blocked. `deps <issue-id>` already reads relations/inverseRelations and has done since the first commit, so the use case is covered by a command that gets it right. The link-deps skill listed `issues dependencies` among its verification commands, directly below the two `deps` invocations that supersede it.
Both commands read a `[robot] Metadata` block embedded in the issue description. Nothing has written that block since 63fc213, first released in v1.5.0, moved dependency writes to Linear's native issueRelationCreate mutation, and `blocking` never had a writer in any version — it has never worked. `blocked-by` was the worse of the two. With no metadata to parse it fell through to a hardcoded `check description or Linear UI for blocking issues`, but only when the description was non-empty; otherwise it printed `none`. Its answer turned on whether the description happened to be blank and never on the relations. An issue with two genuine relations set through the Linear UI got `none` from the commands whose job is reporting relations. Both now call GetIssueWithRelations and read relations/inverseRelations, the same source `deps` has read correctly since the first commit. Linear stores a blocker as "blocker blocks queried", so blockers come off the inverse side; both connections also carry related/duplicate/similar relations, which say nothing about blocking and are filtered out. Output is one line per issue as `ABC-123 [State] Title`, replacing the Go slice syntax (`[DEV-12 DEV-9]`) that `fmt.Printf("%v")` on a []string would have printed had the store ever held data. The state is not decoration: a blocker already in Done does not block anything, and the identifier alone cannot tell you that. Completed blockers are listed rather than filtered, because silently dropping a real relation is the failure this commit exists to fix. Empty results still print `none`. The line builders are pure functions over IssueWithRelations, so they are testable without a mock client — the first coverage this area has had since c449bf2 deleted the metadata tests.
The store kept structured data by appending a block to an issue's description
and to a project's description and content:
<details><summary>🤖 Metadata</summary>
```json
{ "dependencies": ["DEV-1", "DEV-2"] }
```
</details>
pkg/linear/metadata/metadata.go justified this as "a hidden collapsible section
in descriptions to avoid cluttering the UI". It was not hidden. Linear's editor
is rich text and converts recognised Markdown as it is typed; it has no raw-HTML
passthrough, so the <details> and <summary> tags rendered as literal visible
text and the json fence rendered as a code block. Every user looking at the
issue in Linear saw the block sitting at the bottom of the description.
The CLI did write to this store. `issues create` and `issues update` fed
--depends-on and --blocked-by into UpdateIssueMetadataKey under the keys
"dependencies" and "blocked_by", from the first release up to and including
v1.4.1. 63fc213, first released in v1.5.0, replaced those writes with native
issueRelationCreate, and nothing has written to the store since. The project-side
writers were different: UpdateProjectMetadataKey and RemoveProjectMetadataKey
were never reachable from any command, only from the Client passthrough, so no
project description ever received a block from this CLI. The last readers went
with `issues dependencies` and the blocked-by/blocking port in the two preceding
commits.
Removed: the pkg/linear/metadata package; UpdateIssueMetadataKey and
RemoveIssueMetadataKey on the issues client; UpdateProjectMetadataKey and
RemoveProjectMetadataKey on projects; the four Client passthroughs;
validation.IsValidMetadataKey, whose only caller was UpdateIssueMetadataKey; the
Metadata field on core.Issue, core.ParentIssue, core.Project and
core.IssueWithDetails; and the Metadata Management section of pkg/linear/doc.go.
core.Attachment.Metadata stays. That is Linear's own Attachment.metadata
JSONObject, unrelated to this store.
Two consequences beyond the deletion. Extraction ran on every issue and project
read to strip these blocks back out before display, so any issue written by
v1.4.1 or earlier will now show its block in CLI output. That is a smaller change
than it sounds: the block has been plainly visible in Linear's own UI the whole
time, and extraction only ever hid it from the CLI, so this makes the two agree.
And UpdateIssue no longer fetches the issue before every description update just
to re-inject metadata, which drops an API round-trip from each description edit.
BREAKING CHANGE: pkg/linear is a public library as of joa23#47 and doc.go documented
the metadata API as a supported generic key/value feature. Library consumers
using it will not compile. pkg.go.dev reports no known importers for pkg/linear
or any of its subpackages, and a GitHub code search for the removed symbols
returns hits only in this repository.Linear gives a project two text fields. Its API calls the short one under the project title "description" and caps it at 255 characters; the long-form document below it is "content", with no limit. The CLI mirrored those API names straight through, so `projects create -d` and `projects update -d` wrote the 255-character field and the long document had no flag at all. That made the obvious command fail on anything real: cat spec.md | linear projects create "Rewrite" -d - Any spec longer than 255 characters came back as an opaque GraphQL error, and the field a user would call the description — the one holding the actual document in the UI — was unreachable. --description now writes the long field and a new --summary writes the short one, so each flag names what a reader of the Linear UI would call it. --summary is length-checked before the request, so an over-long one is reported against the flag that carries it instead of arriving as "Argument Validation Error" naming no field. The measuring is left to validation.ValidateStringLength; only the suggestion to move the text to --description is added here, since the library layer knows nothing about flags. The same swap runs through core.Project and UpdateProjectInput, where Summary and Description now carry the `description` and `content` struct tags. Those tags are the only place Linear's names survive; everything above them speaks in summary/description terms. JSON output follows the flags: a project's `description` key is now the long document and `summary` is the short line. Text output labels them `Summary:` and `DESCRIPTION`. Keeping the keys aligned to Linear's API names instead would have left `--description` writing one field while `.description` read the other, which is the confusion this commit exists to remove. Breaking for callers passing a short blurb to --description: it now lands in the description rather than the summary. Nothing errors; the text moves.
torarnvforce-pushed
the
torarne/dev-17-projects-summary-description-json-keys
branch
from
August 9, 2026 12:35
f9185f3 to
d142cccCompare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Linear gives a project two pieces of prose. In its API the short line under the project title is called
descriptionand is capped at 255 characters; the long-form document below it iscontent, with no limit.The CLI passed those API names straight through, so
--descriptionwrote the short field and the long document had no flag at all. The obvious command failed on anything real:This PR renames the two fields to match what a reader of the Linear UI would call them:
--description--summary--description--summaryis length-checked locally against the 255-character cap instead of deferring to the server's error, which named no field and suggested no fix.Changes
projects create/projects updategain--summary;-d/--descriptionnow writes the long document. Both commands' help text and examples updated, including-d -for stdin.core.ProjectandUpdateProjectInput:SummaryandDescriptionnow carry thedescriptionandcontentstruct tags. Those tags are the only place Linear's names survive — everything above them speaks in summary/description terms.CreateProjecttakes asummaryparameter alongsidedescription, throughpkg/linear/client.goand theProjectClientOperationsinterface..summaryis the short line,.descriptionis the long document.Summary:andDESCRIPTION(wereDescription:andCONTENT).Breaking changes
Both are documented in the CHANGELOG under Changed.
--descriptionnow writes a different field. Scripts passing a short blurb should switch to--summary. Nothing errors if they don't — the text just lands in the description instead of the summary.jq '.description'on a project now returns the long document;.summaryis the short line.Keeping the JSON keys aligned to Linear's API names was the alternative. It was rejected because it would leave
--descriptionwriting one field while.descriptionread the other — precisely the confusion this PR exists to remove.Testing
make testgreen,go build ./...clean.--description.Review notes
torarne/dev-15-finish-9-port-stranded-dependency-commands-to-native), which removes the description-embedded metadata store. It needs to land first — retarget this tomainonce it does. Only the single commitfeat(projects): …belongs to this PR.