feat(sproutclaw): modularize webui, extensions, and agent config layout
Some checks failed
CI / build-check-test (push) Has been cancelled
Some checks failed
CI / build-check-test (push) Has been cancelled
Restructure local extensions into per-feature directories, split WebUI into backend modules with slash commands and systemd support, and track prompts/skills under .pi/agent for portable Gitea deployment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,9 +5,14 @@ import { type ExecutionEnv, type FileInfo, type Result, type Skill, toError } fr
|
||||
const MAX_NAME_LENGTH = 64;
|
||||
const MAX_DESCRIPTION_LENGTH = 1024;
|
||||
const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
|
||||
const IGNORED_ROOT_SKILL_MARKDOWN = new Set(["README.md"]);
|
||||
|
||||
type IgnoreMatcher = ReturnType<typeof ignore>;
|
||||
|
||||
function isIgnoredRootSkillMarkdown(fileName: string): boolean {
|
||||
return IGNORED_ROOT_SKILL_MARKDOWN.has(fileName) || fileName.toLowerCase() === "readme.md";
|
||||
}
|
||||
|
||||
export type SkillDiagnosticCode =
|
||||
| "file_info_failed"
|
||||
| "list_failed"
|
||||
@@ -165,7 +170,8 @@ async function loadSkillsFromDirInternal(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (kind !== "file" || !includeRootFiles || !entry.name.endsWith(".md")) continue;
|
||||
if (kind !== "file" || !includeRootFiles || !entry.name.endsWith(".md") || isIgnoredRootSkillMarkdown(entry.name))
|
||||
continue;
|
||||
const result = await loadSkillFromFile(env, fullPath);
|
||||
if (result.skill) skills.push(result.skill);
|
||||
diagnostics.push(...result.diagnostics);
|
||||
@@ -234,6 +240,10 @@ async function loadSkillFromFile(
|
||||
env: ExecutionEnv,
|
||||
filePath: string,
|
||||
): Promise<{ skill: Skill | null; diagnostics: SkillDiagnostic[] }> {
|
||||
if (isIgnoredRootSkillMarkdown(basenameEnvPath(filePath))) {
|
||||
return { skill: null, diagnostics: [] };
|
||||
}
|
||||
|
||||
const diagnostics: SkillDiagnostic[] = [];
|
||||
const rawContent = await env.readTextFile(filePath);
|
||||
if (!rawContent.ok) {
|
||||
|
||||
@@ -113,4 +113,20 @@ Use this skill.
|
||||
expect(skills.map((skill) => skill.name)).toEqual(["skills"]);
|
||||
expect(skills[0]?.content).toBe("Root content");
|
||||
});
|
||||
|
||||
it("ignores README.md at the skills directory root", async () => {
|
||||
const root = createTempDir();
|
||||
const env = new NodeExecutionEnv({ cwd: root });
|
||||
await env.createDir("skills/example", { recursive: true });
|
||||
await env.writeFile("skills/README.md", "# Skills\n\nDirectory documentation.");
|
||||
await env.writeFile(
|
||||
"skills/example/SKILL.md",
|
||||
"---\nname: example\ndescription: Example skill\n---\nUse this skill.",
|
||||
);
|
||||
|
||||
const { skills, diagnostics } = await loadSkills(env, "skills");
|
||||
|
||||
expect(diagnostics).toEqual([]);
|
||||
expect(skills.map((skill) => skill.name)).toEqual(["example"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user