feat(skills): ship professional story skill pack

This commit is contained in:
Ma
2026-08-14 01:08:03 +08:00
parent bac7abcc2d
commit 8dee4cb236
31 changed files with 513 additions and 8 deletions
@@ -273,4 +273,24 @@ describe.sequential("publish packaging", () => {
await rm(packDir, { recursive: true, force: true });
}
});
it("packs the built-in professional skills with core", { timeout: STUDIO_PACK_TEST_TIMEOUT_MS }, async () => {
const packDir = await mkdtemp(join(tmpdir(), "inkos-core-pack-"));
try {
const coreDir = resolve(workspaceRoot, "packages", "core");
const tarballPath = await packPackage(coreDir, packDir);
const tarArgs = [...tarForceLocalArgs(), "-tf"];
const archiveListing = execFileSync("tar", [...tarArgs, tarballPath], {
cwd: workspaceRoot,
encoding: "utf-8",
});
expect(archiveListing).toContain("package/skills/inkos-long-writing/SKILL.md");
expect(archiveListing).toContain("package/skills/inkos-story-review/references/review-matrix.md");
expect(archiveListing).toContain("package/skills/inkos-story-cover/SKILL.md");
} finally {
await rm(packDir, { recursive: true, force: true });
}
});
});
+2 -1
View File
@@ -41,7 +41,8 @@
"files": [
"dist",
"!dist/__tests__",
"genres"
"genres",
"skills"
],
"license": "AGPL-3.0-only",
"repository": {
@@ -0,0 +1,16 @@
---
name: inkos-long-market-research
description: 长篇网文市场、榜单、平台趋势与对标研究。Use for evidence-based long-form fiction market research, not for ordinary drafting.
---
# Long-form market research
Use this skill when the user wants current market evidence, platform differences, comparable works, audience expectations, or topic selection for a long-form project.
- Clarify the market, platform, audience, language, and time window only when they materially affect the answer.
- Use `research_web` for current claims. Separate observed evidence, interpretation, and creative recommendation.
- Use `ingest_material` for user-provided reports or URLs and `retrieve_material` for already archived evidence.
- Do not treat rankings, popularity, or one successful book as a writing formula. Extract mechanisms and uncertainty.
- Research never mutates book canon. If the user later wants a source available during chapter writing, archive it and explicitly bind it with `manage_book_reference` in the active book.
- Respond in the user's language.
For a detailed evidence and deliverable rubric, load `references/research-rubric.md` with `use_skill` only when needed.
@@ -0,0 +1,19 @@
# Long-form market research rubric
## Evidence
- Record source, publication date, observation window, platform, and whether a number is first-party, third-party, or inferred.
- Distinguish durable reader needs from a temporary chart pattern.
- Compare at least two sources when making a market-level claim.
- State missing evidence rather than filling it with genre common sense.
## Deliverable
1. Market boundary and user objective.
2. Observed signals with sources.
3. Audience and reading-context implications.
4. Transferable story mechanisms.
5. Risks, saturation, and counter-signals.
6. Two or three differentiated directions, not a single mandatory formula.
Do not copy titles, character configurations, signature scenes, or wording from a comparable work.
@@ -0,0 +1,17 @@
---
name: inkos-long-story-analysis
description: 长篇小说拆稿、文风分析与可迁移机制提炼。Use when analyzing a full novel or long sample without copying its expression.
---
# Long-form story analysis
Use this skill when the user asks to deconstruct, compare, learn from, or continue from a long novel or substantial sample.
- Preserve the source as traceable material with `ingest_material` when it comes from a file, PDF, URL, or upload.
- Analyze semantic mechanisms: reader promise, conflict escalation, character motivation, information release, scene function, emotional payoff, volume rhythm, prose behavior, and continuity load.
- Keep source evidence pointers. Separate direct observation from inference.
- Extract reusable craft, not names, wording, scene order, or signature combinations.
- If the user explicitly wants the active book to consult this source later, call `manage_book_reference(action="bind")` with the user's natural-language purposes. Binding is guidance, never canon.
- If the user only asks a question, answer it; do not start writing or editing a book.
- Respond in the user's language.
Load `references/analysis-lens.md` only for a full decomposition or comparison deliverable.
@@ -0,0 +1,16 @@
# Long-form decomposition lens
## Layers
- Promise: what continuing experience the reader expects.
- Story engine: the repeatable pressure, choice, consequence, and reward loop.
- Structure: opening contract, arc transitions, volume goals, midpoint changes, and payoff placement.
- Character: desire, fear, tactic, relationship movement, voice, and irreversible choices.
- Information: what the reader, protagonist, and opposition know at each stage.
- Scene craft: entry pressure, concrete action, evidence, turn, consequence, and exit tension.
- Prose behavior: viewpoint distance, sentence movement, dialogue subtext, detail selection, and paragraph rhythm.
- Long-horizon state: facts, promises, hooks, unresolved costs, and continuity burdens.
## Output discipline
Anchor important claims to a chapter or excerpt. End with transferable principles, failure conditions, and several recombination options. Never output a disguised rewrite of the source.
@@ -0,0 +1,17 @@
---
name: inkos-long-writing
description: 长篇小说建书、续写、方向调整与章节协作。Use for long-form creation while preserving author intent, canon, and review gates.
---
# Long-form writing
Use this skill for creating, planning, continuing, or substantially revising a long-form book.
- Conversation comes first. A discussion, question, or request for options is not permission to write or edit.
- In general chat, propose the appropriate action and wait for confirmation. In an active book, use the existing `sub_agent` writer, auditor, or reviser rather than drafting chapter prose in chat.
- Preserve the authority order: established facts and published chapters; durable book rules; current author direction; model defaults and genre convention.
- When sources are bound to the book, use them only for the purposes the user stated. Never let a reference override author intent or canon, and never reproduce source wording.
- Make each scene change conflict, evidence, emotion, relationship, knowledge, or consequence. Do not pad to satisfy length.
- Report the real tool result and review status. Never claim a chapter was saved without a successful artifact result.
- Respond in the user's language.
Load `references/collaboration-protocol.md` when deciding between discussion, writing, revision, and canon change.
@@ -0,0 +1,15 @@
# Long-form collaboration protocol
## Decide the operation semantically
- Explore or compare directions: answer in conversation.
- Create a new book: discuss the core, then propose confirmation.
- Write the next chapter or several sequential chapters: use the writer pipeline after explicit execution intent.
- Diagnose an existing chapter: use the auditor and show concrete findings.
- Rewrite or restructure an existing chapter: use the reviser with the user's instruction and appropriate revision mode.
- Change durable setting: explain conflicts, then use the truth-editing path after explicit authorization.
- Retcon published history: stop and present the conflict and affected scope before editing.
## Completion
Completion comes from persisted artifacts and tool results, not assistant prose. Preserve failed review details and let the user choose whether to revise, accept, or change the standard.
@@ -0,0 +1,17 @@
---
name: inkos-short-market-research
description: 商业短篇市场、平台样本、标题与移动端阅读趋势研究。Use for evidence-based short-fiction market research.
---
# Short-fiction market research
Use this skill when the user asks what short fiction is working, how platforms differ, or which commercial direction to test.
- Use `research_web` for current platform and market claims; archive user-provided samples with `ingest_material`.
- Study titles, openings, pressure chains, evidence chains, emotional gaps, reversals, payoffs, chapter titles, and mobile-reading density.
- Separate a durable mechanism from a temporary surface trend.
- Do not turn benchmark research into plagiarism detection or a fixed tag table.
- Recommend several recombinations with different characters, causes, evidence, and consequences.
- Research does not authorize generation. If the user wants a short produced, move through the normal confirmation flow.
- Respond in the user's language.
Load `references/short-market-rubric.md` for a full benchmark report.
@@ -0,0 +1,16 @@
# Short-fiction benchmark rubric
Evaluate real samples on:
- title promise and mobile thumbnail readability;
- first-screen pressure and unanswered question;
- protagonist desire expressed through action;
- antagonist or pressure-source motivation;
- evidence, misunderstanding, and information asymmetry;
- emotional deficit before payoff;
- causal setup before reversal;
- scene-by-scene anti-padding value;
- chapter-end continuation pressure;
- ending payoff and residual emotion.
The report should explain why a mechanism works, when it fails, and how to recombine it without preserving the source's expression.
@@ -0,0 +1,16 @@
---
name: inkos-short-story-analysis
description: 商业短篇拆稿、情绪链、证据链和反转机制分析。Use to deconstruct a short story into transferable craft.
---
# Short-story analysis
Use this skill when the user supplies a complete short story or sample and asks why it works, how it is structured, or what can be learned from it.
- Read the whole supplied story when possible; do not infer the ending from the opening.
- Preserve traceability with `ingest_material` for uploaded files, PDFs, URLs, or long text assets.
- Analyze the story core, character motivation, pressure chain, evidence chain, emotional movement, information gap, reversal setup, payoff, opening, chapter titles, and mobile prose density.
- Quote sparingly and point to evidence. Extract methods rather than replacement prose.
- Do not silently turn analysis into a new short-fiction run.
- Respond in the user's language.
Load `references/deconstruction-template.md` for a complete Markdown analysis.
@@ -0,0 +1,14 @@
# Short-story decomposition template
1. One-sentence story core and reader promise.
2. Character table: desire, wound, tactic, misbelief, cost, and change.
3. Opening hook: concrete event, pressure, and missing answer.
4. Scene chain: input pressure, action, new evidence, emotional movement, consequence.
5. Evidence and information chain.
6. Reversal chain: setup, reader expectation, reveal, causal validity, payoff.
7. Emotional curve and reader deficit/reward.
8. Title and chapter-title functions.
9. Prose mechanics and mobile readability.
10. Transferable mechanisms, anti-patterns, and original recombination directions.
Do not reduce the result to labels. Each conclusion needs source evidence and an explanation of causality.
@@ -0,0 +1,18 @@
---
name: inkos-short-writing
description: 12–18章商业短篇的构思、一次写完、整篇审改与包装。Use for confirmed standalone short-fiction production.
---
# Short-fiction writing
Use this skill when the user wants a complete standalone commercial short story, not a long serial or a discussion-only answer.
- Preserve the user's premise, emotional target, taboo, viewpoint, and ending preference. Ask only for a missing choice that would materially change the story.
- In chat or short mode, use the normal proposal and confirmation flow before `short_fiction_run` or cover generation.
- Build one main line and at most one supporting reversal line. Prefer 1218 focused chapters and complete scenes over a synopsis montage.
- Express personality and values through action. Every scene must move conflict, causality, evidence, emotion, payoff, or relationship.
- Set up reversals and rewards through visible evidence; do not use sudden stupidity or unexplained luck.
- Treat title, chapter titles, opening, full manuscript, synopsis, selling points, and cover direction as one coherent product.
- Report partial failures honestly; a cover failure must not erase a successful manuscript.
- Respond in the user's language.
Load `references/production-checklist.md` before a full production run or whole-story revision.
@@ -0,0 +1,19 @@
# Short-fiction production checklist
## Before writing
- Core conflict can sustain a complete short story.
- Protagonist has an active desire and constrained tactic.
- Pressure source has a credible interest, fear, or misunderstanding.
- Opening creates a concrete emotional or practical deficit.
- Evidence and setup exist before each major reversal.
- Ending delivers the promised emotional result and cost.
## During whole-story review
- No scene exists only to explain.
- Later chapters do not collapse into summary.
- Chapter transitions preserve causality.
- Names, facts, objects, and evidence remain consistent.
- Mobile paragraphs remain readable without turning prose into fragments.
- Title and synopsis promise only events the manuscript actually delivers.
@@ -0,0 +1,17 @@
---
name: inkos-story-cover
description: 根据作品、平台与用户视觉要求设计或生成封面。Use for story-cover direction and confirmed cover generation.
---
# Story cover
Use this skill when the user wants cover strategy, a cover prompt, generation, or regeneration.
- Start from the actual work: title, genre, protagonist, central conflict, emotional promise, platform, and display size.
- Treat the user's visual instructions as authoritative. Do not impose a fixed InkOS house style, watermark, frame, collage, typography, or text prohibition.
- Distinguish a mobile book cover, cinematic key art, interactive-world scene image, character image, and item image. They serve different purposes.
- In chat, discuss or propose the action. Generate only through the available `generate_cover` confirmation path.
- Do not promise textual accuracy from an image model. If deterministic title layout is available, separate background generation from typography.
- Keep the cover faithful to the finished content; do not advertise events absent from the story.
- Respond in the user's language.
Load `references/cover-brief.md` when converting a manuscript into a production-ready visual brief.
@@ -0,0 +1,16 @@
# Cover brief
Capture only decisions supported by the user or work:
- artifact type and target platform;
- aspect ratio and thumbnail context;
- title treatment requirements, if any;
- focal subject, expression, pose, and viewpoint;
- one central conflict signal;
- one or two legible genre objects;
- palette, material, lighting, and atmosphere;
- prohibited elements;
- continuity references for recurring characters or places;
- whether typography is image-generated or applied deterministically later.
Evaluate at final size and thumbnail size. A beautiful image that hides genre, conflict, or title hierarchy may still be a poor cover.
@@ -0,0 +1,16 @@
---
name: inkos-story-deslop
description: 识别并修复空泛、模板化、总结腔和其他AI写作痕迹,同时保留作者声音。Use for semantic prose cleanup, not keyword deletion.
---
# Semantic prose cleanup
Use this skill when the user says prose feels generic, mechanical, over-explained, repetitive, or AI-generated.
- Diagnose by reading function and effect, not by counting banned words or applying global replacements.
- Distinguish a real defect from a legitimate voice choice. Repetition can be rhythm; abstraction can be intentional; short sentences can be pressure.
- Look for unsupported conclusions, emotion labels without scene evidence, generic transitions, symmetrical canned phrasing, repeated interpretation, decorative detail, summary replacing scenes, and dialogue that only transfers information.
- In an active book, audit first when scope is unclear. Use the reviser for an authorized rewrite; do not paste a replacement chapter into chat and claim it was saved.
- Preserve plot facts, viewpoint, character voice, evidence, pacing function, and strong original lines.
- Respond in the user's language.
Load `references/semantic-cleanup.md` for a passage-level diagnosis or revision brief.
@@ -0,0 +1,13 @@
# Semantic cleanup rubric
Ask of every suspect passage:
1. What story function should this passage perform?
2. Is that function visible through action, image, evidence, choice, or consequence?
3. Does the narrator explain an emotion already demonstrated?
4. Could the sentence fit any character or story unchanged?
5. Is detail carrying evidence, relationship, pressure, or voice?
6. Is dialogue motivated, or is it disguised exposition?
7. Does sentence rhythm match the scene's physical and emotional movement?
Repair by restoring concrete cause and character-specific behavior. Do not mechanically delete transitions, metaphors, three-part lists, or every occurrence of a flagged word.
@@ -0,0 +1,19 @@
---
name: inkos-story-import
description: 导入已有小说、逆向重建设定与续写工程,并区分母本参考。Use for importing manuscripts or attaching external canon safely.
---
# Story import and reconstruction
Use this skill when the user wants an existing manuscript to become an editable, continuable InkOS project.
- Determine the user's intent before acting:
- `import_chapters`: the text becomes real chapters in the active book and InkOS reconstructs state.
- `ingest_material`: the text remains reference material only.
- `manage_book_reference`: an archived source should guide future chapters for user-stated purposes.
- Preserve chapter order with natural chapter-number ordering. Confirm the continuation point when the active book already has chapters.
- Never treat a user's own manuscript as an imitation target by default.
- After import, report imported chapter count, detected gaps, reconstructed facts, uncertainties, and the next safe continuation point.
- Do not invent missing canon to make the project look complete. Mark uncertainty for later confirmation.
- Respond in the user's language.
Load `references/reconstruction-rubric.md` for large or inconsistent manuscripts.
@@ -0,0 +1,13 @@
# Manuscript reconstruction rubric
Reconstruct only what the text supports:
- chapter order and titles;
- character identity, voice, goals, relationships, and last known state;
- world rules and exceptions;
- timeline and location changes;
- revealed facts versus reader-facing uncertainty;
- planted, advanced, resolved, and dormant hooks;
- current conflict, unresolved costs, and immediate continuation pressure.
For contradictions, retain both evidence pointers and mark the conflict. Do not silently choose one version. Imported chapter prose is authoritative unless the user explicitly requests a rewrite or retcon.
@@ -0,0 +1,16 @@
---
name: inkos-story-review
description: 按题材、目标读者和用户标准审稿,展示具体问题并协作修订。Use for chapter or manuscript review with transparent criteria.
---
# Story review
Use this skill when the user wants diagnosis, scoring, comparison, approval, or revision advice for existing prose.
- First identify the applicable standard from genre, target audience, tone, platform, and explicit user preference. Everyday comedy, literary fiction, romance, mystery, and commercial serials should not share one logic-density threshold.
- In an active book, use the auditor for persisted chapters. Show concrete issue descriptions, severity, evidence, and likely reader impact.
- A parser or model-format failure is an audit failure, not evidence that the prose is bad and not permission to rewrite it.
- Do not revise merely because an audit failed. Revise when the user asks, or when the confirmed workflow authorizes it; report whether the revised artifact was actually applied.
- Preserve the user's voice and successful passages. Prefer the smallest scope that resolves the real defect.
- Respond in the user's language.
Load `references/review-matrix.md` for a full review or when standards are disputed.
@@ -0,0 +1,21 @@
# Adaptive review matrix
Always inspect:
- user intent and durable constraints;
- continuity and causal coherence;
- character motivation and voice;
- scene function and consequence;
- information and evidence consistency;
- prose clarity and viewpoint stability;
- promised versus delivered emotion.
Adjust by form:
- Comedy may accept coincidence or exaggeration when setup, rhythm, and character logic support the joke.
- Romance prioritizes relationship movement, subtext, and emotional credibility.
- Mystery requires evidence visibility, fair inference, and controlled information release.
- Commercial serials require continuation pressure without sacrificing scene completeness.
- Literary work may trade plot velocity for perception, language, or thematic movement, but not for empty repetition.
For every blocking issue, include evidence, impact, and a repair direction. Separate objective contradiction from taste preference.
@@ -4,11 +4,26 @@ import { delimiter, join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
createSkillRegistry,
loadAvailableAgentSkills,
loadBuiltinAgentSkills,
loadConfiguredAgentSkills,
loadExternalAgentSkills,
parseAgentSkillDocument,
} from "../skills/index.js";
const BUILTIN_SKILL_IDS = [
"inkos-long-market-research",
"inkos-long-story-analysis",
"inkos-long-writing",
"inkos-short-market-research",
"inkos-short-story-analysis",
"inkos-short-writing",
"inkos-story-cover",
"inkos-story-deslop",
"inkos-story-import",
"inkos-story-review",
] as const;
describe("external skill loader", () => {
let root: string;
@@ -20,6 +35,56 @@ describe("external skill loader", () => {
await import("node:fs/promises").then(({ rm }) => rm(root, { recursive: true, force: true }));
});
it("loads the built-in professional story skill pack", async () => {
const loaded = await loadBuiltinAgentSkills();
expect(loaded.diagnostics).toEqual([]);
expect(loaded.skills.map((skill) => skill.id)).toEqual(BUILTIN_SKILL_IDS);
expect(loaded.skills).toEqual(expect.arrayContaining([
expect.objectContaining({
id: "inkos-long-writing",
source: "builtin",
body: expect.stringContaining("established facts"),
baseDir: expect.stringMatching(/skills[\\/]inkos-long-writing$/),
}),
expect.objectContaining({
id: "inkos-story-review",
source: "builtin",
body: expect.stringContaining("parser or model-format failure"),
}),
]));
});
it("lets a project skill replace a built-in skill with the same id", async () => {
const skillDir = join(root, ".agents", "skills", "inkos-story-review");
await mkdir(skillDir, { recursive: true });
await writeFile(
join(skillDir, "SKILL.md"),
[
"---",
"name: inkos-story-review",
"description: Project-specific review standard.",
"---",
"Use the project's own review standard.",
].join("\n"),
"utf-8",
);
const loaded = await loadAvailableAgentSkills({
projectRoot: root,
env: {},
homeDir: join(root, "home"),
});
const registry = createSkillRegistry({ skills: loaded.skills });
expect(registry.getSkill("inkos-story-review")).toMatchObject({
source: "project",
body: "Use the project's own review standard.",
baseDir: skillDir,
});
expect(registry.listSkills()).toHaveLength(BUILTIN_SKILL_IDS.length);
});
it("loads only the standard AgentSkills discovery fields and body", async () => {
const skillDir = join(root, "detective-play");
await mkdir(join(skillDir, "scripts"), { recursive: true });
+2 -2
View File
@@ -65,7 +65,7 @@ import type { TranscriptEvent, TranscriptRole } from "../interaction/session-tra
import type { PlayMode, SessionKind } from "../interaction/session.js";
import type { ActionPayload, ActionSource, RequestedIntent } from "../interaction/action-envelope.js";
import type { ContextCompressionCallback } from "../models/context-compression.js";
import { createSkillRegistry, loadConfiguredAgentSkills } from "../skills/index.js";
import { createSkillRegistry, loadAvailableAgentSkills } from "../skills/index.js";
import { assertSafeBookId } from "../utils/book-id.js";
import { PlayStore } from "../play/play-store.js";
import { isLlmStubEnabled, stubAgentStream } from "./llm-stub.js";
@@ -1018,7 +1018,7 @@ async function runAgentSessionUnlocked(
const requestedIntent = config.requestedIntent;
const actionPayload = config.actionPayload;
const actionPayloadKey = actionPayloadCacheKey(actionPayload);
const configuredSkills = await loadConfiguredAgentSkills({ projectRoot });
const configuredSkills = await loadAvailableAgentSkills({ projectRoot });
const skillRegistry = createSkillRegistry({ skills: configuredSkills.skills });
const skillResolution = skillRegistry.resolveSkills({
requestedSkills: config.requestedSkills,
+3
View File
@@ -139,6 +139,8 @@ export {
export {
AgentSkillSchema,
createSkillRegistry,
loadAvailableAgentSkills,
loadBuiltinAgentSkills,
loadConfiguredAgentSkills,
loadExternalAgentSkills,
parseAgentSkillDocument,
@@ -146,6 +148,7 @@ export {
type CreateSkillRegistryOptions,
type ExternalSkillDiagnostic,
type LoadConfiguredAgentSkillsInput,
type LoadAvailableAgentSkillsResult,
type LoadExternalAgentSkillsInput,
type LoadExternalAgentSkillsResult,
type ParseAgentSkillDocumentOptions,
@@ -0,0 +1,41 @@
import { fileURLToPath } from "node:url";
import {
loadConfiguredAgentSkills,
loadExternalAgentSkills,
type ExternalSkillDiagnostic,
type LoadConfiguredAgentSkillsInput,
} from "./external-loader.js";
import type { AgentSkill } from "./types.js";
export interface LoadAvailableAgentSkillsResult {
readonly skills: ReadonlyArray<AgentSkill>;
readonly diagnostics: ReadonlyArray<ExternalSkillDiagnostic>;
}
export async function loadBuiltinAgentSkills(
builtinRoot = builtinSkillsRoot(),
): Promise<LoadAvailableAgentSkillsResult> {
return loadExternalAgentSkills({
externalDirs: [builtinRoot],
source: "builtin",
});
}
export async function loadAvailableAgentSkills(
input: LoadConfiguredAgentSkillsInput,
): Promise<LoadAvailableAgentSkillsResult> {
const [builtin, configured] = await Promise.all([
loadBuiltinAgentSkills(),
loadConfiguredAgentSkills(input),
]);
return {
// Registry de-duplication is last-write-wins, so project/user skills can
// intentionally replace an InkOS default with the same AgentSkills id.
skills: [...builtin.skills, ...configured.skills],
diagnostics: [...builtin.diagnostics, ...configured.diagnostics],
};
}
function builtinSkillsRoot(): string {
return fileURLToPath(new URL("../../skills", import.meta.url));
}
+5
View File
@@ -8,6 +8,11 @@ export {
type LoadExternalAgentSkillsResult,
type ParseAgentSkillDocumentOptions,
} from "./external-loader.js";
export {
loadAvailableAgentSkills,
loadBuiltinAgentSkills,
type LoadAvailableAgentSkillsResult,
} from "./builtin-loader.js";
export { createSkillRegistry, type CreateSkillRegistryOptions } from "./registry.js";
export {
AgentSkillSchema,
+1 -1
View File
@@ -5,7 +5,7 @@ export const AgentSkillSchema = z.object({
name: z.string().min(1).max(64),
description: z.string().min(1).max(1024),
body: z.string().default(""),
source: z.enum(["project", "user", "external"]).default("external"),
source: z.enum(["builtin", "project", "user", "external"]).default("external"),
baseDir: z.string().min(1).optional(),
}).strict();
export type AgentSkill = z.infer<typeof AgentSkillSchema>;
@@ -15,7 +15,7 @@ describe("Studio skill endpoints", () => {
await rm(root, { recursive: true, force: true });
});
it("lists project and external SKILL.md folders without implicit built-ins", async () => {
it("lists built-in professional skills alongside editable project skills", async () => {
await mkdir(join(root, ".agents", "skills", "detective-play"), { recursive: true });
await writeFile(
join(root, ".agents", "skills", "detective-play", "SKILL.md"),
@@ -34,7 +34,11 @@ describe("Studio skill endpoints", () => {
const json = await res.json() as { skills: Array<{ id: string; source: string; editable: boolean; body?: string }> };
expect(res.status).toBe(200);
expect(json.skills.map((skill) => skill.source)).not.toContain("builtin");
expect(json.skills).toContainEqual(expect.objectContaining({
id: "inkos-long-writing",
source: "builtin",
editable: false,
}));
expect(json.skills).toContainEqual(expect.objectContaining({
id: "detective-play",
source: "project",
@@ -44,6 +48,40 @@ describe("Studio skill endpoints", () => {
expect(json.skills.find((skill) => skill.id === "detective-play")).not.toHaveProperty("whenToUse");
});
it("lets an imported project skill override a built-in skill", async () => {
const app = createStudioServer({} as never, root);
const manifest = Buffer.from([
"---",
"name: inkos-story-review",
"description: Review with this project's acceptance standard.",
"---",
"Apply the project-specific review standard.",
].join("\n")).toString("base64");
const imported = await app.request("/api/v1/skills/import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
files: [
{ path: "inkos-story-review/SKILL.md", dataUrl: `data:text/markdown;base64,${manifest}` },
],
}),
});
expect(imported.status).toBe(200);
const listed = await app.request("/api/v1/skills");
const json = await listed.json() as {
skills: Array<{ id: string; source: string; editable: boolean; body: string }>;
};
const reviewSkills = json.skills.filter((skill) => skill.id === "inkos-story-review");
expect(reviewSkills).toEqual([expect.objectContaining({
source: "project",
editable: true,
body: "Apply the project-specific review standard.",
})]);
});
it("does not expose the legacy JSON skill create or update protocol", async () => {
const app = createStudioServer({} as never, root);
+1
View File
@@ -352,6 +352,7 @@ vi.mock("@actalk/inkos-core", async (importOriginal) => {
normalizeSkillIdList: actual.normalizeSkillIdList,
createSkillRegistry: actual.createSkillRegistry,
loadConfiguredAgentSkills: actual.loadConfiguredAgentSkills,
loadAvailableAgentSkills: actual.loadAvailableAgentSkills,
createTranslationCreateTool: actual.createTranslationCreateTool,
createLLMTranslationModel: createLLMTranslationModelMock,
createTranslationProjectFromFile: actual.createTranslationProjectFromFile,
+2 -2
View File
@@ -71,7 +71,7 @@ import {
inferLanguage,
ingestMaterial,
createSkillRegistry,
loadConfiguredAgentSkills,
loadAvailableAgentSkills,
parseAgentSkillDocument,
getBuiltinPrompt,
listBuiltinPromptPacks,
@@ -934,7 +934,7 @@ async function importStudioSkillFolder(
}
async function loadStudioSkills(root: string) {
const configured = await loadConfiguredAgentSkills({ projectRoot: root });
const configured = await loadAvailableAgentSkills({ projectRoot: root });
const projectSkillIds = await listProjectSkillIds(root);
const registry = createSkillRegistry({ skills: configured.skills });
return {