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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

---

## [1.5.1] - 2026-11-10

### Added
- **Deterministic Project Update & Lockfile Sync (`#202`, `#203`):** Added `updateInstalledSkills()` in [`src/sync/adapters.ts`](./src/sync/adapters.ts) and the `skillops update [name...]` (aliases: `sync`, `upgrade`) CLI command in [`src/cli.ts`](./src/cli.ts) to refresh installed Skills and Subagents across all active AI harnesses to the latest catalog version while preserving local user edits via SHA-256 drift detection (with `--dry-run` and `--force` support).
- **Project Status & SHA-256 Drift Diagnostics (`#200`, `#201`):** Added `inspectProjectStatus()` and `formatProjectStatusReport()` in [`src/sync/adapters.ts`](./src/sync/adapters.ts) and the `skillops status` (aliases: `list`, `ls`) CLI command in [`src/cli.ts`](./src/cli.ts) with 5-state SHA-256 artifact classification (`= HEALTHY`, `~ OUTDATED`, `! DRIFT`, `? MISSING`, `+ UNTRACK`) and `table`, `json`, and `markdown` outputs.
- **Multilingual Documentation (`PT-BR` & `ES`):** Added full Portuguese ([`docs/README.pt-BR.md`](./docs/README.pt-BR.md)) and Spanish ([`docs/README.es.md`](./docs/README.es.md)) translations linked from the header of [`README.md`](./README.md).
- **5-Pillar User Trust Model & Security Documentation (`#193`):** Documented the 5-Pillar Supply-Chain Security & User Trust Architecture (`CODEOWNERS` curated registry, `INV-SKILL-06` AST + CodeQL SAST script gate, Anti-Trojan Unicode/boundary guards, Sigstore NPM `--provenance`, and `--dry-run` + `.skillops-lock.json` SHA-256 verification) in [`README.md`](./README.md) and [`.github/SECURITY.md`](./.github/SECURITY.md).
- **`INV-SKILL-06` Security Invariant & Mandatory PR Security Review Checklist (`#192`):** Codified `INV-SKILL-06` (*Zero-Network, Trojan-Free & Boundary-Safe Security Contract*) in [`SKILL_CONTRACT.md`](./SKILL_CONTRACT.md) and added a mandatory supply-chain security review checklist in [`.github/PULL_REQUEST_TEMPLATE.md`](./.github/PULL_REQUEST_TEMPLATE.md) and [`CONTRIBUTING.md`](./CONTRIBUTING.md).
- **Anti-Trojan Unicode, Symlink, Binary & Path Containment Guards (`#191`):** Extended [`src/auditor/security.ts`](./src/auditor/security.ts), [`src/auditor/parser.ts`](./src/auditor/parser.ts), and [`src/sync/adapters.ts`](./src/sync/adapters.ts) (`resolveSafeRepoTargetPath`) to block Trojan-Source (CVE-2021-42574) bidirectional control characters, LLM invisible Unicode Tag smuggling (`U+E0000..U+E007F`), zero-width script identifier spoofing, symbolic links, opaque compiled binary/archive payloads (`.exe`, `.dll`, `.so`, `.dylib`, `.bin`, `.wasm`, `.pyc`, `.zip`, `.tar.gz`), and repository boundary path traversal.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>

<p>
<a href="https://www.npmjs.com/package/skillops-cli"><img src="https://img.shields.io/badge/npm-v1.3.10-dafa9c?style=flat-square&logo=npm&logoColor=0f0f0f&labelColor=0f0f0f" alt="npm version"></a>
<a href="https://www.npmjs.com/package/skillops-cli"><img src="https://img.shields.io/badge/npm-v1.5.1-dafa9c?style=flat-square&logo=npm&logoColor=0f0f0f&labelColor=0f0f0f" alt="npm version"></a>
<a href="https://github.com/OdevMatheus/SkillOps/actions/workflows/ai-ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/OdevMatheus/SkillOps/ai-ci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=dafa9c&labelColor=0f0f0f&color=1a1a1a&label=CI" alt="CI"></a>
<a href="https://github.com/OdevMatheus/SkillOps/actions/workflows/codeql.yml"><img src="https://img.shields.io/github/actions/workflow/status/OdevMatheus/SkillOps/codeql.yml?branch=main&style=flat-square&logo=github&logoColor=dafa9c&labelColor=0f0f0f&color=1a1a1a&label=CodeQL%20SAST" alt="CodeQL SAST"></a>
<a href="#supported-ai-ides--clis-28"><img src="https://img.shields.io/badge/AI%20IDEs%20%26%20CLIs-28-dafa9c?style=flat-square&labelColor=0f0f0f" alt="28 AI IDEs & CLIs"></a>
Expand Down Expand Up @@ -50,6 +50,8 @@ Install globally (`npm i -g skillops-cli`) or run directly with `npx skillops-cl
| :--- | :--- |
| `skillops add` | Open the interactive TUI (select target AI IDEs → select Skills & Subagents) |
| `skillops add <name...> -t cursor,claude` | Non-interactive install into specific targets (`-t all` for all 28 tools) |
| `skillops status` (`list`, `ls`) | Inspect installed Skills, Subagents, and SHA-256 health across AI harnesses |
| `skillops update [name...]` (`sync`, `upgrade`) | Refresh installed modules to the latest catalog version while preserving local edits |
| `skillops remove [name...]` | Interactively or directly uninstall managed artifacts and update `.skillops-lock.json` |

> **Flags:** `--all` selects every item in the catalog, `--dry-run` previews target file paths without writing to disk, and `--force` overwrites locally modified files.
Expand Down
4 changes: 3 additions & 1 deletion docs/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>

<p>
<a href="https://www.npmjs.com/package/skillops-cli"><img src="https://img.shields.io/badge/npm-v1.3.10-dafa9c?style=flat-square&logo=npm&logoColor=0f0f0f&labelColor=0f0f0f" alt="npm version"></a>
<a href="https://www.npmjs.com/package/skillops-cli"><img src="https://img.shields.io/badge/npm-v1.5.1-dafa9c?style=flat-square&logo=npm&logoColor=0f0f0f&labelColor=0f0f0f" alt="npm version"></a>
<a href="https://github.com/OdevMatheus/SkillOps/actions/workflows/ai-ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/OdevMatheus/SkillOps/ai-ci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=dafa9c&labelColor=0f0f0f&color=1a1a1a&label=CI" alt="CI"></a>
<a href="https://github.com/OdevMatheus/SkillOps/actions/workflows/codeql.yml"><img src="https://img.shields.io/github/actions/workflow/status/OdevMatheus/SkillOps/codeql.yml?branch=main&style=flat-square&logo=github&logoColor=dafa9c&labelColor=0f0f0f&color=1a1a1a&label=CodeQL%20SAST" alt="CodeQL SAST"></a>
<a href="#ides-y-clis-de-ia-compatibles-28"><img src="https://img.shields.io/badge/IDEs%20%26%20CLIs%20de%20IA-28-dafa9c?style=flat-square&labelColor=0f0f0f" alt="28 IDEs & CLIs de IA"></a>
Expand Down Expand Up @@ -50,6 +50,8 @@ Instálalo globalmente (`npm i -g skillops-cli`) o ejecútalo directamente con `
| :--- | :--- |
| `skillops add` | Abre la TUI interactiva (selecciona IDEs de IA de destino → selecciona Skills y Subagents) |
| `skillops add <name...> -t cursor,claude` | Instalación no interactiva en destinos específicos (`-t all` para las 28 herramientas) |
| `skillops status` (`list`, `ls`) | Inspecciona las Skills y Subagents instalados y la integridad SHA-256 en las IDEs |
| `skillops update [name...]` (`sync`, `upgrade`) | Actualiza los módulos instalados a la versión más reciente del catálogo preservando ediciones locales |
| `skillops remove [name...]` | Desinstala artefactos gestionados de forma interactiva o directa y actualiza `.skillops-lock.json` |

> **Flags:** `--all` selecciona todos los elementos del catálogo, `--dry-run` previsualiza las rutas de destino sin escribir en disco y `--force` sobrescribe archivos modificados localmente.
Expand Down
4 changes: 3 additions & 1 deletion docs/README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>

<p>
<a href="https://www.npmjs.com/package/skillops-cli"><img src="https://img.shields.io/badge/npm-v1.3.10-dafa9c?style=flat-square&logo=npm&logoColor=0f0f0f&labelColor=0f0f0f" alt="npm version"></a>
<a href="https://www.npmjs.com/package/skillops-cli"><img src="https://img.shields.io/badge/npm-v1.5.1-dafa9c?style=flat-square&logo=npm&logoColor=0f0f0f&labelColor=0f0f0f" alt="npm version"></a>
<a href="https://github.com/OdevMatheus/SkillOps/actions/workflows/ai-ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/OdevMatheus/SkillOps/ai-ci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=dafa9c&labelColor=0f0f0f&color=1a1a1a&label=CI" alt="CI"></a>
<a href="https://github.com/OdevMatheus/SkillOps/actions/workflows/codeql.yml"><img src="https://img.shields.io/github/actions/workflow/status/OdevMatheus/SkillOps/codeql.yml?branch=main&style=flat-square&logo=github&logoColor=dafa9c&labelColor=0f0f0f&color=1a1a1a&label=CodeQL%20SAST" alt="CodeQL SAST"></a>
<a href="#ides-e-clis-de-ia-suportados-28"><img src="https://img.shields.io/badge/IDEs%20%26%20CLIs%20de%20IA-28-dafa9c?style=flat-square&labelColor=0f0f0f" alt="28 IDEs & CLIs de IA"></a>
Expand Down Expand Up @@ -50,6 +50,8 @@ Instale globalmente (`npm i -g skillops-cli`) ou execute diretamente com `npx sk
| :--- | :--- |
| `skillops add` | Abre o TUI interativo (selecione as IDEs de IA de destino → selecione Skills e Subagents) |
| `skillops add <name...> -t cursor,claude` | Instalação não interativa em alvos específicos (`-t all` para todas as 28 ferramentas) |
| `skillops status` (`list`, `ls`) | Inspeciona Skills e Subagents instalados e a integridade SHA-256 dos arquivos nas IDEs |
| `skillops update [name...]` (`sync`, `upgrade`) | Atualiza módulos instalados para a versão mais recente do catálogo preservando edições locais |
| `skillops remove [name...]` | Desinstala artefatos gerenciados de forma interativa ou direta e atualiza o `.skillops-lock.json` |

> **Flags:** `--all` seleciona todos os itens do catálogo, `--dry-run` pré-visualiza os caminhos de destino sem gravar no disco e `--force` sobrescreve arquivos modificados localmente.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skillops-cli",
"version": "1.3.10",
"version": "1.5.1",
"description": "Install and manage AI coding Skills (SKILL.md) and Subagents (SUBAGENT.md) across 28 AI IDEs and CLIs (Cursor, Claude Code, Copilot, Antigravity, Windsurf, Codex, Gemini CLI & more).",
"type": "module",
"license": "MIT",
Expand Down
42 changes: 42 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
formatSyncReport,
inspectProjectStatus,
removeSkillOrSubagent,
updateInstalledSkills,
} from "./sync/index.js";
import {
findCommandHelpSpec,
Expand Down Expand Up @@ -692,6 +693,47 @@ export function runCli(argv: readonly string[]): number {
}
}

if (command === "update" || command === "sync" || command === "upgrade") {
const positionals = getPositionalArgs(args);
const repoPath = resolveTargetRepoPath(args);
const cfgRes = loadSkillOpsConfig(repoPath);
if (cfgRes.error) {
process.stderr.write(
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}\n`,
);
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
}
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
const rawTargetsArg = resolveTargetsArg(args);
const dryRun = args.includes("--dry-run");
const force = args.includes("--force");
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
const format: "table" | "json" | "markdown" =
rawFormat === "json"
? "json"
: rawFormat === "markdown" || rawFormat === "md"
? "markdown"
: "table";

try {
const targets = rawTargetsArg
? resolveRuntimeTargets(rawTargetsArg, repoPath)
: undefined;
const report = updateInstalledSkills(repoPath, catalogDir, {
targets,
names: positionals.length > 0 ? positionals : undefined,
dryRun,
force,
});
process.stdout.write(formatSyncReport(report, format));
return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
process.stderr.write(`Error: ${msg}\n`);
return ExitCode.ERROR;
}
}

const suggestion = suggestCommand(command);
const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
process.stderr.write(
Expand Down
2 changes: 1 addition & 1 deletion src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import fs from "node:fs";
import YAML from "yaml";
import { z } from "zod";

export const SKILLOPS_VERSION = "1.3.10";
export const SKILLOPS_VERSION = "1.5.1";

const SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
Expand Down
50 changes: 49 additions & 1 deletion src/ui/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,49 @@ export const COMMAND_HELP_SPECS: readonly CommandHelpSpec[] = [
},
],
},
{
name: "update",
aliases: ["sync", "upgrade"],
summary: "Update installed Skills & Subagents while preserving local edits",
usage: "npx skillops-cli update [name...] [options]",
options: [
{
flags: "-p, --project <dir>",
description: "Target repository directory (default: current directory)",
},
{
flags: "-g, --global",
description: "Update user home directory (~) instead of current project",
},
{
flags: "-t, --target <list>",
description: "Restrict update to specific AI harnesses",
},
{
flags: "--dry-run",
description: "Preview planned updates without modifying disk",
},
{
flags: "--force",
description:
"Overwrite locally drifted files with latest catalog version",
},
{
flags: "-f, --format <table|json|md>",
description: "Output format (default: table)",
},
],
examples: [
{
command: "skillops update",
description: "Update all installed Skills & Subagents in project",
},
{
command: "skillops update mcp-architect --force",
description: "Force-refresh mcp-architect overwriting local edits",
},
],
},
];

/**
Expand Down Expand Up @@ -203,6 +246,11 @@ export function renderGlobalHelp(
color: CHARM_PALETTE.cyan,
desc: "Inspect installed Skills, Agents & SHA-256 health",
},
{
cmd: "update [name...]",
color: CHARM_PALETTE.amber,
desc: "Update installed Skills & Agents (preserves local edits)",
},
{
cmd: "remove [name...]",
color: CHARM_PALETTE.hotPink,
Expand All @@ -217,7 +265,7 @@ export function renderGlobalHelp(
)
.join("\n");

const usageLine = `${bar} ${fgHex(CHARM_PALETTE.lavender, style.bold("Usage:", c), c)} ${style.bold(style.green("skillops", c), c)} ${fgHex(CHARM_PALETTE.cyan, "<add|status|remove>", c)} ${style.dim("[options]", c)}`;
const usageLine = `${bar} ${fgHex(CHARM_PALETTE.lavender, style.bold("Usage:", c), c)} ${style.bold(style.green("skillops", c), c)} ${fgHex(CHARM_PALETTE.cyan, "<add|status|update|remove>", c)} ${style.dim("[options]", c)}`;

return [
banner,
Expand Down
3 changes: 3 additions & 0 deletions src/ui/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,9 @@ export const DEFAULT_VALID_COMMANDS: readonly string[] = [
"status",
"list",
"ls",
"update",
"sync",
"upgrade",
"remove",
"uninstall",
"rm",
Expand Down
3 changes: 2 additions & 1 deletion tests/npx_core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ describe("SkillOps Core Models & Subagent Contracts", () => {
expect(runCli(["node", "cli.js", "--help"])).toBe(ExitCode.SUCCESS);
expect(runCli(["node", "cli.js", "help", "add"])).toBe(ExitCode.SUCCESS);
expect(runCli(["node", "cli.js", "help", "status"])).toBe(ExitCode.SUCCESS);
expect(runCli(["node", "cli.js", "help", "update"])).toBe(ExitCode.SUCCESS);
expect(runCli(["node", "cli.js", "help", "remove"])).toBe(ExitCode.SUCCESS);
expect(SKILLOPS_VERSION).toBe("1.3.10");
expect(SKILLOPS_VERSION).toBe("1.5.1");
});

it("validates Top-28 AI Coding Agent registry, auto-detection matrix, and target resolution", () => {
Expand Down
49 changes: 49 additions & 0 deletions tests/router_sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,55 @@ describe("SkillOps Multi-Runtime Add & Remove Engine", () => {
names: ["non-existent-module"],
}),
).toThrow(/No installed artifacts found for 'non-existent-module'/);

// 9. CLI invocation for 'skillops update', 'skillops sync', and 'skillops upgrade' (Issue #203)
fs.appendFileSync(
catalogSkillMd,
"\n<!-- upstream v3 cli update -->\n",
"utf-8",
);
fs.appendFileSync(cursorMdc, "\n<!-- local user edit v2 -->\n", "utf-8");

expect(
runCli([
"node",
"cli.js",
"update",
"mcp-architect",
"--repo",
tmpRepo,
"--catalog",
tmpCatalog,
]),
).toBe(ExitCode.ERROR);

expect(
runCli([
"node",
"cli.js",
"sync",
"mcp-architect",
"--repo",
tmpRepo,
"--catalog",
tmpCatalog,
"--force",
]),
).toBe(ExitCode.SUCCESS);

expect(
runCli([
"node",
"cli.js",
"upgrade",
"--repo",
tmpRepo,
"--catalog",
tmpCatalog,
"-f",
"json",
]),
).toBe(ExitCode.SUCCESS);
} finally {
fs.rmSync(tmpRepo, { recursive: true, force: true });
fs.rmSync(tmpCatalog, { recursive: true, force: true });
Expand Down
8 changes: 4 additions & 4 deletions tests/tui_terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ describe("Zero-Dependency ANSI TUI Aesthetic Engine", () => {

it("renders rounded Unicode panels, clean tree header banners, and multi-column aligned tables", () => {
const banner = renderHeaderBanner("Framework & Library", {
version: "1.3.10",
version: "1.5.1",
colorEnabled: false,
});
expect(banner).toContain("◆ SkillOps v1.3.10");
expect(banner).toContain("◆ SkillOps v1.5.1");
expect(banner).toContain("Framework & Library");
expect(banner).not.toContain("⚡");

Expand Down Expand Up @@ -117,7 +117,7 @@ describe("Zero-Dependency ANSI TUI Aesthetic Engine", () => {
expect(visibleLength(line)).toBeLessThanOrEqual(78);
}

const helpOut = renderGlobalHelp("1.3.10", false);
const helpOut = renderGlobalHelp("1.5.1", false);
expect(helpOut).not.toMatch(/(?:🚀|📚|🛠️|⚙️|🌐|💡|⚡|📦|🔍)/u);
expect(helpOut).toContain("add [name...]");
expect(helpOut).toContain("remove [name...]");
Expand Down Expand Up @@ -466,7 +466,7 @@ describe("Zero-Dependency ANSI TUI Aesthetic Engine", () => {
true,
);
expect(stripAnsi(syncReportOut)).toContain(
"◆ SkillOps v1.3.10 — Skills & Subagents Installed",
"◆ SkillOps v1.5.1 — Skills & Subagents Installed",
);
expect(stripAnsi(syncReportOut)).toContain("+ CREATED");
expect(stripAnsi(syncReportOut)).toContain("(cursor)");
Expand Down
Loading