Back to Aionui

Module Migration — Skill Library

docs/backend-migration/modules/skill-library.md

1.9.26-dev-e9bbc4324.5 KB
Original Source

Module Migration — Skill Library

Status: frontend side complete (pending e2e validation) Frontend branch: feat/backend-migration-fe-skill-library Backend branch: feat/extension-skill-library (in aionui-backend) Pilot date: 2026-04-22

Endpoints migrated

IDMethodPathRenderer APIBackend commitNotes
E1GET/api/skillsipcBridge.fs.listAvailableSkills75ab3f1Added source: 'builtin' | 'custom' | 'extension' field
E2GET/api/skills/builtin-autoipcBridge.fs.listBuiltinAutoSkills95ab84cNet-new endpoint; scans <builtin_skills_dir>/_builtin/
E3POST/api/skills/builtin-ruleipcBridge.fs.readBuiltinRule5da1b87Returns empty string on missing file; traversal rejected
E4POST/api/skills/builtin-skillipcBridge.fs.readBuiltinSkill358c364Same graceful-degradation + traversal guard as E3
E5POST/api/skills/infoipcBridge.fs.readSkillInfoac1d2dcEmpty name in frontmatter → falls back to directory basename

All five endpoints sit behind auth middleware; unauthenticated requests get 403. Response envelope is ApiResponse<T>, auto-unwrapped by the renderer HTTP bridge.

Shape changes vs. prior TS baseline

None required at ipcBridge.ts level — the renderer declarations at src/common/adapter/ipcBridge.ts:301–329 already targeted the HTTP paths and matched the implemented DTOs:

  • E1: Array<{ name, description, location, isCustom, source: 'builtin' | 'custom' | 'extension' }>source was already declared on the renderer side; backend added it in commit 75ab3f1 to close the delta.
  • E2: Array<{ name, description }> — match.
  • E3 / E4: string (raw file content, empty on missing) — match.
  • E5: { name, description } — match.

The HTTP bridge (src/common/adapter/httpBridge.ts) auto-unwraps the ApiResponse<T>.data field, so renderer call sites see plain T (identical to the legacy ipcBridge return contract).

Renderer files touched

No production source changes required — the renderer had already migrated to HTTP-based ipcBridge declarations. Only test files needed adaptation to the new HTTP contract.

FileChangeCommit
tests/unit/SkillsHubSettings.dom.test.tsxUnwrap detectAndCountExternalSkills mock from legacy { success, data } to plain array9d27f3a7a
tests/unit/assistantHooks.dom.test.tsUnwrap getAvailableAgents, detectAndCountExternalSkills, addCustomExternalPath mocksab06d3a3b
tests/unit/fsBridge.skills.test.tsDeleted — covered removed TS src/process/bridge/fsBridge.ts handlers (gone in 5c4b010f5)2289b1e41

Smoke-test results (local, 2026-04-22)

Ran aionui-backend --local --port 25810 --data-dir /tmp/aionui-smoke-data (fresh TempDir) and exercised each endpoint with curl:

EndpointResponseExpected?
GET /api/skills{"success":true,"data":[]}✅ empty dir → empty list
GET /api/skills/builtin-auto{"success":true,"data":[]}✅ missing _builtin/ → empty list
POST /api/skills/builtin-rule{"success":true,"data":""} for missing file✅ graceful-degradation per spec E3
POST /api/skills/builtin-skill{"success":true,"data":""} for missing file✅ graceful-degradation per spec E4
POST /api/skills/info{"success":false,"error":"Not found: Skill not found: /tmp/nonexistent","code":"NOT_FOUND"}✅ missing path → 404 per spec E5

All five endpoints matched the contract documented in docs/api-spec/13-extension.md (backend repo, ## Skill Library section) and in the backend-dev handoff. No incident files needed.

Test impact

  • Target paths (tests/unit/assistantHooks.dom.test.ts, tests/unit/SkillsHubSettings.dom.test.tsx, tests/unit/initAgent.skills.test.ts, tests/unit/skillSuggestParser.test.ts, tests/unit/skillsMarket.test.ts, tests/unit/assistantPresets.i18n.test.ts, tests/unit/assistantUtils.test.ts): 106 passed / 0 failed post-fix.
  • Full suite baseline (feat/backend-migration): 103 failed / 4305 passed / 444 files.
  • Full suite on this branch: 78 failed / 4313 passed / 443 files.
  • Net delta: –25 failures (–1 file from deleting the stale fsBridge.skills.test.ts; –24 from fixing wrapped-mock patterns in two test files that are not themselves in pilot scope but block vi.mock resolution for everything that imports them).

The remaining 78 failures are pre-existing base-branch issues (other stale src/process/bridge/* tests, shellBridgeStandalone.test.ts, configMigration.test.ts, zero renderer/dom flakes) and are out of pilot scope.

Known caveats / follow-ups

  1. Extension-contributed skills are declared on the renderer contract (source: 'extension'), but the Rust backend's list_available_skills does not yet merge ExtensionRegistry::get_skills() into the response. Pilot scope: Extension is reserved. Follow-up in the main feat/backend-migration cycle once extension loading lands on the Rust side.

  2. Plan Step 3.1/3.2 discrepancy — bun run dev does not exist. The pilot plan says bun run dev; the actual script is bun start (which runs electron-vite dev). Plan text should be corrected in a follow-up plan revision. This did not block the pilot because contract smoke-testing was done via curl directly against the running backend binary; full Electron-level interactive exercise is deferred to the e2e-tester in Task 4.

  3. ~/.aionui/skills/ layout in dev. On the developer's machine only _builtin/ exists under the user skills dir — no top-level custom skills. This makes E1 return an empty array against real data (builtin skills live under the bundled app resources, not under ~/.aionui/skills/). Fine for the backend contract, but the renderer-level exercise of "see skills in SkillsHub" needs the packaged builtin-skills dir, not a dev-mode smoke test.

  4. Other skill-scoped endpoints (importSkill, deleteSkill, exportSkillWithSymlink, scanForSkills, detectAndCountExternalSkills, etc.) are NOT in pilot scope — they belong to modules 4 and 5 of the decomposition. Their test coverage is still exercised by the SkillsHubSettings.dom.test.tsx and assistantHooks.dom.test.ts files; those tests now use the correct HTTP-unwrapped mock shape and will continue to work when the underlying endpoints migrate.

References

  • Plan: docs/backend-migration/plans/2026-04-22-skill-library-pilot-plan.md (Task 3).
  • Spec: docs/backend-migration/specs/2026-04-22-backend-migration-team-pilot-design.md.
  • Backend handoff: aionui-backend/docs/backend-migration/handoffs/backend-dev-skill-library-2026-04-22.md.
  • Backend API spec: aionui-backend/docs/api-spec/13-extension.md (## Skill Library section).

Final pilot outcome (post-Phase-D)

Status: pilot closed successfully. Transport/migration layer CLEAN. Final e2e result: 22 PASS / 7 FAIL / 0 skip (29 total in tests/e2e/features/settings/skills/).

Failure classification

ClassCountTestsCategory
D0(5 cleared: TC-S-10, 14, 16, 09, 12)Transport/migration ✓ CLEAN
A1TC-S-25 (bulk import at N=20)Test-infra state-interaction / pollution
F1TC-S-17 (duplicate-path modal)Pre-existing TS contract gap (inherited)
B2TC-S-27, TC-S-28 (conditional sections)Test-authoring — fixture assumptions
C1TC-S-06 (no builtin skills in sandbox)Test-authoring — fixture assumptions
E2TC-S-08, TC-S-15 (matcher collision + state leak)Test-authoring — exact-match + cleanup

Transport/migration verdict: CLEAN. All pilot-scope endpoints (E1–E5) pass end-to-end; the backend source field fix on ExternalSkillSourceResponse (commit 3a86d58) closed the last transport-layer gap. None of the 7 remaining failures is a regression from the pilot's own work — they are either pre-existing TS baseline gaps that migration inherited (F), test-infra state confounds that the pilot surfaced but doesn't own (A), or test-authoring items that depend on fixture state the sandbox doesn't guarantee (B/C/E).

Commit SHAs by role

Backend (aionui-backend@feat/extension-skill-library):

RoleCommitSubject
Specb2e3c9fdocs(extension): draft Skill Library API spec for pilot migration
E175ab3f1feat(extension/skills): add source field to GET /api/skills
E295ab84cfeat(extension/skills): implement GET /api/skills/builtin-auto
E3 tests5da1b87test(extension/skills): HTTP tests for POST /api/skills/builtin-rule
E4 tests358c364test(extension/skills): HTTP tests for POST /api/skills/builtin-skill
E5 testsac1d2dctest(extension/skills): HTTP tests for POST /api/skills/info
Spec align686e855docs(extension): align Skill Library spec with implementation
Handoff38a216edocs(backend-migration): backend-dev handoff for skill-library pilot
Handoff +229b6e0docs(backend-migration): add scope breakdown to backend-dev handoff
Phase B fix3a86d58feat(extension/skills): add source field to ExternalSkillSourceResponse
Phase B doc274f8abdocs(backend-migration): append source-field fix to backend-dev handoff

Frontend (AionUi@feat/backend-migration-fe-skill-library):

RoleCommitSubject
Test fix 19d27f3a7atest(skills-hub): unwrap detectAndCountExternalSkills mock for HTTP bridge
Test fix 2ab06d3a3btest(assistant-hooks): unwrap ipcBridge mocks for HTTP bridge auto-unwrap
Test cull2289b1e41test(skills): remove stale fsBridge.skills.test.ts covering deleted TS handlers
Module rec5c92dbf58docs(backend-migration): record skill-library module migration
Handoff316f63bebdocs(backend-migration): frontend-dev handoff for skill-library pilot

Frontend E2E helper fix (AionUi@feat/backend-migration-e2e-skill-library):

RoleCommitSubject
Trace gatecfdec9655chore(e2e): gate trace retention behind E2E_TRACE env var
Helpers000676801test(e2e/helpers): migrate skills helpers from legacy IPC to HTTP bridge
PATH docaa8042fa3docs(e2e): note aionui-backend must be on PATH for tests
Handoff21cf93c6bdocs(backend-migration): frontend-dev handoff for e2e helper fix

E2E-tester (AionUi@feat/backend-migration-e2e-skill-library):

RoleCommitSubject
Report v1028a560cadocs(backend-migration): e2e report for skill-library pilot
Handoff v11e0c0b3b6docs(backend-migration): e2e-tester handoff for skill-library pilot
Handoff v1aee42e50d3docs(backend-migration): add recommended follow-up section to e2e-tester handoff
Rerun09036a925docs(backend-migration): append rerun results to skill-library e2e report
Rerun h/o497999516docs(backend-migration): update e2e-tester handoff with rerun outcome
Phase Bffa8852e0docs(backend-migration): append Phase B rerun results to e2e report and handoff
Phase D76294e7fddocs(backend-migration): Phase D trace findings and closure recommendation

Handoff files

All teammate handoffs are on the corresponding branches; paths relative to each repo root:

  • backend-dev: aionui-backend/docs/backend-migration/handoffs/backend-dev-skill-library-2026-04-22.md
  • frontend-dev (Task 3): docs/backend-migration/handoffs/frontend-dev-skill-library-2026-04-22.md
  • frontend-dev (Task 4-fix e2e helpers): docs/backend-migration/handoffs/frontend-dev-e2e-helper-fix-2026-04-22.md
  • e2e-tester: docs/backend-migration/handoffs/e2e-tester-skill-library-2026-04-22.md
  • coordinator (Task 5): to be written at closure; path will be docs/backend-migration/handoffs/coordinator-skill-library-2026-04-22.md.

E2E reports + post-pilot followups

  • E2E report (all phases): docs/backend-migration/e2e-reports/2026-04-22-skill-library.md. Contains the first-run 0/29 FAIL diagnosis, helper-fix rerun 17/12, Phase B clean-state 22/7, and Phase D trace findings + closure recommendation.
  • Post-pilot followup ticket list: docs/backend-migration/post-pilot/2026-04-23-skill-library-followups.md. Concrete P0/P1/P2 items for module-2 prerequisites and deferred work.

Built-in Skill Migration — 2026-04-23

Scope: move built-in skill resources from AionUi frontend (src/process/resources/skills/) to the Rust backend. Embed via include_dir!. Rename _builtin/auto-inject/. Frontend AcpSkillManager and gemini CLI wiring all route through HTTP; frontend never touches skill files.

Feature branches (no PRs raised per user instruction):

BranchRepoFinal SHA
feat/backend-migration-builtin-skillsAionUiff5290db5
feat/builtin-skillsaionui-backend04f1537

Endpoints added

MethodPathBehavior
POST/api/skills/materialize-for-agentWrite a conversation's skill bundle to {data_dir}/agent-skills/{conversationId}/, return absolute dir path. Flat layout: {target}/{name}/SKILL.md, auto-inject unconditional, opt-in overwrites on collision.
DELETE/api/skills/materialize-for-agent/{conversationId}Idempotent cleanup.

Endpoints modified (contract additions)

  • GET /api/skills/builtin-auto — response entries gain location field (relative path like "auto-inject/cron/SKILL.md").
  • GET /api/skillssource=builtin rows gain optional relativeLocation field (for HTTP body reads); location synthesizes an absolute-style path under {data_dir}/builtin-skills-view/ for the export-symlink flow.
  • POST /api/skills/builtin-skillfileName now accepts the auto-inject/ prefix.
  • All camelCase on the wire enforced via #[serde(rename_all = "camelCase")] on 21 of 22 skill.rs derive blocks (H1 hotfix).

Constant rename

BUILTIN_AUTO_SKILLS_SUBDIR: &str = "_builtin""auto-inject" in aionui-extension/constants.rs. All downstream callers updated.

Built-in skill corpus

  • Location at compile time: aionui-backend/crates/aionui-app/assets/builtin-skills/
  • 23 skills total (4 auto-inject + 19 opt-in)
  • Embedded into the binary via include_dir!. AIONUI_BUILTIN_SKILLS_PATH env var overrides with disk path (E2E/dev).

Invariants established

  • Frontend src/process/resources/skills/ deleted. grep -rnE '"_builtin"|/_builtin|_builtin/' src/ returns zero production hits.
  • Frontend code no longer reads skill files; all skill access is HTTP through ipcBridge.fs.*.
  • Gemini CLI gets a pre-materialized skill dir path from the backend; never scans for skill files itself.
  • Packaging: aionui-backend binary self-contained — no sibling assets/ directory required. Verified via release-binary smoke in a fresh tempdir.

Tests

SuiteCountLocation
Rust inline unit (aionui-extension::skill_service)+13 newcrates/aionui-extension/src/skill_service.rs
Rust HTTP integration+14 newcrates/aionui-app/tests/skills_builtin_e2e.rs
Rust wire-shape regression (camelCase + snake-case rejection)+multiple per typecrates/aionui-api-types/src/skill.rs
Frontend Vitest+11 newtests/unit/acpSkillManager.test.ts, tests/unit/initAgent.materialize.test.ts
Playwright E2E8 scenariostests/e2e/features/builtin-skill-migration/builtin-skill-migration.e2e.ts

Hotfix in flight

  • H1 (04f1537): T3 run-1 found 3 of 8 scenarios failing because 19 of 22 public skill.rs derive blocks lacked #[serde(rename_all = "camelCase")]. Backend-dev audited the whole file (3 → 21 rename_all attrs) + added regression-guard tests. T3 run-2 passed 8/8 in 13.5s.

Lessons (see playbook)

  • stat -f on a symlink reports link-mtime not target-mtime; use stat -L or readlink for ~/.cargo/bin/aionui-backend freshness checks.
  • camelCase on the wire is a project-wide invariant; a linter would prevent this class of reactive hotfixes.
  • Packaging smoke (release binary in fresh tempdir) is a worthwhile standalone step for any pilot touching asset delivery.

Snake-Case Wire Realignment — 2026-04-24

Scope: revert H1's directional mistake. The builtin-skill pilot (2026-04-23) landed H1 (04f1537) which added 21 #[serde(rename_all = "camelCase")] attributes to skill.rs. origin/main at dae96f8 had already established snake_case as the project-wide wire convention; H1 created a camelCase island. Subsequent merges didn't clean it up. This realignment undoes H1 and flips every pilot-introduced field to snake_case on both sides.

Feature branches (no PRs raised per user instruction):

BranchRepoFinal SHA
feat/backend-migration-builtin-skillsAionUi64bddde5c
feat/builtin-skillsaionui-backend326e228

Changes

  • crates/aionui-api-types/src/skill.rs: 21 → 0 rename_all = "camelCase" attrs; 18 unit tests flipped to snake_case assertions; 2 test names updated (_camel_case_snake_case, _serializes_camel_serializes_snake).
  • crates/aionui-app/tests/skills_builtin_e2e.rs: 13 JSON body keys flipped.
  • crates/aionui-app/tests/assistants_e2e.rs: 14 assistantId body keys flipped to assistant_id (the rule/read path tunnels through skill.rs types).
  • Frontend ipcBridge.ts: listAvailableSkills row (relativeLocationrelative_location, isCustomis_custom); materializeSkillsForAgent req (conversationIdconversation_id, enabledSkillsenabled_skills); resp (dirPathdir_path).
  • Frontend AcpSkillManager: field access sites.
  • Frontend initAgent.ts: destructure with rename ({ dir_path: dirPath }) preserves JS-style locals while sending snake_case on wire.
  • Frontend incidental: SkillInfo type narrowing in SkillsHubSettings.tsx + AssistantSettings/types.ts (TSC required).
  • Frontend E2E: 5 classes of camelCase residue flipped in the Playwright spec.
  • Frontend Vitest: acpSkillManager.test.ts, initAgent.materialize.test.ts, SkillsHubSettings.dom.test.tsx updated.

Wire format (final)

EndpointRequest bodyResponse
GET /api/skills/builtin-auto[{name, description, location}] (all snake lowercase)
POST /api/skills/builtin-skill{file_name}string
GET /api/skills[{name, description, location, relative_location?, is_custom, source}]
POST /api/skills/materialize-for-agent{conversation_id, enabled_skills}{dir_path}
DELETE /api/skills/materialize-for-agent/{id}path param only{success: true}

Tests

SuiteResult
cargo test -p aionui-api-types421/421 (18 flipped tests included)
cargo test --test skills_builtin_e2e14/14
cargo test --test assistants_e2e44/44 (regression clean)
cargo clippy --workspace -- -D warningssame baseline (pre-existing debt)
bun run test --runbaseline unchanged
bunx tsc --noEmitclean
Playwright builtin-skill-migration8/8 on run 3 (13.1s, 0 flakes)
Packaging smoke (release binary + tempdir)all green

Lesson

The H1 mis-direction cost one full hotfix cycle + this realignment cycle. Root cause: I treated the T3 run-1 contract mismatch as "fix backend to match frontend" without checking git log crates/aionui-api-types/ for the project-wide convention. dae96f8 was a month-old blanket refactor that would have told me snake_case was the right direction.

New rule in the playbook: any wire-format question must first check api-types commit history for a blanket convention refactor before proposing a fix direction.