aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
164 lines (154 loc) • 5.41 kB
JavaScript
/**
* Enrich manifest.json files with per-file descriptions.
*
* - Walks target tree, finds directories containing manifest.json
* - Adds/updates a `descriptions` object keyed by filename
* - Skips hidden directories; ignores files listed under `ignore`
*
* Usage:
* node tools/manifest/enrich-manifests.mjs [--target <path>|.] [--write]
*
* Note: manifest.md generation has been deprecated - manifest.json serves the same purpose.
*/
import fs from 'fs';
import path from 'path';
function parseArgs() {
const args = process.argv.slice(2);
let target = process.cwd();
let write = false;
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--target' && args[i + 1]) target = path.resolve(args[++i]);
else if (a === '--write') write = true;
}
return { target, write };
}
function listManifestDirs(root) {
const out = [];
function walk(d) {
const base = path.basename(d);
if (base.startsWith('.') && base !== '.github' && base !== '.claude') return;
const entries = fs.readdirSync(d, { withFileTypes: true });
const hasManifest = entries.some(e => e.isFile() && e.name === 'manifest.json');
if (hasManifest) out.push(d);
for (const e of entries) {
if (e.isDirectory() && e.name !== 'node_modules' && e.name !== 'dist' && e.name !== 'build') {
walk(path.join(d, e.name));
}
}
}
walk(root);
return out;
}
function readText(p) {
try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
}
function summarizeMarkdown(absPath) {
const text = readText(absPath);
if (!text) return '';
const lines = text.split(/\r?\n/);
let i = 0;
// Skip frontmatter
if (lines[0]?.trim() === '---') {
i = 1;
while (i < lines.length && lines[i].trim() !== '---') i++;
if (i < lines.length) i++;
}
// find first non-empty line
while (i < lines.length && lines[i].trim() === '') i++;
if (i >= lines.length) return '';
const first = lines[i].trim();
// prefer H1 or H2 text
if (/^#{1,2}\s+/.test(first)) {
const t = first.replace(/^#{1,6}\s+/, '').trim();
// Try to grab the next paragraph if exists for more context
let j = i + 1;
while (j < lines.length && lines[j].trim() === '') j++;
let second = '';
if (j < lines.length && !/^#/.test(lines[j].trim())) {
second = lines[j].trim();
}
const combined = second ? `${t} — ${second}` : t;
return combined.length > 220 ? combined.slice(0, 217) + '...' : combined;
}
// otherwise use first paragraph
const para = first;
return para.length > 220 ? para.slice(0, 217) + '...' : para;
}
function summarizeCode(absPath) {
const text = readText(absPath);
if (!text) return '';
// Look for leading block comment
const block = text.match(/^\s*\/\*\*?[\s\S]*?\*\//);
if (block) {
const body = block[0].replace(/^\s*\/\*+/, '').replace(/\*+\/\s*$/, '');
const lines = body.split(/\r?\n/).map(l => l.replace(/^\s*\*\s?/, '').trim()).filter(Boolean);
const s = lines[0] || '';
if (s) return s.length > 220 ? s.slice(0, 217) + '...' : s;
}
// Fallback: first non-empty line comment
const line = text.split(/\r?\n/).map(l => l.trim()).find(l => l.startsWith('//'));
if (line) return line.replace(/^\/\//, '').trim();
return '';
}
function humanizeFilename(name) {
const base = name.replace(/\.[^.]+$/, '')
.replace(/[-_]+/g, ' ')
.replace(/\s+/g, ' ')
.trim();
return base ? base[0].toUpperCase() + base.slice(1) : name;
}
function deriveDescription(dir, file) {
const abs = path.join(dir, file);
const ext = path.extname(file).toLowerCase();
if (ext === '.md') {
const s = summarizeMarkdown(abs);
if (s) return s;
return `Markdown: ${humanizeFilename(file)}`;
}
if (ext === '.mjs' || ext === '.js' || ext === '.ts') {
const s = summarizeCode(abs);
if (s) return s;
return `Script: ${humanizeFilename(file)}`;
}
return `${ext.slice(1).toUpperCase()} file: ${file}`;
}
function enrichManifest(dir, write) {
const manifestPath = path.join(dir, 'manifest.json');
let json;
try {
json = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
} catch {
return { dir, updated: false, reason: 'invalid json' };
}
const ignore = new Set([...(json.ignore || []), 'manifest.json']);
const files = (json.files || []).filter(f => !ignore.has(f));
const descriptions = { ...(json.descriptions || {}) };
let changed = false;
for (const f of files) {
const cur = descriptions[f];
const next = deriveDescription(dir, f);
if (!cur || (typeof cur === 'string' && cur.trim().length === 0)) {
descriptions[f] = next;
changed = true;
}
}
if (changed) json.descriptions = descriptions;
if (write && changed) {
fs.writeFileSync(manifestPath, JSON.stringify(json, null, 2) + '\n', 'utf8');
}
return { dir, updated: write && changed, changed };
}
(function main(){
const { target, write } = parseArgs();
const dirs = listManifestDirs(target);
let updated = 0;
for (const d of dirs) {
const { updated: u, changed } = enrichManifest(d, write);
if (u) updated++;
if (!write && changed) console.log(`would-enrich ${path.relative(process.cwd(), d)}`);
if (write && u) console.log(`enriched ${path.relative(process.cwd(), d)}`);
}
console.log(`${write ? 'Enriched' : 'Would enrich'} ${updated} manifest(s).`);
})();