Skip to content

gtb sync drops deploy:skills when skills/ lives at the workspace root #451

Description

@gtbuchanan

What happens

In a monorepo, deploy:skills is generated only when a package has a skills/ directory:

// packages/cli/src/lib/turbo-config.ts
hasSkills: discovery.packages.some(pkg => pkg.hasSkills),

deploySkillsTasks is gated on that flag, so a workspace whose skills/ sits at the root gets no deploy:skills task at all. It disappears the moment packages: globs are added to pnpm-workspace.yaml and the root stops being the lone package — nothing else about the skills changes.

The inconsistency

Two lines apart in the same function, the root is counted for one capability and not the other:

hasE2e:    discovery.root.hasVitestE2e || discovery.packages.some(pkg => pkg.hasVitestE2e),
hasSkills: discovery.packages.some(pkg => pkg.hasSkills),

discovery.root is a full PackageCapabilities, and capabilitiesFor already computes hasSkills for it. The information is present and unused.

Why it is silent

The turbo check in gtb verify reports only tasks that the generated expectation has and turbo.json lacks:

return Object.keys(expected.tasks)
  .filter(name => !ignored.has(name))
  .filter(name => tasks === undefined || !Object.hasOwn(tasks, name))
  .map(name => `turbo.json: missing task '${name}'`);

When the flag is false the expectation contains no deploy:skills, so there is nothing to miss and verify passes. Hand-writing //#deploy:skills into turbo.json works until the next gtb sync, which rewrites the file without it — and verify still says nothing, because it does not report extra tasks either. The failure is silent in both directions: the task never appears, and its disappearance is never reported.

Suggested fix

Count the root, and emit the root's copy as a root task — the shape hasRootEslint and rootLintTasks already use:

hasSkills:     discovery.root.hasSkills || discovery.packages.some(pkg => pkg.hasSkills),
hasRootSkills: discovery.isMonorepo && discovery.root.hasSkills,

with deploySkillsTasks emitting rootTaskKey(taskNames.deploySkills) for the root's copy, its inputs subtracting the package globs the way rootLintTasks does, and the build aggregate depending on it the way lint already depends on //#lint:eslint in turbo-aggregates.ts. gtb verify then covers it for free, since it becomes part of the generated expectation.

Context

Hit in gtbuchanan/skills, converting from a single-package repo to a monorepo. Everything that can move into a package is moving — the eval harness, the shared CLI doubles, the eval suites themselves. The skills cannot, so the root ends up carrying its configuration and skills/, and nothing else.

They cannot move because of how they are consumed. Both paths require real directories at <root>/skills/<name>/SKILL.md: the skills CLI fetching a repository tarball, and a dotfiles script that walks a local clone and symlinks each skill directory into ~/.agents/skills. The workspace's packages are private, so a skill inside one would reach neither consumer. Per-package skills serve a different model — a published package shipping skills to whoever installs it — and that is not what these are.

So a workspace can package everything it reasonably can and still have authored skills at the root. That is the shape hasSkills currently reads as "this workspace has no skills".

Related, lower priority

The same root-blindness costs a little elsewhere. After such a conversion the root still holds eslint.config.ts and vitest.config.ts; both are TypeScript, both are linted through the generated //#lint:eslint, and nothing typechecks them, because there is no root typecheck:ts counterpart gated on discovery.root.hasTypeScript. Much smaller than the skills gap, and worth splitting out if it complicates the fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions