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
318 lines • 11.8 kB
JavaScript
import fs from 'fs/promises';
import path from 'path';
const PLAN_REL_PATH = path.join('.aiwg', 'workspace-skill-plan.json');
const FRAMEWORKS = [
'sdlc',
'marketing',
'media-curator',
'research',
'forensics',
'security-engineering',
'ops',
'knowledge-base',
];
const ADDONS = [
'aiwg-utils',
];
const EXTENSIONS = [
'dev',
'it',
'net',
'sec',
'stream',
'sys',
];
const PROFILE_FRAMEWORKS = {
sdlc: ['sdlc'],
default: ['sdlc'],
marketing: ['marketing'],
'media-marketing': ['marketing'],
'media-curator': ['media-curator'],
research: ['research'],
forensics: ['forensics'],
'security-engineering': ['security-engineering'],
security: ['security-engineering'],
ops: ['ops'],
'knowledge-base': ['knowledge-base'],
kb: ['knowledge-base'],
};
async function exists(filePath) {
try {
await fs.access(filePath);
return true;
}
catch {
return false;
}
}
async function readJsonIfPresent(filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
return JSON.parse(content);
}
catch {
return null;
}
}
function stringArray(value) {
return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
}
function addReason(included, id, reason) {
const reasons = included.get(id) ?? [];
if (!reasons.includes(reason))
reasons.push(reason);
included.set(id, reasons);
}
function includeProfile(included, profile, reason) {
const normalized = profile.toLowerCase();
if (normalized === 'all') {
for (const framework of FRAMEWORKS)
addReason(included, framework, reason);
return;
}
for (const framework of PROFILE_FRAMEWORKS[normalized] ?? []) {
addReason(included, framework, reason);
}
}
function extractConfigHints(config) {
if (!config)
return { bundles: [] };
const skillLoading = typeof config.skillLoading === 'object' && config.skillLoading
? config.skillLoading
: {};
const profile = typeof config.workspaceProfile === 'string' ? config.workspaceProfile :
typeof config.profile === 'string' ? config.profile :
typeof skillLoading.profile === 'string' ? skillLoading.profile :
undefined;
const bundles = [
...stringArray(config.enabledBundles),
...stringArray(config.enabledSkills),
...stringArray(skillLoading.include),
...stringArray(skillLoading.bundles),
];
return { profile, bundles };
}
async function detectIntakeProfiles(projectDir) {
const intakePath = path.join(projectDir, '.aiwg', 'intake', 'solution-profile.md');
try {
const content = (await fs.readFile(intakePath, 'utf-8')).toLowerCase();
const profiles = [];
if (content.includes('forensic') || content.includes('incident response'))
profiles.push('forensics');
if (content.includes('research') || content.includes('citation') || content.includes('literature'))
profiles.push('research');
if (content.includes('marketing') || content.includes('campaign') || content.includes('brand'))
profiles.push('marketing');
if (content.includes('media curator') || content.includes('discography') || content.includes('archive'))
profiles.push('media-curator');
if (content.includes('security') || content.includes('cryptograph') || content.includes('threat model'))
profiles.push('security-engineering');
if (content.includes('ops') || content.includes('runbook') || content.includes('infrastructure'))
profiles.push('ops');
if (content.includes('knowledge base') || content.includes('wiki') || content.includes('semantic memory'))
profiles.push('knowledge-base');
return profiles;
}
catch {
return [];
}
}
export async function resolveWorkspaceSignalPlan(projectDir, opts = {}) {
const included = new Map();
const signals = [];
const missedSignals = [];
const notes = [];
addReason(included, 'sdlc', 'always included as core lifecycle support');
addReason(included, 'aiwg-utils', 'always included as core AIWG utilities');
let profile = opts.profile;
let profileSource = profile ? 'flag' : 'auto';
const config = await readJsonIfPresent(path.join(projectDir, '.aiwg', 'aiwg.config'));
const configHints = extractConfigHints(config);
if (!profile && configHints.profile) {
profile = configHints.profile;
profileSource = 'config';
}
const requestedTarget = opts.requestedTarget;
if (!profile && requestedTarget && requestedTarget !== 'all' && !requestedTarget.startsWith('-')) {
profile = requestedTarget;
profileSource = 'requested-target';
}
if (profile) {
includeProfile(included, profile, `${profileSource} profile "${profile}"`);
}
for (const bundle of configHints.bundles) {
addReason(included, bundle, 'explicitly enabled in .aiwg/aiwg.config');
}
const domainSignals = [
['forensics', '.aiwg/forensics/', 'forensics'],
['research', '.aiwg/research/', 'research'],
['marketing', '.aiwg/marketing/', 'marketing'],
['media-curator', '.aiwg/media-curator/', 'media-curator'],
['ops', '.aiwg/ops/', 'ops'],
['knowledge-base', '.aiwg/knowledge-base/', 'knowledge-base'],
['security-engineering', '.aiwg/security-engineering/', 'security-engineering'],
];
for (const [framework, relPath, reasonPath] of domainSignals) {
if (await exists(path.join(projectDir, relPath))) {
signals.push(relPath);
addReason(included, framework, `matched ${reasonPath} workspace signal`);
}
else {
missedSignals.push(relPath);
}
}
if (await exists(path.join(projectDir, 'package.json')) && await exists(path.join(projectDir, 'src'))) {
signals.push('package.json + src/');
addReason(included, 'sdlc', 'matched application source workspace signal');
}
else {
missedSignals.push('package.json + src/');
}
const containerFiles = ['Dockerfile', 'compose.yaml', 'compose.yml', 'docker-compose.yaml', 'docker-compose.yml'];
const matchedContainer = [];
for (const file of containerFiles) {
if (await exists(path.join(projectDir, file)))
matchedContainer.push(file);
}
if (matchedContainer.length > 0) {
signals.push(matchedContainer.join(', '));
addReason(included, 'forensics', 'matched container workspace signal');
notes.push('Container signals currently map to the forensics framework; a narrower container skill subset can be layered on this plan later.');
}
else {
missedSignals.push('Dockerfile / compose.yaml');
}
const intakeProfiles = await detectIntakeProfiles(projectDir);
for (const intakeProfile of intakeProfiles) {
signals.push(`.aiwg/intake/solution-profile.md:${intakeProfile}`);
includeProfile(included, intakeProfile, `matched intake purpose "${intakeProfile}"`);
}
if (intakeProfiles.length === 0) {
missedSignals.push('.aiwg/intake/solution-profile.md');
}
if (included.has('security-engineering')) {
addReason(included, 'sdlc', 'dependency of security-engineering');
}
const bundles = [
...FRAMEWORKS.map((id) => ({
type: 'framework',
id,
included: included.has(id),
reasons: included.get(id) ?? ['no matching workspace signal'],
})),
...ADDONS.map((id) => ({
type: 'addon',
id,
included: included.has(id),
reasons: included.get(id) ?? ['no matching workspace signal'],
})),
...EXTENSIONS.map((id) => ({
type: 'extension',
id,
included: included.has(id),
reasons: included.get(id) ?? ['no matching workspace signal'],
})),
];
return {
projectDir,
generatedAt: new Date().toISOString(),
profile: profile ?? 'auto',
profileSource,
requestedTarget,
signals,
missedSignals,
bundles,
notes,
};
}
export async function writeWorkspaceSignalPlan(projectDir, plan) {
const planPath = path.join(projectDir, PLAN_REL_PATH);
await fs.mkdir(path.dirname(planPath), { recursive: true });
await fs.writeFile(planPath, JSON.stringify(plan, null, 2) + '\n', 'utf-8');
return planPath;
}
export async function readWorkspaceSignalPlan(projectDir) {
try {
const content = await fs.readFile(path.join(projectDir, PLAN_REL_PATH), 'utf-8');
return JSON.parse(content);
}
catch {
return null;
}
}
export function includedBundleIds(plan, type) {
return plan.bundles
.filter((bundle) => bundle.type === type && bundle.included)
.map((bundle) => bundle.id);
}
function formatBundleGroup(plan, included, type) {
return plan.bundles
.filter((bundle) => bundle.type === type && bundle.included === included)
.map((bundle) => ` - ${bundle.id}: ${bundle.reasons.join('; ')}`);
}
export function formatWorkspaceSignalPlan(plan) {
const lines = [];
lines.push('Workspace skill loading plan (dry run)');
lines.push(`Project: ${plan.projectDir}`);
lines.push(`Profile: ${plan.profile} (${plan.profileSource})`);
if (plan.requestedTarget)
lines.push(`Requested target: ${plan.requestedTarget}`);
lines.push('');
lines.push('Included');
for (const type of ['framework', 'addon', 'extension']) {
const rows = formatBundleGroup(plan, true, type);
if (rows.length > 0) {
lines.push(`${type}s:`);
lines.push(...rows);
}
}
lines.push('');
lines.push('Excluded');
for (const type of ['framework', 'addon', 'extension']) {
const rows = formatBundleGroup(plan, false, type);
if (rows.length > 0) {
lines.push(`${type}s:`);
lines.push(...rows);
}
}
lines.push('');
lines.push('Signals matched:');
if (plan.signals.length === 0) {
lines.push(' - none');
}
else {
for (const signal of plan.signals)
lines.push(` - ${signal}`);
}
lines.push('');
lines.push('Signals not present:');
for (const signal of plan.missedSignals)
lines.push(` - ${signal}`);
if (plan.notes.length > 0) {
lines.push('');
lines.push('Notes:');
for (const note of plan.notes)
lines.push(` - ${note}`);
}
lines.push('');
lines.push('No files were changed. Run `aiwg use --profile <name>` to deploy a workspace-aware subset, or `aiwg use all` for the full deployment.');
return lines.join('\n');
}
export function formatDeployedWorkspaceSignalPlan(plan) {
const lines = [];
lines.push(`\nWorkspace skill filter: ${plan.profile} (${plan.profileSource})`);
if (plan.generatedAt)
lines.push(`Resolved: ${plan.generatedAt}`);
lines.push('Included:');
const included = plan.bundles.filter((bundle) => bundle.included);
for (const bundle of included) {
lines.push(` ${bundle.type}/${bundle.id}: ${bundle.reasons.join('; ')}`);
}
lines.push('Filtered out:');
const excluded = plan.bundles.filter((bundle) => !bundle.included);
for (const bundle of excluded) {
lines.push(` ${bundle.type}/${bundle.id}: ${bundle.reasons.join('; ')}`);
}
return lines.join('\n') + '\n';
}
//# sourceMappingURL=workspace-signals.js.map