loadout-ai 0.9.4 → 0.9.5
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/CHANGELOG.md +14 -0
- package/README.md +17 -5
- package/catalog/discovered.json +78352 -41856
- package/dist/src/core/catalog/skills.js +61 -3
- package/dist/src/core/workspace/active-policy.js +1 -1
- package/docs/DISCOVERED.md +249 -249
- package/docs/superpowers/plans/2026-09-08-fast-launch-demo.md +65 -0
- package/docs/superpowers/plans/2026-09-08-maximum-curator-demo-recording.md +247 -0
- package/docs/superpowers/specs/2026-09-08-fast-launch-demo-design.md +42 -0
- package/docs/superpowers/specs/2026-09-08-maximum-curator-demo-design.md +73 -0
- package/docs/superpowers/specs/2026-09-08-social-launch-copy-design.md +43 -0
- package/package.json +4 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { cp, lstat, readdir, readFile, rm } from "node:fs/promises";
|
|
2
|
-
import {
|
|
1
|
+
import { cp, lstat, mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
3
4
|
import { ensureDirectory } from "../agents/paths.js";
|
|
4
5
|
import { assertSkillSecurity, scanSkillSecurity } from "./skill-security.js";
|
|
5
6
|
export async function discoverSkillDirectories(root, options = {}) {
|
|
@@ -82,6 +83,60 @@ export async function discoverSkillDirectories(root, options = {}) {
|
|
|
82
83
|
await visit(root, 0);
|
|
83
84
|
return result;
|
|
84
85
|
}
|
|
86
|
+
// A link such as `references/security-checklist.md`, not a deeper path like
|
|
87
|
+
// `docs/references/x.md`, which the lookbehind leaves to its own directory.
|
|
88
|
+
const SHARED_REFERENCE = /(?<![\w./-])references\/([A-Za-z0-9][\w.-]*\.md)\b/g;
|
|
89
|
+
async function isRegularFile(path) {
|
|
90
|
+
try {
|
|
91
|
+
const info = await lstat(path);
|
|
92
|
+
return info.isFile() && !info.isSymbolicLink();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Some repositories keep shared checklists in a root `references/` folder that
|
|
100
|
+
* their skills link to relatively. A skill copied on its own would point at
|
|
101
|
+
* nothing, so plan from a staged copy that carries those files beside it.
|
|
102
|
+
*/
|
|
103
|
+
async function withSharedReferences(skill, packageRoot) {
|
|
104
|
+
if (skill === packageRoot)
|
|
105
|
+
return skill;
|
|
106
|
+
const text = await readFile(join(skill, "SKILL.md"), "utf8");
|
|
107
|
+
const names = new Set([...text.matchAll(SHARED_REFERENCE)].map((m) => m[1]));
|
|
108
|
+
const sharedRoot = join(packageRoot, "references");
|
|
109
|
+
// lstat checks only the last path segment, so a symlinked folder would let a
|
|
110
|
+
// package copy files from anywhere on disk into the skill.
|
|
111
|
+
const rootInfo = await lstat(sharedRoot).catch(() => undefined);
|
|
112
|
+
if (!rootInfo?.isDirectory() || rootInfo.isSymbolicLink())
|
|
113
|
+
return skill;
|
|
114
|
+
const shared = [];
|
|
115
|
+
for (const name of names) {
|
|
116
|
+
if (!name || (await isRegularFile(join(skill, "references", name))))
|
|
117
|
+
continue;
|
|
118
|
+
const candidate = join(sharedRoot, name);
|
|
119
|
+
if (await isRegularFile(candidate))
|
|
120
|
+
shared.push(candidate);
|
|
121
|
+
}
|
|
122
|
+
if (!shared.length)
|
|
123
|
+
return skill;
|
|
124
|
+
// ponytail: staged copies stay in the OS temp dir until it is cleaned; track and remove them if installs become frequent
|
|
125
|
+
const staged = join(await mkdtemp(join(tmpdir(), "loadout-skill-")), basename(skill));
|
|
126
|
+
await cp(skill, staged, {
|
|
127
|
+
recursive: true,
|
|
128
|
+
errorOnExist: true,
|
|
129
|
+
force: false,
|
|
130
|
+
});
|
|
131
|
+
await ensureDirectory(join(staged, "references"));
|
|
132
|
+
for (const file of shared)
|
|
133
|
+
await cp(file, join(staged, "references", basename(file)), {
|
|
134
|
+
errorOnExist: true,
|
|
135
|
+
force: false,
|
|
136
|
+
});
|
|
137
|
+
await validateSkillDirectory(staged);
|
|
138
|
+
return staged;
|
|
139
|
+
}
|
|
85
140
|
function safeTarget(root, target) {
|
|
86
141
|
const resolvedRoot = resolve(root);
|
|
87
142
|
const resolvedTarget = resolve(target);
|
|
@@ -95,6 +150,9 @@ export async function planSkillInstall(sourceRoot, targetDirectories, packageId,
|
|
|
95
150
|
const skills = await discoverSkillDirectories(sourceRoot, options);
|
|
96
151
|
if (skills.length === 0)
|
|
97
152
|
throw new Error(`No SKILL.md found under ${sourceRoot}`);
|
|
153
|
+
const sources = new Map();
|
|
154
|
+
for (const skill of skills)
|
|
155
|
+
sources.set(skill, await withSharedReferences(skill, sourceRoot));
|
|
98
156
|
const files = [];
|
|
99
157
|
for (const targetRoot of targetDirectories) {
|
|
100
158
|
try {
|
|
@@ -116,7 +174,7 @@ export async function planSkillInstall(sourceRoot, targetDirectories, packageId,
|
|
|
116
174
|
const target = safeTarget(targetRoot, join(targetRoot, name));
|
|
117
175
|
const frontmatter = await readFile(join(skill, "SKILL.md"), "utf8");
|
|
118
176
|
const skillName = frontmatter.match(/^name:\s*(\S+)/m)?.[1];
|
|
119
|
-
files.push({ source: skill, target, skillName });
|
|
177
|
+
files.push({ source: sources.get(skill) ?? skill, target, skillName });
|
|
120
178
|
}
|
|
121
179
|
}
|
|
122
180
|
const conflicts = detectInstallConflicts([
|
|
@@ -34,7 +34,7 @@ const SPECIALIZED_WITHOUT_SIGNAL = /(?:appstore|msstore|backtesting|bats-|oracle
|
|
|
34
34
|
function hasProjectCompatibility(name, project) {
|
|
35
35
|
const hasLanguage = (language) => project.languages.includes(language);
|
|
36
36
|
const languageRequirements = [
|
|
37
|
-
[/(?:dotnet|csharp|aspnet|nuget|maui)/, ".net"],
|
|
37
|
+
[/(?:dotnet|csharp|aspnet|nuget|maui|blazor)/, ".net"],
|
|
38
38
|
[
|
|
39
39
|
/(?:python|pytest|fastapi|django|flask|pydantic|poetry|uv-package)/,
|
|
40
40
|
"python",
|