mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-01 19:25:10 +02:00
feat: Review Readiness Dashboard + gstack-slug helper (v0.5.1) (#130)
* feat: add bin/gstack-slug helper + migrate all inline SLUG computation
Extract the opaque SLUG sed pipeline into a shared 5-line shell script.
Replace 8 inline copies across templates with eval $(gstack-slug).
Sanitizes branch names (/ → -) to prevent subdirectory creation.
* feat: review readiness dashboard — track CEO/Eng/Design reviews per branch
Each review skill logs its result to JSONL. A shared {{REVIEW_DASHBOARD}}
placeholder displays run counts, timestamps, and a CLEARED TO SHIP verdict.
/ship pre-flight reads the dashboard and prompts when reviews are missing.
* chore: bump version and changelog (v0.5.1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -322,3 +322,30 @@ describe('description quality evals', () => {
|
||||
expect(tipsSection).not.toContain('->');
|
||||
});
|
||||
});
|
||||
|
||||
describe('REVIEW_DASHBOARD resolver', () => {
|
||||
const REVIEW_SKILLS = ['plan-ceo-review', 'plan-eng-review', 'plan-design-review'];
|
||||
|
||||
for (const skill of REVIEW_SKILLS) {
|
||||
test(`review dashboard appears in ${skill} generated file`, () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('reviews.jsonl');
|
||||
expect(content).toContain('REVIEW READINESS DASHBOARD');
|
||||
});
|
||||
}
|
||||
|
||||
test('review dashboard appears in ship generated file', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('reviews.jsonl');
|
||||
expect(content).toContain('REVIEW READINESS DASHBOARD');
|
||||
});
|
||||
|
||||
test('resolver output contains key dashboard elements', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'plan-ceo-review', 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('VERDICT');
|
||||
expect(content).toContain('CLEARED TO SHIP');
|
||||
expect(content).toContain('NOT YET RUN');
|
||||
expect(content).toContain('7 days');
|
||||
expect(content).toContain('Design Review');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -665,3 +665,45 @@ describe('Planted-bug fixture validation', () => {
|
||||
expect(content).toContain('update_column');
|
||||
});
|
||||
});
|
||||
|
||||
// --- gstack-slug helper ---
|
||||
|
||||
describe('gstack-slug', () => {
|
||||
const SLUG_BIN = path.join(ROOT, 'bin', 'gstack-slug');
|
||||
|
||||
test('binary exists and is executable', () => {
|
||||
expect(fs.existsSync(SLUG_BIN)).toBe(true);
|
||||
const stat = fs.statSync(SLUG_BIN);
|
||||
expect(stat.mode & 0o111).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('outputs SLUG and BRANCH lines in a git repo', () => {
|
||||
const result = Bun.spawnSync([SLUG_BIN], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
const output = result.stdout.toString();
|
||||
expect(output).toContain('SLUG=');
|
||||
expect(output).toContain('BRANCH=');
|
||||
});
|
||||
|
||||
test('SLUG does not contain forward slashes', () => {
|
||||
const result = Bun.spawnSync([SLUG_BIN], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' });
|
||||
const slug = result.stdout.toString().match(/SLUG=(.*)/)?.[1] ?? '';
|
||||
expect(slug).not.toContain('/');
|
||||
expect(slug.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('BRANCH does not contain forward slashes', () => {
|
||||
const result = Bun.spawnSync([SLUG_BIN], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' });
|
||||
const branch = result.stdout.toString().match(/BRANCH=(.*)/)?.[1] ?? '';
|
||||
expect(branch).not.toContain('/');
|
||||
expect(branch.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('output is eval-compatible (KEY=VALUE format)', () => {
|
||||
const result = Bun.spawnSync([SLUG_BIN], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' });
|
||||
const lines = result.stdout.toString().trim().split('\n');
|
||||
expect(lines.length).toBe(2);
|
||||
expect(lines[0]).toMatch(/^SLUG=.+/);
|
||||
expect(lines[1]).toMatch(/^BRANCH=.+/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user