mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-12 03:30:28 +01:00
* feat: retire /investigate after a no-gain eval (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(skill-start): core routing offer drops every non-core rule line (GST-1717)
The retired /investigate rule became 'Bugs/errors → fix them directly', which no
longer matches '→ invoke /', so proactive=core leaked it into the offered section.
Match any '- … → ' rule line.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* revert(review): keep review/checklist.md at its no-loss-pinned bytes (GST-1717)
The pin names replay GSTA-224 H1; a reviewer checklist edit merges only after a
passing no-loss replay (crew rule 15). The /investigate flake line stays until a
replayed repin.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: repair free-suite fallout from retiring /investigate (GST-1717)
CI run 42340956269 at 83a8a9a: drop fixtures, ledger rows, allowlist entries and
counts for the retired skill and its deleted paid files; record the harness
change as non-behavioral; the test-of-test detector accepts path.join(ROOT, 'bin');
uncovered-prompt fixtures use a tracked prompt, since an untracked path now
reads as a deleted one.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(relink): back up a retired skill's real SKILL.md before cleaning it (GST-1717)
With the skill's source gone, the customized-file check never ran, so a
banner-carrying local copy of investigate/SKILL.md was deleted instead of moved
to the backup root (red-team review, pass 1).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(qm): restore the review checklist's flake-line rewrite (GST-1717)
review/checklist.md keeps its pinned /investigate line, so the QM edition needs
its replaceText rule back; qm/ stays fresh against the generator.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): name the setup flag break, the relink backup and the pinned checklist line (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(setup,relink): back up a retired skill's real SKILL.md instead of deleting it (GST-1717)
Scoped fix round under grant a-360dd62bd46a (outside-voice blocking finding and
adversarial #1, #4). setup's prune compares a copied host entry with the render
before removing that render, and backs up a copy that differs or whose render is
gone; relink no longer takes the marker's rm -rf for a skill whose source is
gone, so a real SKILL.md is backed up and the user's files stay. setup reports a
failed remove-source of the retired gate hooks on stderr.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pr-profile): a changed prompt counts as deleted only when it is gone from disk (GST-1717)
An untracked prompt file on disk still needs prompt validation (targeted review
finding); harness change recorded as non-behavioral.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): exact backup wording, the hook-removal warning and GST-1744 (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(setup): one backup folder per host for a retired skill's copies (GST-1717)
Under grant a-c95576b8b1f9 (fix-check#1): every copy host moved its
gstack-investigate/SKILL.md to the same backup path, so a later host's copy
overwrote an earlier host's edits. A sandboxed ./setup --host factory run showed
gen-skill-docs removes the retired render before the prune, so every real copy
is backed up; each host now gets its own folder.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): say exactly what setup backs up for a retired skill (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): end the two colon sentences prose-lint flagged (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(retired-skills): name plan-eng-review once behind a pregate-literal waiver (GST-1717)
pregate's literals check read the GSTACK_RETIRED_SKILLS change as an old
value still in the tests. plan-eng-review is still retired; investigate was
appended. The fixture names it once, with the waiver.
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>
87 lines
3.5 KiB
TypeScript
87 lines
3.5 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: no host registers them, and setup removes an earlier
|
|
* install's gstack-owned entry. plan-eng-review's source stays as /autoplan's
|
|
* eng-phase methodology (GST-1698); external hosts get it beside
|
|
* gstack-autoplan/SKILL.md as <name>.md, since Codex and Copilot register every
|
|
* nested SKILL.md. investigate was cut after a no-gain eval and its source is
|
|
* gone (GST-1717). 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', 'investigate'];
|
|
|
|
/** 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;
|
|
}
|