mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-14 12:27:18 +01:00
* feat: cut standalone /plan-eng-review; /autoplan keeps its eng phase (GST-1698) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: lower shrunk SKILL.md size pins; keep the retired methodology's pins measured beside /autoplan Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: carry /autoplan's plan-eng-review.md into Kiro installs Kiro's per-skill loop copied only SKILL.md and sections, so ~/.kiro/skills/gstack-autoplan lacked the Phase 3 methodology file its SKILL.md reads. Generated sibling *.md files now travel with SKILL.md under the same ownership guards. Review-Finding: p1/specialist-batched#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: file /plan-eng-review retirement follow-ups; scope the changelog claim to ./setup Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: route architecture-review requests to /autoplan by its description (journey-plan-eng) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
/**
|
|
* 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';
|
|
import type { HostConfig } from './host-config';
|
|
|
|
const SKIP = new Set(['node_modules', '.git', 'dist']);
|
|
|
|
/**
|
|
* Retired skills: the source stays because another skill reads it, but no host
|
|
* registers it. plan-eng-review is /autoplan's eng-phase methodology (GST-1698).
|
|
* External hosts get that methodology beside gstack-autoplan/SKILL.md as
|
|
* <name>.md, since Codex and Copilot register every nested SKILL.md. setup and
|
|
* gstack-relink read the same list from GSTACK_RETIRED_SKILLS in
|
|
* bin/gstack-install-registry.sh.
|
|
*/
|
|
export const RETIRED_SKILLS = ['plan-eng-review'];
|
|
|
|
/** The generator and its coverage checks use the same include-minus-skip rule. */
|
|
export function includesSkill(host: HostConfig, skillDir: string): boolean {
|
|
const { includeSkills, skipSkills } = host.generation;
|
|
return (!includeSkills?.length || includeSkills.includes(skillDir))
|
|
&& !skipSkills?.includes(skillDir);
|
|
}
|
|
|
|
function subdirs(root: string): string[] {
|
|
return fs.readdirSync(root, { withFileTypes: true })
|
|
.filter(d => d.isDirectory() && !d.name.startsWith('.') && !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;
|
|
}
|
|
|
|
/**
|
|
* Discover on-demand section templates: `<skill>/sections/*.md.tmpl`.
|
|
*
|
|
* Returns the relative tmpl path, its generated output path (`.tmpl` stripped),
|
|
* and the owning skill directory so the generator can build a TemplateContext
|
|
* with the PARENT skill's name (not "sections") — see processSectionTemplate.
|
|
*
|
|
* Scans one level of subdirs (same depth as discoverTemplates), looking only
|
|
* inside a `sections/` child. Skills without a sections/ dir contribute nothing,
|
|
* so this is a no-op for every skill that hasn't been carved.
|
|
*/
|
|
export function discoverSectionTemplates(
|
|
root: string,
|
|
): Array<{ tmpl: string; output: string; skillDir: string }> {
|
|
const results: Array<{ tmpl: string; output: string; skillDir: string }> = [];
|
|
for (const dir of subdirs(root)) {
|
|
const sectionsDir = path.join(root, dir, 'sections');
|
|
if (!fs.existsSync(sectionsDir) || !fs.statSync(sectionsDir).isDirectory()) continue;
|
|
for (const entry of fs.readdirSync(sectionsDir, { withFileTypes: true })) {
|
|
if (!entry.isFile() || !entry.name.endsWith('.md.tmpl')) continue;
|
|
const rel = `${dir}/sections/${entry.name}`;
|
|
results.push({ tmpl: rel, output: rel.replace(/\.tmpl$/, ''), skillDir: dir });
|
|
}
|
|
}
|
|
// Deterministic order so CI freshness checks don't flap on FS iteration order.
|
|
return results.sort((a, b) => a.tmpl.localeCompare(b.tmpl));
|
|
}
|
|
|
|
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;
|
|
}
|