Background
A skillset references its members as name@ref strings, where ref is either pinned (foo@2.0, resolved literally) or moving (foo@latest → skill.latestVersion; foo@<dist-tag> → skill.distTags[tag]). The propagation engine already exists: when a member skill publishes a new version, fireSkillsetMirrorForMember → bumpRevisionsForChangedMember → maybeBumpRevisionForMemberChange recomputes the skillset's public-resolved member snapshot, and only if that snapshot moved does it cut a new skillset revision and re-export the plugin (syncSkillsetsForMember).
Consequence today: a member new version propagates to the skillset only if that member was set to a moving ref (@latest/dist-tag). A skillset whose members are pinned never auto-updates — the owner must re-point each member by hand. This is the friction reported in practice: chronoai-service-manual-bundle stayed at revision 1.2 after a member (ornn-agent-manual-cli) advanced to 1.5, because the member ref did not float.
Purpose
Add a single skillset-level opt-in toggle — display name "Always keep skills in this skillset up to date" — that makes the resolver treat every member as "resolve to latest," so any member new version auto-updates the skillset and (if exported) re-publishes the marketplace plugin, with no per-member ref juggling.
This is a lifecycle/registry convenience (the npm-analog: a set that always tracks each dependency's latest), exposed API-first on the skillset resource so agents can set it, with the ornn-web toggle as the secondary surface. It mirrors the existing exportAsPlugin opt-in in shape and default.
Decided semantics (product call): when the toggle is ON it overrides ALL member refs — pinned included — to each member's latest version. Pinning is intentionally inert while the toggle is on (simplest mental model; "always keep ALL up to date" is literal). The override is non-destructive: stored members refs are preserved and only overridden at resolution time, so turning the toggle off restores the original pinned/moving behavior.
Implementation Specification
New persisted field: autoUpdateMembers?: boolean on the skillset, default false. Suggested commit decomposition (each self-contained, tree green at every commit):
-
feat(api): add autoUpdateMembers field to skillset schema + persistence
types.ts: add autoUpdateMembers?: boolean to SkillsetDocument (next to exportAsPlugin, ~L284) and to the read/view types (SkillsetView ~L379 and any DTO returned by the API).
repository.ts: default autoUpdateMembers: data.autoUpdateMembers ?? false on create (~L237); set-on-update guard if (data.autoUpdateMembers !== undefined) setFields.autoUpdateMembers = ... (~L278); project on read mapping (~L523). No new index required.
-
feat(api): resolver honors autoUpdateMembers — resolve every member to latest
- In the member-resolution path used by recompute —
recompute.ts computePublicResolvedMembers (~L108) and/or the loader crud/service.ts createVersionLoader (~L1712) / resolveVersionRef (~L110) — when the owning skillset's autoUpdateMembers === true, resolve each member name to skill.latestVersion regardless of the stored ref (ignore pins and dist-tags). Preserve members: string[] unchanged (override only at resolution). Keep the existing public-only filter and the SKILLSET_MIN_PUBLIC_EXPORT_MEMBERS floor intact. Thread the flag into the resolver (read from the skillset doc already loaded by the recompute path).
- No change needed to
maybeBumpRevisionForMemberChange itself: once the resolver honors the flag, a pinned member's new version does move the public-resolved snapshot, so the existing diff-and-bump logic fires naturally.
-
feat(api): immediate catch-up on enabling autoUpdateMembers
service.ts: when autoUpdateMembers transitions false → true, trigger an immediate recompute → revision bump → re-export for that one skillset (reuse the existing per-skillset recompute/bump entry that bumpRevisionsForChangedMember uses), so members that are already behind are brought current at once rather than waiting for the next member publish. Re-export to the mirror only when exportAsPlugin is on.
-
feat(api): accept + validate autoUpdateMembers on the skillset update route
routes.ts: add autoUpdateMembers: z.boolean().optional() to the skillset update/PATCH body schema and pass it through to the service. Author/admin-authorized only (same guard as other skillset mutations).
-
feat(web): "Always keep skills up to date" toggle in the skillset editor
ornn-web/src/types/skillset.ts: mirror the field.
ornn-web/src/hooks/useSkillsets.ts: include it in the update mutation.
ornn-web/src/components/skillset/SkillsetForm.tsx (or a settings section / near the SkillsetPluginExportCard): a labeled toggle "Always keep skills in this skillset up to date" with helper text stating plainly that while on, all members — including pinned ones — always resolve to their latest version. Follow docs/DESIGN.md.
-
docs: changeset for autoUpdateMembers — bun changeset (minor bump, both packages).
Affected Files
ornn-api/src/domains/skillsets/types.ts
ornn-api/src/domains/skillsets/repository.ts
ornn-api/src/domains/skillsets/service.ts
ornn-api/src/domains/skillsets/recompute.ts
ornn-api/src/domains/skills/crud/service.ts (createVersionLoader / resolveVersionRef)
ornn-api/src/domains/skillsets/routes.ts
ornn-web/src/types/skillset.ts
ornn-web/src/hooks/useSkillsets.ts
ornn-web/src/components/skillset/SkillsetForm.tsx (+ export-card area if that's where the toggle lands)
- Colocated unit tests for each of the above + integration test under
ornn-api/tests/
.changeset/*.md
Verification Checklist
Definition of Done
A skillset owner can turn on "Always keep skills in this skillset up to date" via the API or ornn-web. While on, every member — pinned or not — resolves to its latest version; any member's new version automatically bumps the skillset revision and, for exported skillsets, re-publishes the marketplace plugin, with no manual per-member ref changes. Turning it off restores the previous pinned behavior. Fully tested and documented via changeset.
Related to #1155 (skillset plugin export), #1162 (auto-incremented skillset revision), #1165 (revision-bump-on-member-change). Distinct axis from #1176/#1178 (GitHub source → skill drift auto-sync); this is member skill version → skillset propagation.
Background
A skillset references its members as
name@refstrings, whererefis either pinned (foo@2.0, resolved literally) or moving (foo@latest→skill.latestVersion;foo@<dist-tag>→skill.distTags[tag]). The propagation engine already exists: when a member skill publishes a new version,fireSkillsetMirrorForMember→bumpRevisionsForChangedMember→maybeBumpRevisionForMemberChangerecomputes the skillset's public-resolved member snapshot, and only if that snapshot moved does it cut a new skillset revision and re-export the plugin (syncSkillsetsForMember).Consequence today: a member new version propagates to the skillset only if that member was set to a moving ref (
@latest/dist-tag). A skillset whose members are pinned never auto-updates — the owner must re-point each member by hand. This is the friction reported in practice:chronoai-service-manual-bundlestayed at revision1.2after a member (ornn-agent-manual-cli) advanced to1.5, because the member ref did not float.Purpose
Add a single skillset-level opt-in toggle — display name "Always keep skills in this skillset up to date" — that makes the resolver treat every member as "resolve to latest," so any member new version auto-updates the skillset and (if exported) re-publishes the marketplace plugin, with no per-member ref juggling.
This is a lifecycle/registry convenience (the npm-analog: a set that always tracks each dependency's
latest), exposed API-first on the skillset resource so agents can set it, with the ornn-web toggle as the secondary surface. It mirrors the existingexportAsPluginopt-in in shape and default.Decided semantics (product call): when the toggle is ON it overrides ALL member refs — pinned included — to each member's latest version. Pinning is intentionally inert while the toggle is on (simplest mental model; "always keep ALL up to date" is literal). The override is non-destructive: stored
membersrefs are preserved and only overridden at resolution time, so turning the toggle off restores the original pinned/moving behavior.Implementation Specification
New persisted field:
autoUpdateMembers?: booleanon the skillset, defaultfalse. Suggested commit decomposition (each self-contained, tree green at every commit):feat(api): add autoUpdateMembers field to skillset schema + persistencetypes.ts: addautoUpdateMembers?: booleantoSkillsetDocument(next toexportAsPlugin, ~L284) and to the read/view types (SkillsetView~L379 and any DTO returned by the API).repository.ts: defaultautoUpdateMembers: data.autoUpdateMembers ?? falseon create (~L237); set-on-update guardif (data.autoUpdateMembers !== undefined) setFields.autoUpdateMembers = ...(~L278); project on read mapping (~L523). No new index required.feat(api): resolver honors autoUpdateMembers — resolve every member to latestrecompute.tscomputePublicResolvedMembers(~L108) and/or the loadercrud/service.tscreateVersionLoader(~L1712) /resolveVersionRef(~L110) — when the owning skillset'sautoUpdateMembers === true, resolve each member name toskill.latestVersionregardless of the stored ref (ignore pins and dist-tags). Preservemembers: string[]unchanged (override only at resolution). Keep the existing public-only filter and theSKILLSET_MIN_PUBLIC_EXPORT_MEMBERSfloor intact. Thread the flag into the resolver (read from the skillset doc already loaded by the recompute path).maybeBumpRevisionForMemberChangeitself: once the resolver honors the flag, a pinned member's new version does move the public-resolved snapshot, so the existing diff-and-bump logic fires naturally.feat(api): immediate catch-up on enabling autoUpdateMembersservice.ts: whenautoUpdateMemberstransitionsfalse → true, trigger an immediate recompute → revision bump → re-export for that one skillset (reuse the existing per-skillset recompute/bump entry thatbumpRevisionsForChangedMemberuses), so members that are already behind are brought current at once rather than waiting for the next member publish. Re-export to the mirror only whenexportAsPluginis on.feat(api): accept + validate autoUpdateMembers on the skillset update routeroutes.ts: addautoUpdateMembers: z.boolean().optional()to the skillset update/PATCH body schema and pass it through to the service. Author/admin-authorized only (same guard as other skillset mutations).feat(web): "Always keep skills up to date" toggle in the skillset editorornn-web/src/types/skillset.ts: mirror the field.ornn-web/src/hooks/useSkillsets.ts: include it in the update mutation.ornn-web/src/components/skillset/SkillsetForm.tsx(or a settings section / near theSkillsetPluginExportCard): a labeled toggle "Always keep skills in this skillset up to date" with helper text stating plainly that while on, all members — including pinned ones — always resolve to their latest version. Followdocs/DESIGN.md.docs: changeset for autoUpdateMembers—bun changeset(minor bump, both packages).Affected Files
ornn-api/src/domains/skillsets/types.tsornn-api/src/domains/skillsets/repository.tsornn-api/src/domains/skillsets/service.tsornn-api/src/domains/skillsets/recompute.tsornn-api/src/domains/skills/crud/service.ts(createVersionLoader/resolveVersionRef)ornn-api/src/domains/skillsets/routes.tsornn-web/src/types/skillset.tsornn-web/src/hooks/useSkillsets.tsornn-web/src/components/skillset/SkillsetForm.tsx(+ export-card area if that's where the toggle lands)ornn-api/tests/.changeset/*.mdVerification Checklist
foo@2.0) resolves tofoo'slatestVersionin the public-resolved snapshot.exportAsPluginis on.bun run test,bun run lint,bun run typecheckgreen; changeset present.Definition of Done
A skillset owner can turn on "Always keep skills in this skillset up to date" via the API or ornn-web. While on, every member — pinned or not — resolves to its latest version; any member's new version automatically bumps the skillset revision and, for exported skillsets, re-publishes the marketplace plugin, with no manual per-member ref changes. Turning it off restores the previous pinned behavior. Fully tested and documented via changeset.
Related to #1155 (skillset plugin export), #1162 (auto-incremented skillset revision), #1165 (revision-bump-on-member-change). Distinct axis from #1176/#1178 (GitHub source → skill drift auto-sync); this is member skill version → skillset propagation.