Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
## [Unreleased]

### Added
- `skillforge update <path>`: bump the `version:` field of a SKILL.md in one shot — `--bump <patch|minor|major>` or `--new-version <semver>` (mutually exclusive, exactly one required). Pre-release tags (`-beta`, `-rc.1`, …) are dropped on any bump, matching `npm version`. A missing `version:` field is treated as the schema default of `0.0.1`. Validates the proposed frontmatter against the schema before touching disk and uses a line-surgical write so the body bytes and other YAML formatting are preserved byte-for-byte. `--dry-run` reports the new version without writing.
- `skillforge lint <path>`: warnings-first style/quality linter for `SKILL.md` files. A stricter peer of `validate` that surfaces nine smells `validate` deliberately ignores — short or noun-phrase `description`, descriptions missing trigger language, empty `tags`, stale `version: 0.0.1` files (older than 7 days), missing `## When to use` / `## Examples` headings, `TODO` markers (error), `you should` / `always` second-person phrasing, and trailing whitespace. Exit 0 if only warnings, 1 on errors, 2 with `--strict`. `--json` emits machine-readable issues. Each rule is a tiny pure function so adding rules is a one-liner.

## [0.0.2] — 2026-05-23
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Scaffold, validate, and lint `SKILL.md` files for the agent ecosystem.

---

> **Status — v0.0.2, early days.** `init`, `validate`, `lint`, `pack`, and `install` work today. Registry, publish, and eval flows land in v0.1.
> **Status — v0.0.2, early days.** `init`, `validate`, `lint`, `pack`, `install`, and `update` work today. Registry, publish, and eval flows land in v0.1.

## Install

Expand Down Expand Up @@ -92,6 +92,20 @@ skillforge install https://example.com/code-review.skill

Refuses plaintext `http://` (skills execute on your machine), refuses zip-slip entries, refuses symlinks in archives, caps downloads at 64 MB, and validates the bundle's `SKILL.md` before writing a single file to disk. Pass `--force` to clear an existing install directory before extracting; pass `--dry-run` to validate and report what would happen without touching the filesystem.

### `skillforge update <path>`

Bump the `version:` field of a SKILL.md without hand-editing the frontmatter. Accepts either a file path or a directory containing a `SKILL.md`:

```bash
skillforge update ./code-review --bump patch
# ✓ ./code-review/SKILL.md: 0.1.0 → 0.1.1

skillforge update ./code-review/SKILL.md --new-version 1.0.0
# ✓ ./code-review/SKILL.md: 0.1.1 → 1.0.0
```

Pass exactly one of `--bump <patch|minor|major>` or `--new-version <semver>`. Pre-release tags (`-beta`, `-rc.1`, …) are dropped on any bump, matching `npm version`. A missing `version:` field is treated as `0.0.1` (the schema default) so a `patch` on a freshly-scaffolded skill produces `0.0.2`. The proposed frontmatter is validated against the schema before anything hits disk, and the write is line-surgical — body bytes, field order, and other YAML formatting are preserved byte-for-byte. Pass `--dry-run` to print the would-be new version without writing.

## Schema

```yaml
Expand Down
30 changes: 30 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
* lint <path> surface style/quality warnings on a SKILL.md
* pack <dir> bundle a skill directory into a .skill archive
* install <url> download a remote .skill into ~/.claude/skills/
* update <path> bump the version field of a SKILL.md
*/
import { cac } from "cac";
import kleur from "kleur";
import { initSkill } from "./init.js";
import { installSkill } from "./install.js";
import { computeExitCode, lintSkill } from "./lint.js";
import { packSkill } from "./pack.js";
import { type BumpKind, updateSkillVersion } from "./update.js";
import { validateSkill } from "./validate.js";

const VERSION = "0.0.2";
Expand Down Expand Up @@ -145,6 +147,34 @@ cli
}
});

cli
.command("update <path>", "Bump the version field of a SKILL.md")
.option("--bump <kind>", "Bump direction: patch, minor, or major")
.option("--new-version <semver>", "Set the version to an explicit semver string")
.option("--dry-run", "Report the would-be new version without writing")
.action(async (path: string, opts) => {
try {
if (opts.bump !== undefined && !["patch", "minor", "major"].includes(opts.bump)) {
throw new Error(`--bump must be one of patch, minor, major (got "${opts.bump}")`);
}
const result = await updateSkillVersion({
path,
bump: opts.bump as BumpKind | undefined,
newVersion: opts.newVersion,
dryRun: !!opts.dryRun,
});
const prefix = result.dryRun ? kleur.yellow("dry-run") : kleur.green("✓");
const suffix = result.dryRun ? " (nothing written)" : "";
process.stdout.write(
`${prefix} ${result.path}: ${result.oldVersion} → ${result.newVersion}${suffix}\n`,
);
process.exit(0);
} catch (err) {
process.stderr.write(`${kleur.red("error:")} ${(err as Error).message}\n`);
process.exit(1);
}
});

cli.help();
cli.version(VERSION);
cli.parse();
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ export {
type Severity,
} from "./lint.js";
export { SkillFrontmatterSchema, type SkillFrontmatter } from "./schema.js";
export {
type BumpKind,
bumpVersion,
type UpdateOptions,
type UpdateResult,
updateSkillVersion,
} from "./update.js";
export { validateSkill, type ValidateResult } from "./validate.js";
Loading
Loading