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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ version tags such as `v0.3.0`.

## Unreleased

- No unreleased changes.
- Add one-fetch GitHub repository collection preview/apply with explicit child
selection, stale ref/tree validation, and per-skill managed provenance. This
Phase C change targets v0.9.0 and remains unreleased pending v0.9.0 release qualification; collection-level update/rollback remains planned.
- Update collection roadmap/status wording to distinguish the shipped v0.8.0
local A+B work from the v0.9.0-targeted, unreleased Phase C implementation.

## 0.8.0

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ English | [简体中文](README.zh-CN.md)

SkillBox is a local-first macOS desktop app with a Rust core and CLI for managing `SKILL.md`-based skill and capability packages without treating any supported agent runtime as the source of truth.

Current release: `v0.8.0`. SkillBox is useful today for local skill management, but it is still early software. Keep backups of important skills, and review each filesystem change before applying it.
Current release: `v0.8.0`. SkillBox is useful today for local skill management, but it is still early software. Keep backups of important skills, and review each filesystem change before applying it. GitHub multi-skill collection preview/apply is implemented for the upcoming v0.9.0 qualification and remains unreleased.

## Promo Video

Expand All @@ -32,7 +32,7 @@ A 30-second overview of SkillBox: runtime-aware workspaces, review-before-write
- **Reviewed Git changes in both directions.** Inspect local user-skill diffs before commit/push. The shipped v0.7 line adds an explicit Check remote -> Review incoming changes -> Apply fast-forward flow for safe inbound updates; diverged history remains a normal Git conflict to resolve outside SkillBox.
- **Evidence-aware Calls, references, and operation history.** Count locally confirmed executions plus defensible structured invocations as Calls, keep lower-signal history references separate, and explain coverage without storing full chat transcripts.
- **Safe storage and deployment defaults.** Use ordered SQLite migrations, recovery backups, integrity checks, and ownership-checked symlinks instead of silently overwriting runtime content.
- **Git-backed local collections.** Import Review groups skills from the same local Git worktree into one repository card while keeping each child independently selectable, deployable, and usage-tracked. GitHub multi-skill fetch and collection-level update/rollback remain future work.
- **Git-backed local collections.** Import Review groups skills from the same local Git worktree into one repository card while keeping each child independently selectable, deployable, and usage-tracked. GitHub multi-skill preview/apply is implemented for v0.9.0 and remains unreleased pending v0.9.0 release qualification; collection-level update/rollback remains future work.
- **Installed-source provenance.** Copied skills with valid v3 installer lockfile entries can appear under one normalized GitHub source collection even without a local Git worktree. This is display-only provenance: it does not invent a branch/HEAD or enable updates, and each child keeps the normal reviewed import path.
- **Compatibility before deployment.** Rust-owned runtime profiles identify each workspace and report preserved frontmatter warnings or hard blockers before a confirmed symlink deployment.
- **Signed macOS distribution.** Install a notarized DMG or Homebrew cask and apply signed app updates only after confirmation.
Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

SkillBox 是一个 local-first 的 macOS 桌面应用,带 Rust core/CLI,用来管理基于 `SKILL.md` 的 skill 与能力包,同时避免把任一受支持的 agent runtime 当作唯一真相源。

当前版本:`v0.8.0`。SkillBox 现在已经可以用于本地 skill 管理,但仍是早期软件。重要 skills 请保留备份,并在应用每一次文件系统变更前先 review。
当前版本:`v0.8.0`。SkillBox 现在已经可以用于本地 skill 管理,但仍是早期软件。重要 skills 请保留备份,并在应用每一次文件系统变更前先 review。GitHub 多 skill collection preview/apply 已按 v0.9.0 实现并等待 release qualification,尚未发布。

## 宣传视频

Expand All @@ -32,7 +32,7 @@ SkillBox 是一个 local-first 的 macOS 桌面应用,带 Rust core/CLI,用
- **双向 Git 变更都先审查。** 本地 user-skill diff 会在 commit/push 前 review。已发布的 v0.7 增加显式的 Check remote -> Review incoming changes -> Apply fast-forward 入站流程;远端历史分叉仍在 SkillBox 外按正常 Git 冲突处理。
- **按证据分类的 Calls、引用与操作历史。** Calls 只统计本机 confirmed execution 与可辩护的 structured invocation,低信号 history references 单独展示,并且不保存完整聊天 transcript。
- **安全的存储与部署默认值。** 使用顺序 SQLite migrations、恢复备份、完整性检查和 ownership-checked symlink,不静默覆盖 runtime 内容。
- **Git-backed 本地 Skill Collections。** Import Review 会把同一 Git worktree 中的 skills 聚合为一个仓库卡片,同时保留每个子 skill 的独立选择、部署和 usage。GitHub 多 skill fetch 与 collection 级更新/回滚仍是后续工作。
- **Git-backed 本地 Skill Collections。** Import Review 会把同一 Git worktree 中的 skills 聚合为一个仓库卡片,同时保留每个子 skill 的独立选择、部署和 usage。GitHub 多 skill preview/apply 已按 v0.9.0 实现,尚未发布,待 v0.9.0 release qualification;collection 级更新/回滚仍是后续工作。
- **已安装来源 provenance。** 没有本地 Git worktree、但拥有有效 v3 installer lockfile 条目的复制 skill,也可以按规范化 GitHub source 聚合展示。这只是来源展示,不会伪造 branch/HEAD 或开放更新;每个 child 仍走原有的逐 skill review/import 流程。
- **部署前检查 compatibility。** Rust-owned runtime profiles 标识 workspace,并在确认 symlink 部署前报告会原样保留的 frontmatter warnings 或 hard blockers。
- **签名的 macOS 分发。** 可安装已公证 DMG 或 Homebrew cask,app 更新也只在用户确认后应用。
Expand Down
32 changes: 32 additions & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,36 @@ async fn apply_import_collection(
.map_err(|error| format!("Collection import task failed: {error}"))?
}

#[tauri::command]
async fn preview_github_skill_collection(
request: skillbox_core::PreviewGithubSkillCollectionRequest,
) -> Result<Value, String> {
tauri::async_runtime::spawn_blocking(move || {
let result = skillbox_core::preview_github_skill_collection_result(
request,
skillbox_core::default_managed_root(),
)?;
serde_json::to_value(result).map_err(|error| error.to_string())
})
.await
.map_err(|error| format!("GitHub collection preview task failed: {error}"))?
}

#[tauri::command]
async fn apply_github_skill_collection(
request: skillbox_core::GithubSkillCollectionApplyRequest,
) -> Result<Value, String> {
tauri::async_runtime::spawn_blocking(move || {
let result = skillbox_core::apply_github_skill_collection(
request,
skillbox_core::default_managed_root(),
)?;
serde_json::to_value(result).map_err(|error| error.to_string())
})
.await
.map_err(|error| format!("GitHub collection apply task failed: {error}"))?
}

#[tauri::command]
async fn import_candidates(items: Vec<skillbox_core::ImportRequestItem>) -> Result<Value, String> {
tauri::async_runtime::spawn_blocking(move || {
Expand Down Expand Up @@ -1157,6 +1187,8 @@ pub fn run() {
scan_workspace_import_candidates,
list_skill_collections,
apply_import_collection,
preview_github_skill_collection,
apply_github_skill_collection,
import_candidates,
list_import_records,
revert_import,
Expand Down
97 changes: 97 additions & 0 deletions apps/desktop/src/App.import-candidates.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
collectionSkillCountLabel,
importCandidateGroupLocationCount,
importCandidateGroupTabs,
normalizeGithubSkillCollectionPreviewResult,
normalizeImportCollections,
normalizeImportCandidateGroup,
normalizeImportCandidateGroups,
Expand Down Expand Up @@ -67,6 +68,7 @@ import {
import {
browserImportScanOptions,
createImportScanRequestController,
createRemoteImportRequestController,
importScanCommandArgs,
importScanProgressDetail,
importScanProgressLabel,
Expand Down Expand Up @@ -135,6 +137,22 @@ test('closing and reopening Import Review isolates late scan A from active scan
assert.equal(controller.begin(), 4);
});

test('remote preview controller ignores duplicate clicks and late close/reopen results', () => {
const controller = createRemoteImportRequestController();
const first = controller.begin();
assert.equal(first, 1);
assert.equal(controller.begin(), null);
controller.invalidate();
const second = controller.begin();
assert.equal(second, 3);
assert.equal(controller.isCurrent(first), false);
assert.equal(controller.isCurrent(second), true);
controller.finish(second);
assert.equal(controller.isCurrent(second), true);
controller.invalidate();
assert.equal(controller.isCurrent(second), false);
});

test('normalizes backend is_selected false without selecting importable candidate', () => {
const candidate = normalizeImportCandidate({
name: 'system',
Expand Down Expand Up @@ -415,6 +433,8 @@ test('normalizes Git-backed collection children and submits one selected child r
const requests = selectedImportCollectionRequests(groups, collections);
assert.deepEqual(requests, [{
collectionId: 'collection-demo',
sourceKind: 'git_worktree',
sourceUrl: '',
worktreeRoot: '/Users/example/skills-repo',
previewId: 'preview-demo',
selections: [{
Expand All @@ -426,6 +446,83 @@ test('normalizes Git-backed collection children and submits one selected child r
}]);
});

test('normalizes a GitHub collection without inventing a local worktree root', () => {
const collections = normalizeImportCollections([{
id: 'github-collection-demo',
source_kind: 'github_remote',
source_url: 'https://github.com/acme/skills',
requested_reference: 'main',
reviewed_head_sha: '1234567890abcdef',
children: [{
id: 'child-demo',
group_id: 'skill-demo',
variant_id: 'variant-demo',
name: 'demo',
relative_path: 'skills/demo',
import_status: 'importable',
selected_type: 'remote',
is_selected: true,
locations: []
}]
}]);
const groups = normalizeImportCandidateGroups([{
id: 'skill-demo',
name: 'demo',
selected_variant_id: 'variant-demo',
variants: [{
id: 'variant-demo',
candidate: { name: 'demo', import_status: 'importable', is_selected: true },
selected_type: 'remote',
locations: []
}]
}]);

assert.equal(collections[0].sourceKind, 'github_remote');
assert.equal(collections[0].canonicalWorktreeRoot, '');
assert.deepEqual(selectedImportCollectionRequests(groups, collections), [{
collectionId: 'github-collection-demo',
sourceKind: 'github_remote',
sourceUrl: 'https://github.com/acme/skills',
worktreeRoot: '',
previewId: '',
selections: [{
relativePath: 'skills/demo',
groupId: 'skill-demo',
variantId: 'variant-demo',
skillType: 'remote'
}]
}]);
});

test('routes structured GitHub collection preview outcomes without parsing human errors', () => {
const collection = { id: 'collection-demo' };
assert.deepEqual(
normalizeGithubSkillCollectionPreviewResult({
kind: 'collection',
preview: collection
}),
{ kind: 'collection', preview: collection }
);
assert.deepEqual(
normalizeGithubSkillCollectionPreviewResult({
kind: 'single_skill',
message: 'Use the single-skill preview.'
}),
{ kind: 'single_skill', message: 'Use the single-skill preview.' }
);
assert.deepEqual(
normalizeGithubSkillCollectionPreviewResult({
kind: 'explicit_reference_required',
message: 'Use /tree/<ref>.'
}),
{ kind: 'explicit_reference_required', message: 'Use /tree/<ref>.' }
);
assert.throws(
() => normalizeGithubSkillCollectionPreviewResult({ error: 'points to one skill' }),
/invalid result/
);
});

test('installed-source collections stay on per-skill apply and suppress imported type review', () => {
const collections = normalizeImportCollections([{
id: 'installed-source-dbs',
Expand Down
Loading