UNPKG

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

305 lines 10.7 kB
/** * Artifact Index Types * * Shared TypeScript types for the artifact indexing system. * Used by index-builder, query-engine, dep-graph, and stats modules. * * @implements #420 * @source @src/artifacts/cli.ts * @tests @test/unit/artifacts/index-builder.test.ts */ import fs from 'fs'; import { load as loadYaml } from 'js-yaml'; /** * Normalize a raw edge value to TypedEdge. * Handles backward compatibility: plain strings become { path, type: "depends-on" }. */ export function normalizeEdge(edge) { if (typeof edge === 'string') return { path: edge, type: 'depends-on' }; return edge; } /** * Normalize an array of raw edges to TypedEdge[]. */ export function normalizeEdges(edges) { return edges.map(normalizeEdge); } /** * Phase name to directory mapping */ export const PHASE_DIRECTORIES = { requirements: '.aiwg/requirements', architecture: '.aiwg/architecture', testing: '.aiwg/testing', security: '.aiwg/security', deployment: '.aiwg/deployment', risks: '.aiwg/risks', planning: '.aiwg/planning', intake: '.aiwg/intake', reports: '.aiwg/reports', }; /** * Default index output directory */ export const INDEX_DIR = '.aiwg/.index'; /** * Current index format version */ export const INDEX_VERSION = '1.0.0'; /** * Built-in graph definitions */ export const BUILTIN_GRAPH_CONFIGS = { framework: { type: 'framework', // Includes `agentic/code/extensions` and `agentic/code/behaviors` // (#1221) so extension bundles (sys, net, it, sec, stream, dev) and // top-level behaviors (concierge, security-sentinel, ...) appear in // `aiwg discover` alongside frameworks and addons. `inferType()` in // index-builder.ts uses nearest-type-dir ancestor matching to handle // every nested layout (slug vs flat, frameworks/<f>/extensions/<sub>, // research-complete/elaboration/{agents,commands}, etc.). scanDirs: [ 'agentic/code/frameworks', 'agentic/code/addons', 'agentic/code/extensions', 'agentic/code/agents', 'agentic/code/behaviors', 'docs', ], extensions: ['.md', '.yaml', '.json'], shared: true, // Not built by `aiwg index build` (no flag) — that would write to // the shared XDG location from any project. Freshness is guaranteed // by the explicit post-deploy rebuild in `useHandler` (#1212/#1214) // and by `aiwg index build --graph framework` for manual rebuilds. defaultBuild: false, }, project: { type: 'project', scanDirs: ['.aiwg'], extensions: ['.md', '.yaml', '.json'], shared: false, defaultBuild: true, }, codebase: { type: 'codebase', scanDirs: ['src', 'test', 'tools'], extensions: ['.ts', '.mts', '.js', '.mjs', '.json', '.yaml'], shared: false, defaultBuild: true, }, }; /** * Mutable graph configs — starts with built-ins, extended by user config * * @implements #426 */ export const GRAPH_CONFIGS = { ...BUILTIN_GRAPH_CONFIGS }; /** * Normalize metadataSupplements entries. * * Accepts the shorthand `match: "frontmatter.<field>"` and expands it to * `matchOn` + `nodeKey`. This lets users write the compact form in config: * * match: frontmatter.ref * * instead of the explicit two-field form: * * matchOn: frontmatter.ref * nodeKey: ref * * @implements #738 */ function normalizeSupplements(raw) { return raw.map((entry) => { let matchOn = entry.matchOn; let nodeKey = entry.nodeKey; // Accept "match" shorthand: derive matchOn and nodeKey from it if (!matchOn && typeof entry.match === 'string') { matchOn = entry.match; } // Derive nodeKey from matchOn if not explicitly provided // e.g. "frontmatter.ref" -> nodeKey "ref" if (!nodeKey && matchOn) { nodeKey = matchOn.replace(/^frontmatter\./, ''); } return { scanDir: entry.scanDir, matchOn: matchOn ?? '', nodeKey: nodeKey ?? '', mergeFields: Array.isArray(entry.mergeFields) ? entry.mergeFields : [], }; }); } /** * Parse a raw graph definition object into a GraphConfig. * * Shared between loadUserGraphConfigs and loadModuleGraphConfigs. */ function parseGraphDef(name, graphDef) { if (!Array.isArray(graphDef.scanDirs)) return null; return { type: name, scanDirs: graphDef.scanDirs, extensions: Array.isArray(graphDef.extensions) ? graphDef.extensions : ['.md', '.yaml', '.json'], shared: graphDef.shared === true, defaultBuild: graphDef.defaultBuild !== false, edgeExtraction: graphDef.edgeExtraction, nodeStrategy: graphDef.nodeStrategy, filenamePattern: typeof graphDef.filenamePattern === 'string' ? graphDef.filenamePattern : undefined, metadataSupplements: Array.isArray(graphDef.metadataSupplements) ? normalizeSupplements(graphDef.metadataSupplements) : undefined, graphBackend: typeof graphDef.graphBackend === 'string' ? graphDef.graphBackend : undefined, }; } /** * Load graph configs declared in framework/addon manifest.json files. * * Reads `.aiwg/frameworks/registry.json` to find installed modules, * then loads each module's manifest and merges `index.graphs` declarations * into GRAPH_CONFIGS. Module-declared graphs cannot override built-in names. * * This runs before operator config so that .aiwg/config.yaml can override * module-declared graphs. * * @param cwd - Project root directory * @returns Names of module-declared graphs that were loaded * * @implements #726 */ export function loadModuleGraphConfigs(cwd) { const registryPath = `${cwd}/.aiwg/frameworks/registry.json`; const loaded = []; try { if (!fs.existsSync(registryPath)) return loaded; const registryContent = fs.readFileSync(registryPath, 'utf-8'); const registry = JSON.parse(registryContent); if (!Array.isArray(registry.frameworks)) return loaded; // Search paths for manifest.json (framework source locations) const searchRoots = [ `${cwd}/agentic/code/frameworks`, `${cwd}/agentic/code/addons`, ]; for (const entry of registry.frameworks) { const id = entry.id; let manifestData = null; // Try each search root to find the manifest for (const root of searchRoots) { const manifestPath = `${root}/${id}/manifest.json`; if (fs.existsSync(manifestPath)) { try { manifestData = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); } catch { // Malformed manifest — skip } break; } } if (!manifestData) continue; // Extract index.graphs from manifest const indexSection = manifestData.index; if (!indexSection || typeof indexSection !== 'object') continue; const graphs = indexSection.graphs; if (!graphs || typeof graphs !== 'object') continue; for (const [name, def] of Object.entries(graphs)) { if (name in BUILTIN_GRAPH_CONFIGS) continue; // Module graphs don't override already-loaded graphs (first module wins) if (name in GRAPH_CONFIGS && !(name in BUILTIN_GRAPH_CONFIGS)) continue; const config = parseGraphDef(name, def); if (config) { GRAPH_CONFIGS[name] = config; loaded.push(name); } } } } catch { // Module config loading is best-effort } return loaded; } /** * Load user-defined graph configs from .aiwg/config.yaml * * Also loads module-declared graphs from installed framework manifests. * Module graphs are loaded first; operator config overrides them. * Neither can override built-in graph names. * * @param cwd - Project root directory * @returns Names of user-defined graphs that were loaded (includes module graphs) * * @implements #426 #726 */ export function loadUserGraphConfigs(cwd) { // Load module-declared graphs first (frameworks/addons) const moduleLoaded = loadModuleGraphConfigs(cwd); const configPath = `${cwd}/.aiwg/config.yaml`; const loaded = [...moduleLoaded]; try { if (!fs.existsSync(configPath)) return loaded; const content = fs.readFileSync(configPath, 'utf-8'); const config = loadYaml(content); if (!config || typeof config !== 'object') return loaded; const indexConfig = config.index; if (!indexConfig || typeof indexConfig !== 'object') return loaded; const graphs = indexConfig.graphs; if (!graphs || typeof graphs !== 'object') return loaded; for (const [name, def] of Object.entries(graphs)) { if (name in BUILTIN_GRAPH_CONFIGS) { // Cannot override built-in graph names continue; } const graphConfig = parseGraphDef(name, def); if (!graphConfig) continue; // Operator config overrides module-declared graphs (emit warning if overriding) if (moduleLoaded.includes(name)) { // Operator override of a module-declared graph GRAPH_CONFIGS[name] = graphConfig; // Already in loaded list from module phase } else { GRAPH_CONFIGS[name] = graphConfig; loaded.push(name); } } } catch { // Config loading is best-effort } return loaded; } /** * Get the index output directory for a given graph type * * @param cwd - Project root * @param graphType - Graph type * @returns Absolute path to the graph's index directory */ export function getGraphIndexDir(cwd, graphType) { if (graphType === 'framework') { // Shared across projects — XDG data directory const xdgData = process.env.XDG_DATA_HOME ?? `${process.env.HOME}/.local/share`; return `${xdgData}/aiwg/index/framework`; } return `${cwd}/.aiwg/.index/${graphType}`; } //# sourceMappingURL=types.js.map