aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
195 lines • 7.11 kB
JavaScript
/**
* Upstream Artifact Registry
*
* Scans the framework root for upstream artifacts (agents, skills, rules,
* commands) under `agentic/code/{frameworks,addons}/*` and returns an
* id-indexed registry. Each entry carries the `safety-critical` flag (parsed
* from frontmatter for `.md` artifacts and from `manifest.json` at the bundle
* level), the source kind ('bundled' for npm-shipped, 'cache' for git-installed),
* and the absolute source path.
*
* Consumed by the shadow resolver (#1036) to detect collisions between project-
* local bundles and upstream artifacts and apply the override / safety-critical
* denylist policy from `.aiwg/architecture/adr-override-shadow-policy.md`.
*
* @implements #1036
*/
import { readFile, readdir, stat } from 'fs/promises';
import { join, basename } from 'path';
/** Subdir name → singular type, for the four artifact kinds the resolver cares about. */
const ARTIFACT_DIRS = {
agents: 'agent',
skills: 'skill',
rules: 'rule',
commands: 'command',
};
const FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---/;
const ID_LINE_RE = /^id\s*:\s*['"]?([^'"\n]+?)['"]?\s*$/m;
const NAME_LINE_RE = /^name\s*:\s*['"]?([^'"\n]+?)['"]?\s*$/m;
// `safety-critical: true` (also accepts safety_critical for snake_case tolerance)
const SAFETY_CRITICAL_RE = /^(?:safety-critical|safety_critical)\s*:\s*true\s*$/m;
/** Parse YAML frontmatter for the small set of fields we care about. Returns
* `{}` if no frontmatter or no recognized field. We avoid pulling in a YAML
* dependency for this single-purpose scan. */
function parseFrontmatterFields(raw) {
const match = FRONTMATTER_RE.exec(raw);
if (!match)
return { safetyCritical: false };
const body = match[1];
const idMatch = ID_LINE_RE.exec(body);
const nameMatch = NAME_LINE_RE.exec(body);
return {
id: idMatch?.[1]?.trim(),
name: nameMatch?.[1]?.trim(),
safetyCritical: SAFETY_CRITICAL_RE.test(body),
};
}
async function readSafe(path) {
try {
return await readFile(path, 'utf-8');
}
catch {
return null;
}
}
async function isDir(path) {
try {
const st = await stat(path);
return st.isDirectory();
}
catch {
return false;
}
}
/** Read the bundle's manifest.json (if any) and pull `safety-critical: true` —
* a bundle-level flag opts in every artifact in the bundle, per ADR §2. */
async function readBundleSafetyCritical(bundleDir) {
const raw = await readSafe(join(bundleDir, 'manifest.json'));
if (!raw)
return false;
try {
const parsed = JSON.parse(raw);
return parsed['safety-critical'] === true;
}
catch {
return false;
}
}
/** Scan a single artifact-type directory inside a bundle and emit artifacts. */
async function scanArtifactDir(artifactDir, type, bundleName, source, bundleSafetyCritical) {
const out = [];
let entries;
try {
entries = await readdir(artifactDir);
}
catch {
return out;
}
if (type === 'skill') {
// Skills are subdirectories — id = directory name; safety-critical from
// SKILL.md frontmatter or bundle-level flag.
for (const entry of entries) {
const skillDir = join(artifactDir, entry);
if (!(await isDir(skillDir)))
continue;
const skillMd = await readSafe(join(skillDir, 'SKILL.md'));
const fm = skillMd ? parseFrontmatterFields(skillMd) : { safetyCritical: false };
out.push({
id: fm.name ?? fm.id ?? entry,
type,
sourcePath: skillDir,
bundleName,
source,
safetyCritical: bundleSafetyCritical || fm.safetyCritical,
});
}
return out;
}
// agents / rules / commands — flat .md files.
for (const entry of entries) {
if (!entry.endsWith('.md'))
continue;
if (entry === 'README.md' || entry === 'RULES-INDEX.md' || entry === 'INDEX.md')
continue;
const filePath = join(artifactDir, entry);
const raw = await readSafe(filePath);
const fm = raw ? parseFrontmatterFields(raw) : { safetyCritical: false };
const fallbackId = basename(entry, '.md');
out.push({
id: fm.id ?? fm.name ?? fallbackId,
type,
sourcePath: filePath,
bundleName,
source,
safetyCritical: bundleSafetyCritical || fm.safetyCritical,
});
}
return out;
}
/** Scan one bundle directory across all four artifact types. */
async function scanBundle(bundleDir, source) {
const bundleName = basename(bundleDir);
const bundleSafetyCritical = await readBundleSafetyCritical(bundleDir);
const out = [];
for (const [dirName, type] of Object.entries(ARTIFACT_DIRS)) {
const artifactDir = join(bundleDir, dirName);
if (!(await isDir(artifactDir)))
continue;
const found = await scanArtifactDir(artifactDir, type, bundleName, source, bundleSafetyCritical);
out.push(...found);
}
return out;
}
/** Scan all `agentic/code/{frameworks,addons}/*` bundles under the framework
* root (npm-bundled artifacts) plus an optional cache directory for git-
* installed artifacts. */
export async function buildUpstreamRegistry(opts) {
const { frameworkRoot, cacheRoot } = opts;
const all = [];
for (const containerDir of ['agentic/code/frameworks', 'agentic/code/addons']) {
const root = join(frameworkRoot, containerDir);
let bundleNames;
try {
bundleNames = await readdir(root);
}
catch {
continue;
}
for (const bundleName of bundleNames) {
const bundleDir = join(root, bundleName);
if (!(await isDir(bundleDir)))
continue;
all.push(...(await scanBundle(bundleDir, 'bundled')));
}
}
if (cacheRoot) {
try {
const entries = await readdir(cacheRoot);
for (const entry of entries) {
const bundleDir = join(cacheRoot, entry);
if (!(await isDir(bundleDir)))
continue;
all.push(...(await scanBundle(bundleDir, 'cache')));
}
}
catch {
// Cache root absent — fine.
}
}
const byKey = new Map();
const byId = new Map();
for (const art of all) {
const key = `${art.type}:${art.id}`;
// Precedence within upstream: cache > bundled (git-installed wins over npm
// bundle, per ADR §1). First write wins after we sort cache-first.
const existing = byKey.get(key);
if (!existing || (art.source === 'cache' && existing.source === 'bundled')) {
byKey.set(key, art);
}
const list = byId.get(art.id) ?? [];
list.push(art);
byId.set(art.id, list);
}
return { byKey, byId };
}
//# sourceMappingURL=upstream-registry.js.map