Merge remote-tracking branch 'origin/main' into garrytan/elegance

# Conflicts:
#	package.json
#	scripts/gen-skill-docs.ts
This commit is contained in:
Garry Tan
2026-03-23 22:17:12 -07:00
70 changed files with 1000 additions and 200 deletions
+5 -4
View File
@@ -7,16 +7,17 @@
*/
import { validateSkill } from '../test/helpers/skill-parser';
import { discoverTemplates } from './discover-skills';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
const TEMPLATES = [
{ tmpl: path.join(ROOT, 'SKILL.md.tmpl'), output: 'SKILL.md' },
{ tmpl: path.join(ROOT, 'browse', 'SKILL.md.tmpl'), output: 'browse/SKILL.md' },
];
const TEMPLATES = discoverTemplates(ROOT).map(t => ({
tmpl: path.join(ROOT, t.tmpl),
output: t.output,
}));
function regenerateAndValidate() {
// Regenerate
+39
View File
@@ -0,0 +1,39 @@
/**
* Shared discovery for SKILL.md and .tmpl files.
* Scans root + one level of subdirs, skipping node_modules/.git/dist.
*/
import * as fs from 'fs';
import * as path from 'path';
const SKIP = new Set(['node_modules', '.git', 'dist']);
function subdirs(root: string): string[] {
return fs.readdirSync(root, { withFileTypes: true })
.filter(d => d.isDirectory() && !SKIP.has(d.name))
.map(d => d.name);
}
export function discoverTemplates(root: string): Array<{ tmpl: string; output: string }> {
const dirs = ['', ...subdirs(root)];
const results: Array<{ tmpl: string; output: string }> = [];
for (const dir of dirs) {
const rel = dir ? `${dir}/SKILL.md.tmpl` : 'SKILL.md.tmpl';
if (fs.existsSync(path.join(root, rel))) {
results.push({ tmpl: rel, output: rel.replace(/\.tmpl$/, '') });
}
}
return results;
}
export function discoverSkillFiles(root: string): string[] {
const dirs = ['', ...subdirs(root)];
const results: string[] = [];
for (const dir of dirs) {
const rel = dir ? `${dir}/SKILL.md` : 'SKILL.md';
if (fs.existsSync(path.join(root, rel))) {
results.push(rel);
}
}
return results;
}
+15 -10
View File
@@ -9,6 +9,9 @@
* Used by skill:check and CI freshness checks.
*/
import { COMMAND_DESCRIPTIONS } from '../browse/src/commands';
import { SNAPSHOT_FLAGS } from '../browse/src/snapshot';
import { discoverTemplates } from './discover-skills';
import * as fs from 'fs';
import * as path from 'path';
import type { Host, TemplateContext } from './resolvers/types';
@@ -81,6 +84,17 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);
}
// Inject auto-trigger guard into skill descriptions.
// Adds explicit trigger criteria so Claude Code doesn't auto-fire skills
// based on semantic similarity. Preserves existing "Use when" and
// "Proactively suggest" text (both are tested in skill-validation.test.ts).
const triggerGuard = ` MANUAL TRIGGER ONLY: invoke only when user types /${skillName}.\n`;
const descMatch = content.match(/^(description:\s*\|?\s*\n)/m);
if (descMatch && descMatch.index !== undefined) {
const insertAt = descMatch.index + descMatch[0].length;
content = content.slice(0, insertAt) + triggerGuard + content.slice(insertAt);
}
// For codex host: transform frontmatter and replace Claude-specific paths
if (host === 'codex') {
// Extract hook safety prose BEFORE transforming frontmatter (which strips hooks)
@@ -127,16 +141,7 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
// ─── Main ───────────────────────────────────────────────────
function findTemplates(): string[] {
const templates: string[] = [];
const rootTmpl = path.join(ROOT, 'SKILL.md.tmpl');
if (fs.existsSync(rootTmpl)) templates.push(rootTmpl);
for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
const tmpl = path.join(ROOT, entry.name, 'SKILL.md.tmpl');
if (fs.existsSync(tmpl)) templates.push(tmpl);
}
return templates;
return discoverTemplates(ROOT).map(t => path.join(ROOT, t.tmpl));
}
let hasChanges = false;
+4 -26
View File
@@ -9,34 +9,15 @@
*/
import { validateSkill } from '../test/helpers/skill-parser';
import { discoverTemplates, discoverSkillFiles } from './discover-skills';
import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
const ROOT = path.resolve(import.meta.dir, '..');
// Find all SKILL.md files
const SKILL_FILES = [
'SKILL.md',
'browse/SKILL.md',
'qa/SKILL.md',
'qa-only/SKILL.md',
'ship/SKILL.md',
'review/SKILL.md',
'retro/SKILL.md',
'plan-ceo-review/SKILL.md',
'plan-eng-review/SKILL.md',
'setup-browser-cookies/SKILL.md',
'plan-design-review/SKILL.md',
'design-review/SKILL.md',
'gstack-upgrade/SKILL.md',
'document-release/SKILL.md',
'canary/SKILL.md',
'benchmark/SKILL.md',
'land-and-deploy/SKILL.md',
'setup-deploy/SKILL.md',
'cso/SKILL.md',
].filter(f => fs.existsSync(path.join(ROOT, f)));
// Find all SKILL.md files (dynamic discovery — no hardcoded list)
const SKILL_FILES = discoverSkillFiles(ROOT);
let hasErrors = false;
@@ -73,10 +54,7 @@ for (const file of SKILL_FILES) {
// ─── Templates ──────────────────────────────────────────────
console.log('\n Templates:');
const TEMPLATES = [
{ tmpl: 'SKILL.md.tmpl', output: 'SKILL.md' },
{ tmpl: 'browse/SKILL.md.tmpl', output: 'browse/SKILL.md' },
];
const TEMPLATES = discoverTemplates(ROOT);
for (const { tmpl, output } of TEMPLATES) {
const tmplPath = path.join(ROOT, tmpl);