mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-07 17:28:23 +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>
63 lines
3.2 KiB
TypeScript
Executable File
63 lines
3.2 KiB
TypeScript
Executable File
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
|
/**
|
|
* gstack-skill-route — show how the skill-route hook scores a prompt (GST-1088).
|
|
*
|
|
* Usage:
|
|
* gstack-skill-route --explain "<prompt>" [--json] [--threshold <n>]
|
|
* gstack-skill-route --default <skill_routing|skill_contracts|ceo_review_gate|autoplan_gate>
|
|
*
|
|
* Prints the switch state, the threshold, every candidate skill with its
|
|
* score and best trigger, the route the hook would take and the exact line it
|
|
* would inject. Works with the switch off (a dry run): it never injects
|
|
* anything. --default prints a switch's SWITCH_DEFAULT (on or off), the value
|
|
* setup and gstack-config use when the switch is unset. Exit 0, or 2 on a usage error. Docs: docs/skill-routing-and-contracts.md.
|
|
*/
|
|
import {
|
|
ROUTE_THRESHOLD, SWITCH_DEFAULT, hintLine, installedSkillsDir, readSkillSwitch, routePrompt, scanInstalledSkills,
|
|
} from '../lib/skill-route';
|
|
|
|
const USAGE = 'Usage: gstack-skill-route --explain "<prompt>" [--json] [--threshold <n>]\n gstack-skill-route --default <skill_routing|skill_contracts|ceo_review_gate|autoplan_gate>';
|
|
const args = process.argv.slice(2);
|
|
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(`${USAGE}\n`); process.exit(0); }
|
|
if (args[0] === '--default') {
|
|
const name = args[1] ?? '';
|
|
if (!Object.hasOwn(SWITCH_DEFAULT, name)) {
|
|
process.stderr.write(`SKILL_ROUTE_USAGE: --default needs skill_routing, skill_contracts, ceo_review_gate or autoplan_gate\n${USAGE}\n`);
|
|
process.exit(2);
|
|
}
|
|
process.stdout.write(`${SWITCH_DEFAULT[name as keyof typeof SWITCH_DEFAULT]}\n`);
|
|
process.exit(0);
|
|
}
|
|
const at = args.indexOf('--explain');
|
|
const prompt = at >= 0 ? args[at + 1] : undefined;
|
|
const tAt = args.indexOf('--threshold');
|
|
const threshold = tAt >= 0 ? Number(args[tAt + 1]) : ROUTE_THRESHOLD;
|
|
if (prompt === undefined || !Number.isFinite(threshold) || threshold <= 0 || threshold > 1) {
|
|
process.stderr.write(`SKILL_ROUTE_USAGE: --explain needs a prompt; --threshold must be in (0, 1]\n${USAGE}\n`);
|
|
process.exit(2);
|
|
}
|
|
const dir = installedSkillsDir();
|
|
const { skills, problems } = scanInstalledSkills(dir);
|
|
const decision = routePrompt(prompt, skills, threshold);
|
|
const sw = readSkillSwitch('skill_routing');
|
|
const hint = decision.route ? hintLine(decision.route) : null;
|
|
if (args.includes('--json')) {
|
|
process.stdout.write(`${JSON.stringify({
|
|
switch: { on: sw.on, source: sw.source }, threshold, skills_dir: dir, skills: skills.length, problems,
|
|
candidates: decision.candidates, route: decision.route?.skill ?? null, reason: decision.route ? null : decision.reason, hint,
|
|
}, null, 2)}\n`);
|
|
process.exit(0);
|
|
}
|
|
const lines = [
|
|
`switch: skill_routing ${sw.on ? 'on' : 'off'} (${sw.source})${sw.invalid !== undefined ? `, unrecognized value ${JSON.stringify(sw.invalid)}` : ''}`,
|
|
`threshold: ${threshold}`,
|
|
`skills: ${skills.length} scanned in ${dir}${problems.length ? ` (${problems.join('; ')})` : ''}`,
|
|
'candidates:',
|
|
...(decision.candidates.length
|
|
? decision.candidates.map(c => ` ${c.score.toFixed(2)} /${c.skill} "${c.trigger}" (${c.matched} words)`)
|
|
: [' (none)']),
|
|
decision.route ? `route: /${decision.route.skill}` : `route: none (${decision.reason})`,
|
|
...(hint ? [`hint: ${hint}`] : []),
|
|
];
|
|
process.stdout.write(`${lines.join('\n')}\n`);
|