mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-09 10:18:32 +01:00
test(proactive): STATUS line carries the stored value; every surface names the same core four and three values
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9e22d7f326
commit
cf2d66ca0d
2 files changed
+114
No files matched your search
@@ -84,3 +84,42 @@ describe('proactive core set: the one-time prompt and the registry', () => {
|
||||
expect(QUESTIONS['preamble-proactive-behavior'].options).toEqual(['core', 'all', 'off']);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Value: protects=the core four and the three proactive values agree across
|
||||
* the config header, the default table, the router, the preamble, the one-time
|
||||
* prompt and the docs; fails_when=the core set or a value name changes on one
|
||||
* surface only; why_new=the tests above pin the template, preamble and prompt
|
||||
* options but not the docs, nor the skill names the prompt and header speak;
|
||||
* seam=none; no_claim=none beyond protects (static text, no model run)
|
||||
*/
|
||||
describe('proactive core set: every surface names the same four skills and three values', () => {
|
||||
const configSrc = fs.readFileSync(path.join(ROOT, 'bin', 'gstack-config'), 'utf-8');
|
||||
const promptAt = skillStart.indexOf('_emit_block proactive-prompt');
|
||||
const docsSkills = fs.readFileSync(path.join(ROOT, 'docs', 'skills.md'), 'utf-8');
|
||||
const docsSkillsAt = docsSkills.indexOf('## Proactive skill suggestions');
|
||||
const surfaces: Record<string, string> = {
|
||||
'bin/gstack-config header': configSrc.slice(configSrc.indexOf('# proactive:'), configSrc.indexOf('# routing_declined:')),
|
||||
'bin/gstack-config lookup_default': configSrc.slice(configSrc.indexOf('lookup_default()')).match(/^\s*proactive\).*$/m)?.[0] ?? '',
|
||||
'SKILL.md.tmpl core block': routerTmpl.slice(routerTmpl.indexOf('If `PROACTIVE` is `core` (default)'), routerTmpl.indexOf('If `PROACTIVE` is `all`')),
|
||||
'preamble rule': generateUpgradeCheck({ paths: { skillRoot: '~/.claude/skills/gstack' } } as never),
|
||||
'gstack-skill-start prompt': skillStart.slice(promptAt, skillStart.indexOf('.proactive-prompted"\\`', promptAt)),
|
||||
'docs/proactive.md': fs.readFileSync(path.join(ROOT, 'docs', 'proactive.md'), 'utf-8'),
|
||||
'docs/skills.md section': docsSkills.slice(docsSkillsAt, docsSkills.indexOf('\n## ', docsSkillsAt + 1)),
|
||||
};
|
||||
|
||||
test.each(Object.entries(surfaces))('%s names the core four and no sampled non-core skill', (name, text) => {
|
||||
expect(text.length, name).toBeGreaterThan(20);
|
||||
for (const skill of CORE) expect(text, `${name} lacks ${skill}`).toContain(skill);
|
||||
for (const skill of NON_CORE_SAMPLE) expect(text, `${name} names ${skill}`).not.toContain(skill);
|
||||
});
|
||||
|
||||
test('the docs and the config header spell the three values, and the router names the restore-default command', () => {
|
||||
for (const name of ['docs/proactive.md', 'docs/skills.md section', 'bin/gstack-config header']) {
|
||||
for (const value of ['core', 'all', 'off']) expect(surfaces[name], `${name} lacks ${value}`).toMatch(new RegExp(`\\b${value}\\b`));
|
||||
}
|
||||
expect(surfaces['docs/proactive.md']).toContain('`true` and `false`');
|
||||
for (const value of ['all', 'off', 'core']) expect(surfaces['docs/proactive.md']).toContain(`gstack-config set proactive ${value}`);
|
||||
expect(routerTmpl.replace(/\s+/g, ' ')).toContain('`gstack-config set proactive core` restores the default');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* The PROACTIVE STATUS line (GSTA-93 fix 12, proactive core default): every
|
||||
* skill reads `PROACTIVE:` from bin/gstack-skill-start, which asks
|
||||
* bin/gstack-config and falls back to a literal when the config binary is
|
||||
* missing. The default moved from `true` to `core`.
|
||||
*
|
||||
* Value: protects=the PROACTIVE STATUS line a skill reads carries the stored
|
||||
* proactive value and core when unset; fails_when=gstack-skill-start stops
|
||||
* consulting gstack-config for proactive or the default moves back to true;
|
||||
* why_new=proactive-config.test.ts exercises gstack-config alone and
|
||||
* gstack-skill-start.test.ts asserts only that the key is emitted; seam=none;
|
||||
* no_claim=how the model applies the value (prose, not executed here)
|
||||
*/
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { runBin } from './helpers/run-bin';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const BIN = path.join(ROOT, 'bin');
|
||||
|
||||
let tmpHome: string;
|
||||
|
||||
beforeAll(() => {
|
||||
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-proactive-ss-home-'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function freshState(config: string): string {
|
||||
const state = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-proactive-ss-state-'));
|
||||
fs.writeFileSync(path.join(state, 'config.yaml'), config);
|
||||
return state;
|
||||
}
|
||||
|
||||
function runStart(script: string, state: string): string {
|
||||
const r = runBin(script, ['--skill', 'testskill'], {
|
||||
cwd: tmpHome,
|
||||
timeoutMs: 30_000,
|
||||
env: {
|
||||
PATH: process.env.PATH,
|
||||
HOME: tmpHome,
|
||||
GSTACK_HOME: state,
|
||||
GSTACK_STATE_DIR: state,
|
||||
GSTACK_LEASE_DIR: path.join(state, 'leases'),
|
||||
},
|
||||
});
|
||||
expect(r.status, r.stderr).toBe(0);
|
||||
return r.stdout;
|
||||
}
|
||||
|
||||
const proactiveLine = (out: string) => out.match(/^PROACTIVE: (.*)$/m)?.[1];
|
||||
|
||||
describe('gstack-skill-start PROACTIVE status line', () => {
|
||||
test('carries core when nothing is stored, and the stored value (new or legacy spelling) otherwise', () => {
|
||||
const cases: Array<[string, string]> = [
|
||||
['update_check: false\n', 'core'],
|
||||
['update_check: false\nproactive: all\n', 'all'],
|
||||
['update_check: false\nproactive: off\n', 'off'],
|
||||
['update_check: false\nproactive: true\n', 'true'],
|
||||
];
|
||||
for (const [config, expected] of cases) {
|
||||
const state = freshState(config);
|
||||
try {
|
||||
expect(proactiveLine(runStart(path.join(BIN, 'gstack-skill-start'), state)), config).toBe(expected);
|
||||
} finally {
|
||||
fs.rmSync(state, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user