fix(skills): expose authored skill to repository discovery

This commit is contained in:
morluto
2026-10-10 14:20:22 +08:00
parent c89c8f5318
commit 696fb834b8
18 changed files with 119 additions and 14 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ intentionally not included. -->
- Provider, bridge, target-format, or platform compatibility:
- Evidence, artifact, provenance, or reconstruction contract:
- Process execution, authorization, cleanup, or containment impact:
- Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `skill-src/` and never commit build outputs):
- Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs):
## Evidence and regression coverage
+1 -1
View File
@@ -35,7 +35,7 @@ Use [.nvmrc](.nvmrc) and `package.json#packageManager` for the pinned toolchain.
Use relevant checks for the change. The complete local gate, `npm run check:pr`, is optional for broad changes or CI diagnosis. See [CONTRIBUTING.md](CONTRIBUTING.md) for development, generated-file ownership, and release conventions.
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `skill-src/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
`docs/public/product-catalog.json`, `docs/verification/managed-conformance-*.json`, and `skills/` are ignored outputs. Edit source contracts and `.agents/skills/reverse-engineer-anything/`, then regenerate as needed. `.cache/mcp-tool-catalog.json` is test metadata; runtime builds and source checking do not consume it. Never commit binaries, provider project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts.
## Tool and Test Changes
+6 -2
View File
@@ -95,8 +95,12 @@ Pre-commit formats and lints staged files; pre-push runs `check:fast`.
`evidence:generate` prepares only the managed commitment and its runtime/skill
dependencies. The ignored outputs are `docs/public/product-catalog.json`,
`docs/verification/managed-conformance-*.json`, and `skills/`. Edit authored
skill instructions in `skill-src/`. CI validates generated outputs and retains
them as artifacts; do not commit them. Reviewed source metadata, including
skill instructions in `.agents/skills/reverse-engineer-anything/`, where Skills
can discover the public bundle directly from a clean checkout. `verify:package`
installs that bundle with Skills 1.7.2 using default repository-root discovery
and checks every installed file, alongside the packaged setup checks.
CI validates generated outputs and retains them as artifacts; do not commit
them. Reviewed source metadata, including
`src/generatedPackageMetadata.ts` and `docs/error-contract.schema.json`, remains
tracked and checked for freshness.
+1 -1
View File
@@ -95,7 +95,7 @@ npx skills add morluto/rea --skill reverse-engineer-anything
This installs agent instructions and bundled references, not REA MCP
registration or analysis engines. Follow the skill's
[conditional connection guide](https://github.com/morluto/rea/blob/main/skill-src/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
[conditional connection guide](https://github.com/morluto/rea/blob/main/.agents/skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
Working tools can be used immediately. If tools are missing, inspect the current
client's registration with `doctor --client codex --json` (substitute its client
ID), then plan repairs with `setup --client codex --dry-run --json`. Show and
+1 -1
View File
@@ -17,7 +17,7 @@ const lock = JSON.parse(
await readFile(join(root, "package-lock.json"), "utf8"),
);
const skill = await readFile(
join(root, "skill-src/reverse-engineer-anything/SKILL.md"),
join(root, ".agents/skills/reverse-engineer-anything/SKILL.md"),
"utf8",
);
const skillVersion = /^\s{2}version:\s*"([^"]+)"\s*$/mu.exec(skill)?.[1];
+2 -2
View File
@@ -10,7 +10,7 @@ for (const argument of arguments_)
throw new Error(`Unknown skill metadata option: ${argument}`);
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const sourceRoot = join(root, "skill-src/reverse-engineer-anything");
const sourceRoot = join(root, ".agents/skills/reverse-engineer-anything");
const outputRoot = join(root, "skills/reverse-engineer-anything");
const check = arguments_.has("--check");
const current = (
@@ -25,7 +25,7 @@ const { PRODUCT_IDENTITY } = await import(
);
if (/^\s{2}(?:tool_count|catalog_digest):/mu.test(current))
throw new Error(
"Catalog metadata belongs in the generated skill, not skill-src",
"Catalog metadata belongs in the generated skill, not the authored source",
);
const versionLine = /^ {2}version: "[^"\r\n]+"$/mu;
if (!versionLine.test(current))
+1 -1
View File
@@ -38,7 +38,7 @@ export async function verifyPackagePack({ root, workspace }) {
throw new Error("package did not expose both rea command entry points");
if (packedManifest.mcpName !== "io.github.morluto/rea")
throw new Error("package did not retain its MCP Registry ownership marker");
if (packedFiles.some((path) => path.startsWith("package/skill-src/")))
if (packedFiles.some((path) => path.startsWith("package/.agents/")))
throw new Error(
"package included authored skill sources instead of only the generated bundle",
);
+4
View File
@@ -17,6 +17,7 @@ import { verifyPackageMcp } from "./verify-package-mcp.mjs";
import { verifyPackagePack } from "./verify-package-pack.mjs";
import { verifyPackagePlatform } from "./verify-package-platform.mjs";
import { verifyPackageSetup } from "./verify-package-setup.mjs";
import { verifyRepositorySkill } from "./verify-repository-skill.mjs";
import { verifyManaged } from "./verify-package-managed.mjs";
import { verifyUnknownProvider } from "./verify-package-unknown-provider.mjs";
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
@@ -34,9 +35,11 @@ let tarball;
let mcpStartup;
let mcpModuleLoading;
let update;
let repositorySkill;
let report;
try {
repositorySkill = await verifyRepositorySkill({ root, workspace });
({ tarball } = await verifyPackagePack({ root, workspace }));
const environmentData = await verifyPackageEnvironment({
root,
@@ -156,6 +159,7 @@ try {
configSymlinkLifecycle: supportedSetupHost,
skill: supportedSetupHost,
skillReferences: supportedSetupHost,
repositorySkill,
mcpTools: TOOL_CONTRACTS.length,
mcpPrompts: prompts.names.length,
promptCompletion: true,
+97
View File
@@ -0,0 +1,97 @@
import { cp, mkdir, readFile, readdir } from "node:fs/promises";
import { dirname, join, relative } from "node:path";
import { exec, pathExists } from "./lib/verify-package-core.mjs";
/** Exercise Skills' default root discovery without ignored build outputs. */
export async function verifyRepositorySkill({ root, workspace }) {
const snapshot = join(workspace, "repository");
const consumer = join(workspace, "skill-consumer");
const home = join(workspace, "skill-consumer-home");
const tracked = (
await exec(
"git",
["ls-files", "--cached", "--format=%(objectmode) %(path)", "-z"],
{ cwd: root },
)
).stdout
.split("\0")
.filter(Boolean);
for (const entry of tracked) {
const mode = entry.slice(0, 6);
const path = entry.slice(7);
const destination = join(snapshot, path);
// A GitHub clone without submodule initialization has only these directories.
if (mode === "160000") {
await mkdir(destination, { recursive: true });
continue;
}
await mkdir(dirname(destination), { recursive: true });
await cp(join(root, path), destination, { verbatimSymlinks: true });
}
if (await pathExists(join(snapshot, "skills")))
throw new Error("repository skill check must run without generated skills");
await mkdir(consumer, { recursive: true });
await mkdir(home, { recursive: true });
const skillsVersion = "1.7.2";
await exec(
"npm",
[
"exec",
"--yes",
"--package",
`skills@${skillsVersion}`,
"--",
"skills",
"add",
snapshot,
"--skill",
"reverse-engineer-anything",
"--agent",
"codex",
"--yes",
],
{
cwd: consumer,
env: {
...process.env,
HOME: home,
USERPROFILE: home,
XDG_CONFIG_HOME: join(home, ".config"),
CODEX_HOME: join(home, ".codex"),
DO_NOT_TRACK: "1",
SKILLS_NO_TELEMETRY: "1",
CI: "1",
},
timeout: 120_000,
},
);
const authoredRoot = join(
snapshot,
".agents/skills/reverse-engineer-anything",
);
const installedSkills = join(consumer, ".agents/skills");
const installedRoot = join(installedSkills, "reverse-engineer-anything");
const files = await filePaths(authoredRoot);
if (
JSON.stringify(await readdir(installedSkills)) !==
JSON.stringify(["reverse-engineer-anything"]) ||
JSON.stringify(await filePaths(installedRoot)) !== JSON.stringify(files)
)
throw new Error(
"Skills did not install exactly the selected public bundle",
);
for (const path of files) {
const authored = await readFile(join(authoredRoot, path));
if (!authored.equals(await readFile(join(installedRoot, path))))
throw new Error(`Skills changed authored skill content: ${path}`);
}
return { skillsVersion, files };
}
async function filePaths(root) {
return (await readdir(root, { recursive: true, withFileTypes: true }))
.filter((entry) => entry.isFile())
.map((entry) => relative(root, join(entry.parentPath, entry.name)))
.sort();
}
@@ -169,7 +169,7 @@ const createBuildCacheFixture = async (): Promise<string> => {
"scripts/clean-build-output.mjs",
"scripts/generate-package-metadata.mjs",
"scripts/lib/generated-file.mjs",
"skill-src/reverse-engineer-anything/SKILL.md",
".agents/skills/reverse-engineer-anything/SKILL.md",
"src/generatedPackageMetadata.ts",
];
await Promise.all(
@@ -21,7 +21,7 @@ async function fixture(eol = "\n") {
await mkdir(dirname(join(root, path)), { recursive: true });
await copyFile(join(repositoryRoot, path), join(root, path));
}
const sourceRoot = join(root, "skill-src/reverse-engineer-anything");
const sourceRoot = join(root, ".agents/skills/reverse-engineer-anything");
const outputRoot = join(root, "skills/reverse-engineer-anything");
await mkdir(join(sourceRoot, "references"), { recursive: true });
await mkdir(join(root, "dist"));
+3 -3
View File
@@ -39,7 +39,7 @@
"package-lock.json",
"scripts/generate-package-metadata.mjs",
"scripts/lib/generated-file.mjs",
"skill-src/reverse-engineer-anything/SKILL.md",
".agents/skills/reverse-engineer-anything/SKILL.md",
"src/generatedPackageMetadata.ts"
]
},
@@ -138,7 +138,7 @@
"docs/**",
"!docs/public/product-catalog.json",
"!docs/verification/managed-conformance-*.json",
"skill-src/**",
".agents/skills/reverse-engineer-anything/**",
"README.md",
{
"mode": "dependencyOutputs",
@@ -170,7 +170,7 @@
"package-lock.json",
"scripts/generate-skill-metadata.mjs",
"scripts/lib/generated-file.mjs",
"skill-src/**"
".agents/skills/reverse-engineer-anything/**"
],
"outputs": ["skills/**"]
},