vskill 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agents.json +1 -1
- package/dist/commands/check.d.ts +55 -0
- package/dist/commands/check.js +279 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/core/agent-prompts.d.ts +35 -0
- package/dist/core/agent-prompts.js +201 -0
- package/dist/core/agent-prompts.js.map +1 -0
- package/dist/core/skill-generator.d.ts +25 -3
- package/dist/core/skill-generator.js +131 -0
- package/dist/core/skill-generator.js.map +1 -1
- package/dist/eval-server/api-routes.d.ts +16 -35
- package/dist/eval-server/api-routes.js +45 -169
- package/dist/eval-server/api-routes.js.map +1 -1
- package/dist/eval-server/skill-create-routes.d.ts +8 -0
- package/dist/eval-server/skill-create-routes.js +96 -0
- package/dist/eval-server/skill-create-routes.js.map +1 -1
- package/dist/eval-server/source-link.d.ts +73 -0
- package/dist/eval-server/source-link.js +275 -0
- package/dist/eval-server/source-link.js.map +1 -0
- package/dist/eval-server/utils/resolve-editor.d.ts +13 -0
- package/dist/eval-server/utils/resolve-editor.js +92 -0
- package/dist/eval-server/utils/resolve-editor.js.map +1 -0
- package/dist/eval-ui/assets/{CreateSkillPage--g_NEIiD.js → CreateSkillPage-CKvqAya0.js} +1 -1
- package/dist/eval-ui/assets/{FindSkillsPalette-XKomH8zI.js → FindSkillsPalette-B8pTa5NP.js} +2 -2
- package/dist/eval-ui/assets/{SearchPaletteCore-DE6FhFNX.js → SearchPaletteCore-CkVRvaZk.js} +1 -1
- package/dist/eval-ui/assets/SkillDetailPanel-d4_LquVH.js +1 -0
- package/dist/eval-ui/assets/{UpdateDropdown-DcRoBdFQ.js → UpdateDropdown-DA7OktXO.js} +1 -1
- package/dist/eval-ui/assets/{index-DhrY6PTA.js → index-DCbohW6l.js} +37 -37
- package/dist/eval-ui/index.html +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/shared/copy-plugin-filtered.js +5 -0
- package/dist/shared/copy-plugin-filtered.js.map +1 -1
- package/dist/studio/lib/scope-transfer.js +31 -6
- package/dist/studio/lib/scope-transfer.js.map +1 -1
- package/package.json +1 -1
- package/dist/eval-ui/assets/SkillDetailPanel-BEqkY3lG.js +0 -1
|
@@ -1,7 +1,29 @@
|
|
|
1
|
-
import { type GenerateSkillRequest, type GenerateSkillResult } from "../eval-server/skill-create-routes.js";
|
|
2
|
-
export
|
|
1
|
+
import { type GenerateSkillRequest as BaseGenerateSkillRequest, type GenerateSkillResult as BaseGenerateSkillResult } from "../eval-server/skill-create-routes.js";
|
|
2
|
+
export interface GenerateSkillRequest extends BaseGenerateSkillRequest {
|
|
3
|
+
multiFile?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface GenerateSkillResult extends BaseGenerateSkillResult {
|
|
6
|
+
/** Auxiliary files produced by the multi-file pipeline (relative path → contents). */
|
|
7
|
+
files?: Record<string, string>;
|
|
8
|
+
/** Env-var names declared by the test-agent. */
|
|
9
|
+
secrets?: string[];
|
|
10
|
+
/** Runtime requirements declared by the script-agent. */
|
|
11
|
+
runtime?: {
|
|
12
|
+
python?: string;
|
|
13
|
+
pip?: string[];
|
|
14
|
+
node?: string;
|
|
15
|
+
};
|
|
16
|
+
/** Integration-test contract declared by the test-agent. */
|
|
17
|
+
integrationTests?: {
|
|
18
|
+
runner: "vitest" | "pytest" | "none";
|
|
19
|
+
file?: string;
|
|
20
|
+
requires?: string[];
|
|
21
|
+
};
|
|
22
|
+
/** Per-agent error messages from the parallel fan-out (informational). */
|
|
23
|
+
multiFileWarnings?: string[];
|
|
24
|
+
}
|
|
3
25
|
export interface GenerateSkillProgressEvent {
|
|
4
|
-
phase: "preparing" | "generating-body" | "generating-evals" | "parsing" | "done";
|
|
26
|
+
phase: "preparing" | "generating-body" | "generating-evals" | "generating-scripts" | "generating-graders" | "generating-tests" | "generating-references" | "parsing" | "done";
|
|
5
27
|
message: string;
|
|
6
28
|
}
|
|
7
29
|
export interface GenerateSkillOptions {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
// eslint-disable-next-line no-restricted-imports -- see module header TODO
|
|
26
26
|
import { BODY_SYSTEM_PROMPT, EVAL_SYSTEM_PROMPT, buildAgentAwareSystemPrompt, detectProjectLayout, mergeGenerateResults, parseBodyResponse, parseEvalsResponse, } from "../eval-server/skill-create-routes.js";
|
|
27
27
|
import { createLlmClient } from "../eval/llm.js";
|
|
28
|
+
import { SCRIPT_SYSTEM_PROMPT, GRADER_SYSTEM_PROMPT, TEST_SYSTEM_PROMPT, REFERENCE_SYSTEM_PROMPT, parseScriptResponse, parseGraderResponse, parseTestResponse, parseReferenceResponse, } from "./agent-prompts.js";
|
|
28
29
|
/**
|
|
29
30
|
* Resolve provider + model defaults consistent with the pre-extraction
|
|
30
31
|
* handler behavior: when both are absent we use { claude-cli, sonnet }.
|
|
@@ -76,6 +77,18 @@ export async function generateSkill(request, options) {
|
|
|
76
77
|
const evalPrompt = `Generate eval test cases for this skill:\n\n${trimmedPrompt}\n\nReturn only the JSON object with an "evals" array.`;
|
|
77
78
|
// Agent-aware prompt augmentation: append constraints for non-Claude agents
|
|
78
79
|
const effectiveSystemPrompt = buildAgentAwareSystemPrompt(BODY_SYSTEM_PROMPT, request.targetAgents);
|
|
80
|
+
if (request.multiFile === true) {
|
|
81
|
+
return await generateMultiFileSkill({
|
|
82
|
+
trimmedPrompt,
|
|
83
|
+
effectiveSystemPrompt,
|
|
84
|
+
provider,
|
|
85
|
+
model,
|
|
86
|
+
evalModel,
|
|
87
|
+
pluginContext,
|
|
88
|
+
abortSignal,
|
|
89
|
+
emit,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
79
92
|
emit({ phase: "generating-body", message: "Generating skill body..." });
|
|
80
93
|
emit({ phase: "generating-evals", message: "Generating evals..." });
|
|
81
94
|
const bodyCall = bodyClient
|
|
@@ -97,4 +110,122 @@ export async function generateSkill(request, options) {
|
|
|
97
110
|
emit({ phase: "done", message: "Generation complete" });
|
|
98
111
|
return merged;
|
|
99
112
|
}
|
|
113
|
+
async function generateMultiFileSkill(ctx) {
|
|
114
|
+
const { trimmedPrompt, effectiveSystemPrompt, provider, model, evalModel, pluginContext, abortSignal, emit, } = ctx;
|
|
115
|
+
// Capable model for code-producing agents; cheap model for reference + eval.
|
|
116
|
+
const capableClient = createLlmClient({ provider, model });
|
|
117
|
+
const cheapClient = createLlmClient({ provider, model: evalModel });
|
|
118
|
+
// Each agent gets the same skill description; the system prompt does the
|
|
119
|
+
// role-shaping. buildAgentAwareSystemPrompt() is intentionally NOT applied
|
|
120
|
+
// here — the per-role prompts are concrete enough that target-agent
|
|
121
|
+
// constraints are encoded by the body-agent's SKILL.md output, not the
|
|
122
|
+
// auxiliary code files.
|
|
123
|
+
const userPrompt = `Generate the role-specific output for this skill:\n\n${trimmedPrompt}`;
|
|
124
|
+
emit({ phase: "generating-scripts", message: "Generating helper scripts..." });
|
|
125
|
+
emit({ phase: "generating-graders", message: "Generating grader..." });
|
|
126
|
+
emit({ phase: "generating-tests", message: "Generating integration test..." });
|
|
127
|
+
emit({ phase: "generating-references", message: "Generating references..." });
|
|
128
|
+
emit({ phase: "generating-evals", message: "Generating evals..." });
|
|
129
|
+
const scriptCall = capableClient
|
|
130
|
+
.generate(SCRIPT_SYSTEM_PROMPT, userPrompt)
|
|
131
|
+
.then((r) => parseScriptResponse(r.text));
|
|
132
|
+
const graderCall = capableClient
|
|
133
|
+
.generate(GRADER_SYSTEM_PROMPT, userPrompt)
|
|
134
|
+
.then((r) => parseGraderResponse(r.text));
|
|
135
|
+
const testCall = capableClient
|
|
136
|
+
.generate(TEST_SYSTEM_PROMPT, userPrompt)
|
|
137
|
+
.then((r) => parseTestResponse(r.text));
|
|
138
|
+
const referenceCall = cheapClient
|
|
139
|
+
.generate(REFERENCE_SYSTEM_PROMPT, userPrompt)
|
|
140
|
+
.then((r) => parseReferenceResponse(r.text));
|
|
141
|
+
const evalCall = cheapClient
|
|
142
|
+
.generate(EVAL_SYSTEM_PROMPT, `Generate eval test cases for this skill:\n\n${trimmedPrompt}\n\nReturn only the JSON object with an "evals" array.`)
|
|
143
|
+
.then((r) => parseEvalsResponse(r.text));
|
|
144
|
+
const settled = await Promise.allSettled([
|
|
145
|
+
scriptCall,
|
|
146
|
+
graderCall,
|
|
147
|
+
testCall,
|
|
148
|
+
referenceCall,
|
|
149
|
+
evalCall,
|
|
150
|
+
]);
|
|
151
|
+
if (abortSignal?.aborted) {
|
|
152
|
+
const reason = abortSignal.reason ??
|
|
153
|
+
new Error("Skill generation aborted");
|
|
154
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
155
|
+
}
|
|
156
|
+
// Collect produced files + warnings.
|
|
157
|
+
const files = {};
|
|
158
|
+
const warnings = [];
|
|
159
|
+
let secrets;
|
|
160
|
+
let runtime;
|
|
161
|
+
let integrationTests;
|
|
162
|
+
const [scriptR, graderR, testR, referenceR, evalsR] = settled;
|
|
163
|
+
if (scriptR.status === "fulfilled") {
|
|
164
|
+
for (const f of scriptR.value.files)
|
|
165
|
+
files[f.path] = f.content;
|
|
166
|
+
if (scriptR.value.runtime)
|
|
167
|
+
runtime = scriptR.value.runtime;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
warnings.push(`script-agent failed: ${stringifyReason(scriptR.reason)}`);
|
|
171
|
+
}
|
|
172
|
+
if (graderR.status === "fulfilled") {
|
|
173
|
+
for (const f of graderR.value.files)
|
|
174
|
+
files[f.path] = f.content;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
warnings.push(`grader-agent failed: ${stringifyReason(graderR.reason)}`);
|
|
178
|
+
}
|
|
179
|
+
if (testR.status === "fulfilled") {
|
|
180
|
+
for (const f of testR.value.files)
|
|
181
|
+
files[f.path] = f.content;
|
|
182
|
+
if (testR.value.secrets)
|
|
183
|
+
secrets = testR.value.secrets;
|
|
184
|
+
if (testR.value.integrationTests)
|
|
185
|
+
integrationTests = testR.value.integrationTests;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
warnings.push(`test-agent failed: ${stringifyReason(testR.reason)}`);
|
|
189
|
+
}
|
|
190
|
+
if (referenceR.status === "fulfilled") {
|
|
191
|
+
for (const f of referenceR.value.files)
|
|
192
|
+
files[f.path] = f.content;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
warnings.push(`reference-agent failed: ${stringifyReason(referenceR.reason)}`);
|
|
196
|
+
}
|
|
197
|
+
emit({ phase: "generating-body", message: "Generating SKILL.md..." });
|
|
198
|
+
// Body-agent runs LAST with the produced filename list so SKILL.md can
|
|
199
|
+
// reference the actual files. Missing files become generic prose in the
|
|
200
|
+
// SKILL.md body — never broken links.
|
|
201
|
+
const filenameList = Object.keys(files).sort().join("\n - ");
|
|
202
|
+
const bodyPrompt = `Generate a skill definition (body and metadata only, NO evals) for:\n\n${trimmedPrompt}\n\nThe skill is multi-file. The following auxiliary files have been produced and live in the skill directory:\n - ${filenameList || "(no auxiliary files produced)"}\n\nReference them in the SKILL.md body using relative paths (e.g. \`scripts/audit.py\`). If a referenced category is missing from the list above, omit references rather than inventing filenames.\n\nApply Skill Studio best practices. Return the JSON object followed by ---REASONING--- and your explanation.${pluginContext}`;
|
|
203
|
+
const bodyClient = createLlmClient({ provider, model });
|
|
204
|
+
const bodySettled = await Promise.allSettled([
|
|
205
|
+
bodyClient.generate(effectiveSystemPrompt, bodyPrompt).then((r) => parseBodyResponse(r.text)),
|
|
206
|
+
]);
|
|
207
|
+
emit({ phase: "parsing", message: "Merging multi-file results..." });
|
|
208
|
+
const merged = mergeGenerateResults(bodySettled[0], evalsR);
|
|
209
|
+
emit({ phase: "done", message: "Multi-file generation complete" });
|
|
210
|
+
return {
|
|
211
|
+
...merged,
|
|
212
|
+
files,
|
|
213
|
+
...(secrets ? { secrets } : {}),
|
|
214
|
+
...(runtime ? { runtime } : {}),
|
|
215
|
+
...(integrationTests ? { integrationTests } : {}),
|
|
216
|
+
...(warnings.length > 0 ? { multiFileWarnings: warnings } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function stringifyReason(reason) {
|
|
220
|
+
if (reason instanceof Error)
|
|
221
|
+
return reason.message;
|
|
222
|
+
if (typeof reason === "string")
|
|
223
|
+
return reason;
|
|
224
|
+
try {
|
|
225
|
+
return JSON.stringify(reason);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return String(reason);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
100
231
|
//# sourceMappingURL=skill-generator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-generator.js","sourceRoot":"","sources":["../../src/core/skill-generator.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yEAAyE;AACzE,8EAA8E;AAC9E,mCAAmC;AACnC,EAAE;AACF,iCAAiC;AACjC,sEAAsE;AACtE,0BAA0B;AAC1B,2EAA2E;AAC3E,oCAAoC;AACpC,wEAAwE;AACxE,uEAAuE;AACvE,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,wEAAwE;AACxE,yEAAyE;AACzE,yEAAyE;AACzE,uEAAuE;AACvE,wEAAwE;AACxE,iEAAiE;AACjE,uEAAuE;AACvE,kCAAkC;AAClC,8EAA8E;AAE9E,2EAA2E;AAC3E,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GAGnB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"skill-generator.js","sourceRoot":"","sources":["../../src/core/skill-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -107,6 +107,20 @@ export interface SkillMetadataFields {
|
|
|
107
107
|
* "skills/foo/SKILL.md"). Defaults to "SKILL.md" for flat-layout
|
|
108
108
|
* installs derived from a legacy `github:` source string. */
|
|
109
109
|
skillPath: string | null;
|
|
110
|
+
/** Env-var names this skill expects (purposes/hints live in `.env.example` comments). */
|
|
111
|
+
secrets: string[] | null;
|
|
112
|
+
/** Language runtime declaration (Python and/or Node.js). */
|
|
113
|
+
runtime: {
|
|
114
|
+
python: string | null;
|
|
115
|
+
pip: string[] | null;
|
|
116
|
+
node: string | null;
|
|
117
|
+
} | null;
|
|
118
|
+
/** Integration-test contract verified by `vskill check`. */
|
|
119
|
+
integrationTests: {
|
|
120
|
+
runner: "vitest" | "pytest" | "none";
|
|
121
|
+
file: string | null;
|
|
122
|
+
requires: string[] | null;
|
|
123
|
+
} | null;
|
|
110
124
|
}
|
|
111
125
|
/**
|
|
112
126
|
* Minimal YAML frontmatter parser — handles scalars and arrays (inline [a, b]
|
|
@@ -130,40 +144,8 @@ export declare function parseSkillFrontmatter(content: string): Record<string, s
|
|
|
130
144
|
* for `origin="source"` or if no registry entry matches.
|
|
131
145
|
*/
|
|
132
146
|
export declare function deriveSourceAgent(skillDir: string, root: string, origin: "source" | "installed"): string | null;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* (SSH, HTTPS, ssh://) to its canonical `https://github.com/owner/repo`
|
|
136
|
-
* form (no `.git` suffix, no trailing path). Returns null for non-github
|
|
137
|
-
* hosts, malformed input, empty/whitespace strings.
|
|
138
|
-
*/
|
|
139
|
-
export declare function parseGithubRemote(remote: string | null | undefined): string | null;
|
|
140
|
-
/**
|
|
141
|
-
* 0770 — Walk parent directories from `startDir` looking for a `.git` entry
|
|
142
|
-
* (directory OR file — git worktrees use a `.git` file). Bails at the
|
|
143
|
-
* filesystem root or after `maxLevels` iterations. Returns the absolute
|
|
144
|
-
* path of the discovered git root, or null.
|
|
145
|
-
*/
|
|
146
|
-
export declare function walkUpForGitRoot(startDir: string, maxLevels?: number): string | null;
|
|
147
|
-
/**
|
|
148
|
-
* 0770 — Test-only helper to clear the module-level memoization cache so
|
|
149
|
-
* tests can isolate detection runs across `beforeEach`.
|
|
150
|
-
*/
|
|
151
|
-
export declare function resetAuthoredSourceLinkCache(): void;
|
|
152
|
-
/**
|
|
153
|
-
* 0770 — Detect source-repo provenance for a locally-authored skill (no
|
|
154
|
-
* lockfile entry). Walks for `.git`, reads `origin` remote, normalizes via
|
|
155
|
-
* `parseGithubRemote`, and computes `skillPath` from `git ls-files` (with a
|
|
156
|
-
* filesystem fallback for untracked SKILL.md files). Memoized per absolute
|
|
157
|
-
* skill dir for the eval-server process lifetime.
|
|
158
|
-
*
|
|
159
|
-
* All git invocations use `execFileSync` with explicit argv (no shell), a
|
|
160
|
-
* 1500ms hard timeout, and silenced stderr. Any error converts to
|
|
161
|
-
* `{null, null}` — `buildSkillMetadata` never throws because of git.
|
|
162
|
-
*/
|
|
163
|
-
export declare function detectAuthoredSourceLink(skillDir: string): {
|
|
164
|
-
repoUrl: string | null;
|
|
165
|
-
skillPath: string | null;
|
|
166
|
-
};
|
|
147
|
+
import { parseGithubRemote, walkUpForGitRoot, detectAuthoredSourceLink, resolveSourceLink, readCopiedSkillSidecar, resetAuthoredSourceLinkCache, resetCopiedSkillSidecarCache } from "./source-link.js";
|
|
148
|
+
export { parseGithubRemote, walkUpForGitRoot, detectAuthoredSourceLink, resolveSourceLink, readCopiedSkillSidecar, resetAuthoredSourceLinkCache, resetCopiedSkillSidecarCache, };
|
|
167
149
|
/**
|
|
168
150
|
* Build the T-025 metadata payload for a single skill. Reads SKILL.md from
|
|
169
151
|
* disk if present; returns EMPTY_METADATA on any error so the /api/skills
|
|
@@ -233,4 +215,3 @@ export declare function detectAvailableProviders(): Promise<Array<{
|
|
|
233
215
|
resolvedModel?: string | null;
|
|
234
216
|
}>>;
|
|
235
217
|
export declare function registerRoutes(router: Router, root: string, projectName?: string): void;
|
|
236
|
-
export {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
5
5
|
import { execSync, execFileSync } from "node:child_process";
|
|
6
|
-
import { join, resolve, dirname
|
|
6
|
+
import { join, resolve, dirname } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { sendJson, readBody } from "./router.js";
|
|
9
9
|
import { initSSE, sendSSE, sendSSEDone, withHeartbeat, startDynamicHeartbeat } from "./sse-helpers.js";
|
|
@@ -375,6 +375,9 @@ const EMPTY_METADATA = {
|
|
|
375
375
|
sourceAgent: null,
|
|
376
376
|
repoUrl: null,
|
|
377
377
|
skillPath: null,
|
|
378
|
+
secrets: null,
|
|
379
|
+
runtime: null,
|
|
380
|
+
integrationTests: null,
|
|
378
381
|
};
|
|
379
382
|
/**
|
|
380
383
|
* Allow-list of metadata children that may be surfaced at the top level
|
|
@@ -407,6 +410,20 @@ const SURFACED_METADATA_KEYS = new Set([
|
|
|
407
410
|
// Path / file metadata
|
|
408
411
|
"entryPoint",
|
|
409
412
|
"entry-point",
|
|
413
|
+
// 0815: multi-file manifest fields (kebab-case canonical, camelCase tolerated).
|
|
414
|
+
"secrets",
|
|
415
|
+
"runtime-python",
|
|
416
|
+
"runtimePython",
|
|
417
|
+
"runtime-pip",
|
|
418
|
+
"runtimePip",
|
|
419
|
+
"runtime-node",
|
|
420
|
+
"runtimeNode",
|
|
421
|
+
"integration-runner",
|
|
422
|
+
"integrationRunner",
|
|
423
|
+
"integration-file",
|
|
424
|
+
"integrationFile",
|
|
425
|
+
"integration-requires",
|
|
426
|
+
"integrationRequires",
|
|
410
427
|
]);
|
|
411
428
|
/**
|
|
412
429
|
* Minimal YAML frontmatter parser — handles scalars and arrays (inline [a, b]
|
|
@@ -682,174 +699,12 @@ export function deriveSourceAgent(skillDir, root, origin) {
|
|
|
682
699
|
}
|
|
683
700
|
return null;
|
|
684
701
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
export function parseGithubRemote(remote) {
|
|
692
|
-
const trimmed = (remote ?? "").trim();
|
|
693
|
-
if (!trimmed)
|
|
694
|
-
return null;
|
|
695
|
-
// SSH: git@github.com:owner/repo[.git]
|
|
696
|
-
let m = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/.exec(trimmed);
|
|
697
|
-
if (m)
|
|
698
|
-
return `https://github.com/${m[1]}/${m[2]}`;
|
|
699
|
-
// ssh://git@github.com/owner/repo[.git]
|
|
700
|
-
m = /^ssh:\/\/git@github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?$/.exec(trimmed);
|
|
701
|
-
if (m)
|
|
702
|
-
return `https://github.com/${m[1]}/${m[2]}`;
|
|
703
|
-
// http(s)://github.com/owner/repo[.git][/...]
|
|
704
|
-
m = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s?#]+?)(?:\.git)?(?:[/?#].*)?$/.exec(trimmed);
|
|
705
|
-
if (m)
|
|
706
|
-
return `https://github.com/${m[1]}/${m[2]}`;
|
|
707
|
-
return null;
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* 0770 — Walk parent directories from `startDir` looking for a `.git` entry
|
|
711
|
-
* (directory OR file — git worktrees use a `.git` file). Bails at the
|
|
712
|
-
* filesystem root or after `maxLevels` iterations. Returns the absolute
|
|
713
|
-
* path of the discovered git root, or null.
|
|
714
|
-
*/
|
|
715
|
-
export function walkUpForGitRoot(startDir, maxLevels = 12) {
|
|
716
|
-
let current = resolve(startDir);
|
|
717
|
-
for (let i = 0; i < maxLevels; i++) {
|
|
718
|
-
if (existsSync(join(current, ".git")))
|
|
719
|
-
return current;
|
|
720
|
-
const parent = dirname(current);
|
|
721
|
-
if (parent === current)
|
|
722
|
-
return null;
|
|
723
|
-
current = parent;
|
|
724
|
-
}
|
|
725
|
-
return null;
|
|
726
|
-
}
|
|
727
|
-
const authoredSourceLinkCache = new Map();
|
|
728
|
-
/**
|
|
729
|
-
* 0770 — Test-only helper to clear the module-level memoization cache so
|
|
730
|
-
* tests can isolate detection runs across `beforeEach`.
|
|
731
|
-
*/
|
|
732
|
-
export function resetAuthoredSourceLinkCache() {
|
|
733
|
-
authoredSourceLinkCache.clear();
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* 0770 — Detect source-repo provenance for a locally-authored skill (no
|
|
737
|
-
* lockfile entry). Walks for `.git`, reads `origin` remote, normalizes via
|
|
738
|
-
* `parseGithubRemote`, and computes `skillPath` from `git ls-files` (with a
|
|
739
|
-
* filesystem fallback for untracked SKILL.md files). Memoized per absolute
|
|
740
|
-
* skill dir for the eval-server process lifetime.
|
|
741
|
-
*
|
|
742
|
-
* All git invocations use `execFileSync` with explicit argv (no shell), a
|
|
743
|
-
* 1500ms hard timeout, and silenced stderr. Any error converts to
|
|
744
|
-
* `{null, null}` — `buildSkillMetadata` never throws because of git.
|
|
745
|
-
*/
|
|
746
|
-
export function detectAuthoredSourceLink(skillDir) {
|
|
747
|
-
const absDir = resolve(skillDir);
|
|
748
|
-
const cached = authoredSourceLinkCache.get(absDir);
|
|
749
|
-
if (cached)
|
|
750
|
-
return cached;
|
|
751
|
-
const compute = () => {
|
|
752
|
-
const gitRoot = walkUpForGitRoot(absDir);
|
|
753
|
-
if (!gitRoot)
|
|
754
|
-
return { repoUrl: null, skillPath: null };
|
|
755
|
-
let remote = "";
|
|
756
|
-
try {
|
|
757
|
-
remote = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
758
|
-
cwd: gitRoot,
|
|
759
|
-
timeout: 1500,
|
|
760
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
761
|
-
encoding: "utf-8",
|
|
762
|
-
}).trim();
|
|
763
|
-
}
|
|
764
|
-
catch {
|
|
765
|
-
return { repoUrl: null, skillPath: null };
|
|
766
|
-
}
|
|
767
|
-
const repoUrl = parseGithubRemote(remote);
|
|
768
|
-
if (!repoUrl)
|
|
769
|
-
return { repoUrl: null, skillPath: null };
|
|
770
|
-
let skillPath = null;
|
|
771
|
-
try {
|
|
772
|
-
const tracked = execFileSync("git", ["ls-files", "--full-name", "SKILL.md"], {
|
|
773
|
-
cwd: absDir,
|
|
774
|
-
timeout: 1500,
|
|
775
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
776
|
-
encoding: "utf-8",
|
|
777
|
-
}).trim();
|
|
778
|
-
if (tracked)
|
|
779
|
-
skillPath = tracked;
|
|
780
|
-
}
|
|
781
|
-
catch {
|
|
782
|
-
// fall through to filesystem fallback
|
|
783
|
-
}
|
|
784
|
-
if (!skillPath) {
|
|
785
|
-
// Filesystem fallback for untracked SKILL.md — same path the file will
|
|
786
|
-
// have on github.com once committed and pushed.
|
|
787
|
-
skillPath = relative(gitRoot, join(absDir, "SKILL.md")).replace(/\\/g, "/");
|
|
788
|
-
}
|
|
789
|
-
return { repoUrl, skillPath };
|
|
790
|
-
};
|
|
791
|
-
const result = compute();
|
|
792
|
-
authoredSourceLinkCache.set(absDir, result);
|
|
793
|
-
return result;
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* 0737 — Resolve the source-repo provenance (repoUrl + skillPath) for a
|
|
797
|
-
* skill by looking up its lockfile entry. Two precedences:
|
|
798
|
-
* 1. Explicit `sourceRepoUrl` / `sourceSkillPath` (set by `vskill install`
|
|
799
|
-
* after this change ships).
|
|
800
|
-
* 2. Legacy `source: github:owner/repo` string (every existing install).
|
|
801
|
-
*
|
|
802
|
-
* Lockfile entries are keyed by plugin name; for nested-layout plugins the
|
|
803
|
-
* skill dir basename and the lockfile key differ — fall back to the parent
|
|
804
|
-
* directory's basename when no exact match exists.
|
|
805
|
-
*
|
|
806
|
-
* 0770 — When no lockfile entry resolves provenance, fall through to
|
|
807
|
-
* `detectAuthoredSourceLink` which inspects the parent git repo's origin
|
|
808
|
-
* remote. Lockfile-derived values still take precedence to preserve
|
|
809
|
-
* install-time provenance when the workspace itself is a git repo.
|
|
810
|
-
*/
|
|
811
|
-
function resolveSourceLink(skillDir, root) {
|
|
812
|
-
const lock = readLockfile(root);
|
|
813
|
-
if (!lock)
|
|
814
|
-
return detectAuthoredSourceLink(skillDir);
|
|
815
|
-
const skillName = basename(skillDir);
|
|
816
|
-
const parentName = basename(dirname(skillDir));
|
|
817
|
-
const entry = lock.skills[skillName] ?? lock.skills[parentName];
|
|
818
|
-
if (!entry)
|
|
819
|
-
return detectAuthoredSourceLink(skillDir);
|
|
820
|
-
if (entry.sourceRepoUrl) {
|
|
821
|
-
return {
|
|
822
|
-
repoUrl: entry.sourceRepoUrl,
|
|
823
|
-
skillPath: entry.sourceSkillPath ?? "SKILL.md",
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
// Legacy derivation from `source: github:owner/repo`.
|
|
827
|
-
// 0743: We DO NOT blindly default `skillPath` to "SKILL.md" here. Multi-skill
|
|
828
|
-
// repos (vskill, marketingskills, etc.) hold the SKILL.md under a nested
|
|
829
|
-
// path, and the legacy `source` string carries no path information.
|
|
830
|
-
// Guessing "SKILL.md" produced confidently-wrong 404 anchors for every
|
|
831
|
-
// install from a multi-skill repo.
|
|
832
|
-
//
|
|
833
|
-
// 0773 hotfix: when the matched lockfile entry KEY equals the source repo
|
|
834
|
-
// basename (i.e. `vskill install anton-abyzov/greet-anton` keys the entry
|
|
835
|
-
// as `greet-anton` AND the repo is `greet-anton`), the repo IS the skill —
|
|
836
|
-
// SKILL.md sits at the repo root. Defaulting skillPath to "SKILL.md" in
|
|
837
|
-
// that exact shape restores the working SourceFileLink anchor for
|
|
838
|
-
// single-skill repos without re-introducing 404s for multi-skill repos.
|
|
839
|
-
const m = /^github:([^/]+)\/([^/#]+)/.exec(entry.source ?? "");
|
|
840
|
-
// 0770: do NOT fall through here — an installed skill with a non-github
|
|
841
|
-
// `source` (e.g. `marketplace:...`) is still installed, not authored. Local
|
|
842
|
-
// git detection would leak the workspace remote (umbrella, etc.).
|
|
843
|
-
if (!m)
|
|
844
|
-
return { repoUrl: null, skillPath: null };
|
|
845
|
-
const repoBasename = m[2];
|
|
846
|
-
const lockKey = lock.skills[skillName] ? skillName : parentName;
|
|
847
|
-
const isSingleSkillRepo = repoBasename === lockKey;
|
|
848
|
-
return {
|
|
849
|
-
repoUrl: `https://github.com/${m[1]}/${m[2]}`,
|
|
850
|
-
skillPath: entry.sourceSkillPath ?? (isSingleSkillRepo ? "SKILL.md" : null),
|
|
851
|
-
};
|
|
852
|
-
}
|
|
702
|
+
// 0809: parseGithubRemote, walkUpForGitRoot, detectAuthoredSourceLink,
|
|
703
|
+
// resolveSourceLink, and the authored-source-link cache live in
|
|
704
|
+
// `./source-link.ts`. Imported + re-exported here for backwards compatibility
|
|
705
|
+
// with existing consumers (`./skill-create-routes.ts`, the 0770 test file).
|
|
706
|
+
import { parseGithubRemote, walkUpForGitRoot, detectAuthoredSourceLink, resolveSourceLink, readCopiedSkillSidecar, resetAuthoredSourceLinkCache, resetCopiedSkillSidecarCache, } from "./source-link.js";
|
|
707
|
+
export { parseGithubRemote, walkUpForGitRoot, detectAuthoredSourceLink, resolveSourceLink, readCopiedSkillSidecar, resetAuthoredSourceLinkCache, resetCopiedSkillSidecarCache, };
|
|
853
708
|
/**
|
|
854
709
|
* Build the T-025 metadata payload for a single skill. Reads SKILL.md from
|
|
855
710
|
* disk if present; returns EMPTY_METADATA on any error so the /api/skills
|
|
@@ -878,6 +733,24 @@ export function buildSkillMetadata(skillDir, origin, root) {
|
|
|
878
733
|
const deps = toStringArrayOrNull(fm["skill-deps"] ?? fm.skillDeps ?? fm.deps);
|
|
879
734
|
const mcpDeps = toStringArrayOrNull(fm["mcp-deps"] ?? fm.mcpDeps ?? fm.mcpDependencies);
|
|
880
735
|
const tags = toStringArrayOrNull(fm.tags);
|
|
736
|
+
// 0815: assemble runtime + integrationTests from flat fields. Secrets stays
|
|
737
|
+
// a string[] (env-var names); purposes live in the skill's `.env.example`.
|
|
738
|
+
const secrets = toStringArrayOrNull(fm.secrets);
|
|
739
|
+
const runtimePython = toStringOrNull(fm["runtime-python"] ?? fm.runtimePython);
|
|
740
|
+
const runtimePip = toStringArrayOrNull(fm["runtime-pip"] ?? fm.runtimePip);
|
|
741
|
+
const runtimeNode = toStringOrNull(fm["runtime-node"] ?? fm.runtimeNode);
|
|
742
|
+
const runtime = runtimePython || runtimePip || runtimeNode
|
|
743
|
+
? { python: runtimePython, pip: runtimePip, node: runtimeNode }
|
|
744
|
+
: null;
|
|
745
|
+
const rawRunner = toStringOrNull(fm["integration-runner"] ?? fm.integrationRunner);
|
|
746
|
+
const integrationRunner = rawRunner === "vitest" || rawRunner === "pytest" || rawRunner === "none"
|
|
747
|
+
? rawRunner
|
|
748
|
+
: null;
|
|
749
|
+
const integrationFile = toStringOrNull(fm["integration-file"] ?? fm.integrationFile);
|
|
750
|
+
const integrationRequires = toStringArrayOrNull(fm["integration-requires"] ?? fm.integrationRequires);
|
|
751
|
+
const integrationTests = integrationRunner
|
|
752
|
+
? { runner: integrationRunner, file: integrationFile, requires: integrationRequires }
|
|
753
|
+
: null;
|
|
881
754
|
return {
|
|
882
755
|
description: toStringOrNull(fm.description),
|
|
883
756
|
version: toStringOrNull(fm.version),
|
|
@@ -894,6 +767,9 @@ export function buildSkillMetadata(skillDir, origin, root) {
|
|
|
894
767
|
sourceAgent: deriveSourceAgent(skillDir, root, origin),
|
|
895
768
|
repoUrl: sourceLink.repoUrl,
|
|
896
769
|
skillPath: sourceLink.skillPath,
|
|
770
|
+
secrets,
|
|
771
|
+
runtime,
|
|
772
|
+
integrationTests,
|
|
897
773
|
};
|
|
898
774
|
}
|
|
899
775
|
// ---------------------------------------------------------------------------
|