mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-08 22:35:36 +02:00
feat: community wave — 7 fixes, relink, sidebar Write, discoverability (v0.13.5.0) (#641)
* test: add 16 failing tests for 6 community fixes
Tests-first for all fixes in this PR wave:
- #594 discoverability: gstack tag in descriptions, 120-char first line
- #573 feature signals: ship/SKILL.md Step 4 detection
- #510 context warnings: no preemptive warnings in generated files
- #474 Safety Net: no find -delete in generated files
- #467 telemetry: JSONL writes gated by _TEL conditional
- #584 sidebar: Write in allowedTools, stderr capture
- #578 relink: prefixed/flat symlinks, cleanup, error, config hook
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace find -delete with find -exec rm for Safety Net (#474)
-delete is a non-POSIX extension that fails on Safety Net environments.
-exec rm {} + is POSIX-compliant and works everywhere.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: gate local JSONL writes by telemetry setting (#467)
When telemetry is off, nothing is written anywhere — not just remote,
but local JSONL too. Clean trust contract: off means off everywhere.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove preemptive context warnings from plan-eng-review (#510)
The system handles context compaction automatically. Preemptive warnings
waste tokens and create false urgency. Skills should not warn about
context limits — just describe the compression priority order.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add (gstack) tag to skill descriptions for discoverability (#594)
Every SKILL.md.tmpl description now contains "gstack" on the last line,
making skills findable in Claude Code's command palette. First-line hooks
stay under 120 chars. Split ship description to fix wrapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: auto-relink skill symlinks on prefix config change (#578)
New bin/gstack-relink creates prefixed (gstack-*) or flat symlinks
based on skill_prefix config. gstack-config auto-triggers relink
when skill_prefix changes. Setup guards against recursive calls
with GSTACK_SETUP_RUNNING env var.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add feature signal detection to version bump heuristic (#573)
/ship Step 4 now checks for feature signals (new routes, migrations,
test+source pairs, feat/ branches) when deciding version bumps.
PATCH requires no feature signals. MINOR asks the user if any signal
is detected or 500+ lines changed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: sidebar Write tool, stderr capture, cross-platform URL opener (#584)
Add Write to sidebar allowedTools (both sidebar-agent.ts and server.ts).
Write doesn't expand attack surface beyond what Bash already provides.
Replace empty stderr handler with buffer capture for better error
diagnostics. New bin/gstack-open-url for cross-platform URL opening.
Does NOT include Search Before Building intro flow (deferred).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update sidebar-security test for Write tool addition
The fallback allowedTools string now includes Write, matching the
sidebar-agent.ts change from commit 68dc957.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: bump version and changelog (v0.13.5.0)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent gstack-relink from double-prefixing gstack-upgrade
gstack-relink now checks if a skill directory is already named gstack-*
before prepending the prefix. Previously, setting skill_prefix=true would
create gstack-gstack-upgrade, breaking the /gstack-upgrade command.
Matches setup script behavior (setup:260) which already has this guard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add double-prefix fix to changelog
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove .factory/ from git tracking and add to .gitignore
Generated Factory Droid skills are build output, same as .agents/.
They should not be committed to the repo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2036,6 +2036,100 @@ describe('telemetry', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('community fixes wave', () => {
|
||||
// Helper to get all generated SKILL.md files
|
||||
function getAllSkillMds(): Array<{ name: string; content: string }> {
|
||||
const results: Array<{ name: string; content: string }> = [];
|
||||
const rootPath = path.join(ROOT, 'SKILL.md');
|
||||
if (fs.existsSync(rootPath)) {
|
||||
results.push({ name: 'root', content: fs.readFileSync(rootPath, 'utf-8') });
|
||||
}
|
||||
for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
||||
const skillPath = path.join(ROOT, entry.name, 'SKILL.md');
|
||||
if (fs.existsSync(skillPath)) {
|
||||
results.push({ name: entry.name, content: fs.readFileSync(skillPath, 'utf-8') });
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// #594 — Discoverability: every SKILL.md.tmpl description contains "gstack"
|
||||
test('every SKILL.md.tmpl description contains "gstack"', () => {
|
||||
for (const skill of ALL_SKILLS) {
|
||||
const tmplPath = skill.dir === '.' ? path.join(ROOT, 'SKILL.md.tmpl') : path.join(ROOT, skill.dir, 'SKILL.md.tmpl');
|
||||
const content = fs.readFileSync(tmplPath, 'utf-8');
|
||||
const desc = extractDescription(content);
|
||||
expect(desc.toLowerCase()).toContain('gstack');
|
||||
}
|
||||
});
|
||||
|
||||
// #594 — Discoverability: first line of each description is under 120 chars
|
||||
test('every SKILL.md.tmpl description first line is under 120 chars', () => {
|
||||
for (const skill of ALL_SKILLS) {
|
||||
const tmplPath = skill.dir === '.' ? path.join(ROOT, 'SKILL.md.tmpl') : path.join(ROOT, skill.dir, 'SKILL.md.tmpl');
|
||||
const content = fs.readFileSync(tmplPath, 'utf-8');
|
||||
const desc = extractDescription(content);
|
||||
const firstLine = desc.split('\n')[0];
|
||||
expect(firstLine.length).toBeLessThanOrEqual(120);
|
||||
}
|
||||
});
|
||||
|
||||
// #573 — Feature signals: ship/SKILL.md contains feature signal detection
|
||||
test('ship/SKILL.md contains feature signal detection in Step 4', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
expect(content.toLowerCase()).toContain('feature signal');
|
||||
});
|
||||
|
||||
// #510 — Context warnings: no SKILL.md contains "running low on context"
|
||||
test('no generated SKILL.md contains "running low on context"', () => {
|
||||
const skills = getAllSkillMds();
|
||||
for (const { name, content } of skills) {
|
||||
expect(content).not.toContain('running low on context');
|
||||
}
|
||||
});
|
||||
|
||||
// #510 — Context warnings: plan-eng-review has explicit anti-warning
|
||||
test('plan-eng-review/SKILL.md contains "Do not preemptively warn"', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'plan-eng-review', 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('Do not preemptively warn');
|
||||
});
|
||||
|
||||
// #474 — Safety Net: no SKILL.md uses find with -delete
|
||||
test('no generated SKILL.md contains find with -delete flag', () => {
|
||||
const skills = getAllSkillMds();
|
||||
for (const { name, content } of skills) {
|
||||
// Match find commands that use -delete (but not prose mentioning the word "delete")
|
||||
const lines = content.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.includes('find ') && line.includes('-delete')) {
|
||||
throw new Error(`${name}/SKILL.md contains find with -delete: ${line.trim()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// #467 — Telemetry: preamble JSONL writes are gated by telemetry setting
|
||||
test('preamble JSONL writes are inside telemetry conditional', () => {
|
||||
const preamble = fs.readFileSync(path.join(ROOT, 'scripts/resolvers/preamble.ts'), 'utf-8');
|
||||
// Find all skill-usage.jsonl write lines
|
||||
const lines = preamble.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes('skill-usage.jsonl') && lines[i].includes('>>')) {
|
||||
// Look backwards for a telemetry conditional within 5 lines
|
||||
let foundConditional = false;
|
||||
for (let j = i - 1; j >= Math.max(0, i - 5); j--) {
|
||||
if (lines[j].includes('_TEL') && lines[j].includes('off')) {
|
||||
foundConditional = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(foundConditional).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('codex commands must not use inline $(git rev-parse --show-toplevel) for cwd', () => {
|
||||
// Regression test: inline $(git rev-parse --show-toplevel) in codex exec -C
|
||||
// or codex review without cd evaluates in whatever cwd the background shell
|
||||
|
||||
Reference in New Issue
Block a user