Context
The portal tickets view (News → Tickets) shows two garbage "release" groups sorted above the real versions:
vneo.d.ts - Typescript definitions for all neo framework classes
vNeo-Material Component Library v0.1
…appearing before v12.1.0. Surfaced during operator review 2026-05-30.
The Problem
Closed issues are archived under resources/content/archive/issues/<version>/, where <version> is derived from the issue's GitHub milestone title with no semver validation. IssueSyncer.mjs:356-359 (and PullRequestSyncer.mjs:111-114):
version = milestone.title.startsWith(prefix) ? milestone.title : prefix + milestone.title;
So a descriptive milestone becomes a garbage version folder (the leading v is the versionDirectoryPrefix). Empirical: issues #3286 (milestone: neo.d.ts - Typescript definitions for all neo framework classes) and #3287 (milestone: Neo-Material Component Library v0.1), both closed 2024-09-15. gh release list confirms these are not releases — they are milestones. A milestone can map to a release, but only when its title is a valid version.
This is data-level corruption: the syncer writes garbage archive buckets. tickets.mjs's sort then floats the non-semver names to the top (a separate localeCompare quirk) — a symptom; a tickets-view filter is only defense-in-depth.
The Architectural Reality
ai/services/github-workflow/sync/IssueSyncer.mjs:356 — milestone→version, no semver guard, even though the oldVersion branch (:360) and the bucket-shift compare (:389-390) already guard with semver.valid(semver.clean(...)). The milestone branch was simply missed.
ai/services/github-workflow/sync/PullRequestSyncer.mjs:111 — identical pattern for PRs.
- Closed-issue fallback chain: milestone →
oldVersion (semver-guarded) → closedAt→release (IssueSyncer.mjs:367). If version stays null, the item goes to active/Backlog (:378).
The Fix
Add && semver.valid(semver.clean(<milestone>.title)) to the milestone-as-version condition in IssueSyncer.mjs:356 and PullRequestSyncer.mjs:111. A non-semver milestone is then not treated as a release; the closed item falls through to the closedAt→release branch and is bucketed into the real version that shipped after it closed — not a garbage folder, not stuck in Backlog. Re-sync regenerates the clean archive (ADR-0004 regeneratable-cache).
Verified: semver.valid(semver.clean(t)) → v12.1.0 / 12.1.0 / v10.0.0-beta.1 valid; neo.d.ts - … / Neo-Material Component Library v0.1 → null.
Decision Record impact
aligned-with ADR 0004 — the archive is a regeneratable cache; a re-sync produces the corrected buckets.
Acceptance Criteria
Out of Scope
- The
release.tagName branches (IssueSyncer:371 / PullRequestSyncer:119 / DiscussionSyncer:114) — releases are reliably semver; a consistency guard there is a small follow-up, not this bug.
- Re-tagging/removing the legacy GitHub milestones (GitHub-side; unnecessary once the syncer guards).
Related
- #11365 (Restore milestone-aware routing capability in gh-workflow syncers) — adjacent milestone-handling, distinct concern.
- Authority:
learn/agentos/decisions/0004-github-content-architecture.md.
Retrieval Hint: grep milestone.title + versionDirectoryPrefix in ai/services/github-workflow/sync/.
Context
The portal tickets view (News → Tickets) shows two garbage "release" groups sorted above the real versions:
vneo.d.ts - Typescript definitions for all neo framework classesvNeo-Material Component Library v0.1…appearing before
v12.1.0. Surfaced during operator review 2026-05-30.The Problem
Closed issues are archived under
resources/content/archive/issues/<version>/, where<version>is derived from the issue's GitHub milestone title with no semver validation.IssueSyncer.mjs:356-359(andPullRequestSyncer.mjs:111-114):version = milestone.title.startsWith(prefix) ? milestone.title : prefix + milestone.title;So a descriptive milestone becomes a garbage version folder (the leading
vis theversionDirectoryPrefix). Empirical: issues #3286 (milestone: neo.d.ts - Typescript definitions for all neo framework classes) and #3287 (milestone: Neo-Material Component Library v0.1), both closed 2024-09-15.gh release listconfirms these are not releases — they are milestones. A milestone can map to a release, but only when its title is a valid version.This is data-level corruption: the syncer writes garbage archive buckets.
tickets.mjs's sort then floats the non-semver names to the top (a separatelocaleComparequirk) — a symptom; a tickets-view filter is only defense-in-depth.The Architectural Reality
ai/services/github-workflow/sync/IssueSyncer.mjs:356— milestone→version, no semver guard, even though theoldVersionbranch (:360) and the bucket-shift compare (:389-390) already guard withsemver.valid(semver.clean(...)). The milestone branch was simply missed.ai/services/github-workflow/sync/PullRequestSyncer.mjs:111— identical pattern for PRs.oldVersion(semver-guarded) →closedAt→release (IssueSyncer.mjs:367). Ifversionstays null, the item goes to active/Backlog (:378).The Fix
Add
&& semver.valid(semver.clean(<milestone>.title))to the milestone-as-version condition inIssueSyncer.mjs:356andPullRequestSyncer.mjs:111. A non-semver milestone is then not treated as a release; the closed item falls through to theclosedAt→release branch and is bucketed into the real version that shipped after it closed — not a garbage folder, not stuck in Backlog. Re-sync regenerates the clean archive (ADR-0004 regeneratable-cache).Verified:
semver.valid(semver.clean(t))→v12.1.0/12.1.0/v10.0.0-beta.1valid;neo.d.ts - …/Neo-Material Component Library v0.1→null.Decision Record impact
aligned-with ADR 0004— the archive is a regeneratable cache; a re-sync produces the corrected buckets.Acceptance Criteria
IssueSyncer+PullRequestSyncertreat a milestone as a version only when it is valid semver.closedAt→release version (or Backlog if none), not a title-named folder.resources/content/archive/issues/contains only semver version folders; the two garbage groups disappear fromtickets.json.tickets.mjsskips or sorts-last any residual non-semver groups.Out of Scope
release.tagNamebranches (IssueSyncer:371/PullRequestSyncer:119/DiscussionSyncer:114) — releases are reliably semver; a consistency guard there is a small follow-up, not this bug.Related
learn/agentos/decisions/0004-github-content-architecture.md.Retrieval Hint: grep
milestone.title+versionDirectoryPrefixinai/services/github-workflow/sync/.