Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/selected-skill-distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/intent': minor
---

Add explicit repository skill distribution to the maintainer command workflow. Keep skills in their owning packages, record the selection or opt-out in `skill_tree.yaml`, and generate Claude/Cursor plugin metadata and consumer install commands for `npx skills add` and `gh skill add`. Preserve unrelated plugin fields, require explicit prerequisite selection, and check generated files for drift without publishing or installing anything on the maintainer's behalf.
42 changes: 42 additions & 0 deletions packages/intent/meta/generate-skill/references/distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Offer skills from the repository

Read this when setting up maintainer workflow or preparing skills for consumers who use GitHub skill installers or plugins. Keep the authoritative skills beside their owning package's code. Repository distribution generates metadata pointing to those directories; it does not copy the skill text into a second tree.

## Record the maintainer's choice

`intent maintainer setup` explains repository distribution until a choice is saved. For selected repository skills, run:

```sh
intent maintainer setup --distribution repo --skill discover-library --skill query
```

Use the actual registered skill names. The repository is read from package metadata; supply `--repository owner/repo` when it cannot be established. `--plugin-name` chooses an initial plugin name when the default is unsuitable. To keep only the existing package distribution workflow, run:

```sh
intent maintainer setup --distribution none
```

The choice lives under `distribution` in `skill_tree.yaml`. Repeated setup preserves it. Adding a skill never adds it to the public selection. If an exported skill requires another local skill, include that prerequisite explicitly; the generator does not silently expand the selection. Preserve prior decisions in the spec. The domain map continues to describe tasks and knowledge, not installer configuration.

## Generate and check

After authoring, run `intent maintainer sync`. It updates `skills` paths in `.claude-plugin/plugin.json` and `.cursor-plugin/plugin.json`, and the matching root-source entry in each `marketplace.json`. It preserves unrelated plugin fields and other marketplace entries, and rejects conflicting plugin identities or source roots. It also writes `.intent/skill-distribution.json` with the selected source paths and install arguments, and prints copyable consumer commands. Commit the generated metadata alongside the tree and skills through the repository's normal review process.

`intent maintainer status` reports stale generated files; `--json` also includes the saved distribution choice and consumer commands. `intent maintainer check` requires a recorded choice and synchronized exports alongside the existing authoring and source-review checks. Rerun source review after synchronization so the report covers the final files. Opting out after generating exports clears Intent's selected paths and its marketplace entry on the next sync, retaining unrelated plugin features. It does not revoke already installed copies or make public GitHub files private.

## Explain the consumer options

- **Before package installation:** a discovery skill can explain the developer tasks a library supports and help decide whether it fits the project. Respect the chosen stack and existing dependencies. Make any package installation a deliberate project change, then hand off API implementation to the installed version's skills and source. Avoid embedding a second set of version-sensitive API instructions in the discovery skill.
- **Installed package guidance:** consumers can keep using Intent's `list`, `install`, and `load` workflow for skills shipped with the package version they installed.
- **GitHub skill installers:** use the generated `npx skills add owner/repo --skill <selected-names>` or `gh skill add owner/repo <exact-SKILL.md-path>` commands. Their default installation scope is the project. User scope is a separate consumer choice (`--global` for skills, `--scope user` for gh). Repository source location and installation scope are separate concepts.
- **Plugins:** use the generated marketplace metadata with Claude Code or Cursor's native plugin installation. The plugin references the selected package directories; it does not require Intent to invoke those skills.

The generated selection controls Intent's exported metadata and suggested install commands. Third-party installers retain their own discovery rules; a full repository scan or explicit `--all` can expose other public skills. Use the named selection or exact paths when distributing a curated subset. Check references from the installed skill directory and test the actual consumer task separately: copying a skill does not prove that all its links or recommendations are portable.

Publish through the library's normal npm/GitHub release process. `gh skill publish --dry-run` can validate a GitHub skill release without publishing it; actual releases remain a maintainer action. skills.sh indexes public GitHub skills through real installation usage; there is no submission API used by Intent. Do not simulate installations to create a listing.

## Verify installer compatibility

The repository's optional `tests/integration/distribution-installers.test.ts` exercises generated metadata with real CLIs. Build Intent, then set `INTENT_GH_SKILL_BIN` and `INTENT_SKILLS_BIN` to installed executables and run that test. It installs only a selected nested package skill into temporary consumer projects, checks its bundled reference, and runs `gh skill publish --dry-run`. Telemetry is disabled. It does not install user-level skills, publish a release, or prove native plugin activation or agent task quality.

Primary format references: [skills CLI](https://github.com/vercel-labs/skills), [GitHub skill install](https://cli.github.com/manual/gh_skill_install), [Claude plugin paths](https://code.claude.com/docs/en/plugins-reference#path-behavior-rules), and [Cursor plugins](https://cursor.com/docs/reference/plugins).
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Use the repository's Intent command for all six actions. These commands perform bookkeeping; the maintainer or coding agent still supplies task knowledge, source-backed guidance, and review conclusions.

1. Run `intent maintainer setup` once. It installs repository guidance and creates missing planning records, preserving existing documents. A monorepo uses one shared record and package-owned skill directories. If several record locations exist, select the established one with `--artifacts <repository-relative-directory>`; do not merge them by guessing.
1. Run `intent maintainer setup` once. It installs repository guidance and creates missing planning records, preserving existing documents. A monorepo uses one shared record and package-owned skill directories. If several record locations exist, select the established one with `--artifacts <repository-relative-directory>`; do not merge them by guessing. Read [repository distribution](distribution.md) and explain the option to the maintainer during setup. Save the selected skills or opt-out with the setup command; do not infer a public selection from directory placement or repeat a recorded decision.
2. For a new task, run `intent maintainer add <name> --domain <slug> --description <activation-text> --source <path>`. In a monorepo, pass `--package packages/<owner>`. Repeat `--source` or `--requires` for multiple entries. Source paths are relative to the owning package; `owner/repo:path` is relative to the repository. Use `--path <package-relative-path>/SKILL.md` for an established custom layout. To register an existing skill, supply its name, domain, package, and path; its frontmatter supplies the other fields.
3. Author the skill and reconcile all three records using the procedures in this skill. The command creates a skeleton and a domain-map entry with unassessed task coverage. Write that coverage and the spec's decisions/history. Remove `<!-- intent:needs-authoring -->` only after authoring the corresponding document. Do not remove it simply to make a check pass.
4. Run `intent maintainer status` to see missing work, stale metadata, and pending reviews. `--json` includes the full source-review report. For a supplied PR base, use `--base <ref>`.
Expand Down
13 changes: 13 additions & 0 deletions packages/intent/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,19 @@ function createCli(runtime: InstallCommandRuntime = {}): CAC {
)
.option('--path <path>', 'SKILL.md path, relative to the owning package')
.option('--domain <slug>', 'Domain for a new skill')
.option(
'--distribution <mode>',
'Repository distribution: repo for selected public skills, none to opt out',
)
.option(
'--repository <owner/repo>',
'GitHub repository for skill distribution',
)
.option('--plugin-name <name>', 'Name for the generated skill plugin')
.option(
'--skill <name>',
'Skill to distribute from the repository; repeat to select more',
)
.option('--description <text>', 'Activation description for a new skill')
.option(
'--source <path>',
Expand Down
21 changes: 19 additions & 2 deletions packages/intent/src/commands/maintainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ import { addSkill } from '../maintainer/add.js'
import { planMaintainerSync } from '../maintainer/sync.js'
import { withMaintainerLock, writeChanges } from '../maintainer/files.js'
import { createReview } from '../review/review.js'
import {
configureDistribution,
distributionChoice,
readDistribution,
} from '../maintainer/distribution.js'
import { detectIntentCommandPackageManager } from '../shared/command-runner.js'
import {
buildMaintainerGuidanceBlock,
writeIntentSkillsBlock,
} from './install/guidance.js'
import { runReviewCommand } from './review.js'
import { runValidateCommand } from './validate.js'
import type { DistributionOptions } from '../maintainer/distribution.js'

export interface MaintainerCommandOptions {
export interface MaintainerCommandOptions extends DistributionOptions {
artifacts?: string
package?: string
path?: string
Expand All @@ -35,7 +41,7 @@ export async function runMaintainerCommand(
options: MaintainerCommandOptions,
): Promise<void> {
const allowed: Record<string, Array<string>> = {
setup: ['artifacts'],
setup: ['artifacts', 'distribution', 'repository', 'pluginName', 'skill'],
add: [
'artifacts',
'package',
Expand Down Expand Up @@ -69,6 +75,7 @@ export async function runMaintainerCommand(
await withMaintainerLock(project.root, () => {
if (action === 'setup') {
const created = setupRecords(project)
configureDistribution(project, options)
writeIntentSkillsBlock({
...buildMaintainerGuidanceBlock(
detectIntentCommandPackageManager(project.root),
Expand All @@ -83,6 +90,11 @@ export async function runMaintainerCommand(
console.log(
'Next: intent maintainer add <name> --domain <domain> --description <activation> --source <path>. Use --package <directory> for a workspace package. Use intent meta generate-skill for the authoring procedure.',
)
const distribution = readDistribution(project)
if (!distribution) console.log(distributionChoice)
console.log(
`Repository distribution: ${distribution?.mode ?? 'unconfigured'}. Run maintainer sync after authoring to update export metadata.`,
)
} else if (action === 'add') {
console.log(`Registered ${addSkill(project, name, options)}.`)
console.log(
Expand All @@ -94,6 +106,7 @@ export async function runMaintainerCommand(
console.log(`Synchronized ${plan.changes.length} file(s).`)
for (const problem of plan.problems)
console.log(`Remaining: ${problem}`)
for (const command of plan.distribution.commands) console.log(command)
}
})
return
Expand All @@ -109,6 +122,10 @@ export async function runMaintainerCommand(
relative(project.root, change.path),
),
problems: plan.problems,
distribution: {
mode: plan.distribution.mode,
commands: plan.distribution.commands,
},
review,
}
if (options.json) console.log(JSON.stringify(status, null, 2))
Expand Down
Loading
Loading