mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 03:35:09 +02:00
fix: enforce 1024-char Codex description limit + auto-heal stale installs
Build-time guard in gen-skill-docs.ts throws if any Codex description exceeds 1024 chars. Setup always regenerates .agents/ to prevent stale files. One-time migration in gstack-update-check deletes oversized SKILL.md files so they get regenerated on next setup/upgrade.
This commit is contained in:
@@ -224,6 +224,8 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna
|
||||
|
||||
**Stale install?** Run `/gstack-upgrade` — or set `auto_upgrade: true` in `~/.gstack/config.yaml`
|
||||
|
||||
**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. Fix: `cd ~/.codex/skills/gstack && git pull && ./setup --host codex` — or for repo-local installs: `cd "$(readlink -f .agents/skills/gstack)" && git pull && ./setup --host codex`
|
||||
|
||||
**Windows users:** gstack works on Windows 11 via Git Bash or WSL. Node.js is required in addition to Bun — Bun has a known bug with Playwright's pipe transport on Windows ([bun#4253](https://github.com/oven-sh/bun/issues/4253)). The browse server automatically falls back to Node.js. Make sure both `bun` and `node` are on your PATH.
|
||||
|
||||
**Claude says it can't see the skills?** Make sure your project's `CLAUDE.md` has a gstack section. Add this:
|
||||
|
||||
@@ -489,6 +489,20 @@ Shipped in v0.8.3. Step 8.5 added to `/ship` — after creating the PR, `/ship`
|
||||
**Depends on:** gstack-diff-scope (shipped)
|
||||
|
||||
|
||||
## Codex
|
||||
|
||||
### Codex→Claude reverse buddy check skill
|
||||
|
||||
**What:** A Codex-native skill (`.agents/skills/gstack-claude/SKILL.md`) that runs `claude -p` to get an independent second opinion from Claude — the reverse of what `/codex` does today from Claude Code.
|
||||
|
||||
**Why:** Codex users deserve the same cross-model challenge that Claude users get via `/codex`. Currently the flow is one-way (Claude→Codex). Codex users have no way to get a Claude second opinion.
|
||||
|
||||
**Context:** The `/codex` skill template (`codex/SKILL.md.tmpl`) shows the pattern — it wraps `codex exec` with JSONL parsing, timeout handling, and structured output. The reverse skill would wrap `claude -p` with similar infrastructure. Would be generated into `.agents/skills/gstack-claude/` by `gen-skill-docs --host codex`.
|
||||
|
||||
**Effort:** M (human: ~2 weeks / CC: ~30 min)
|
||||
**Priority:** P1
|
||||
**Depends on:** None
|
||||
|
||||
## Completeness
|
||||
|
||||
### Completeness metrics dashboard
|
||||
|
||||
@@ -31,6 +31,24 @@ if [ "$_UC" = "false" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Migration: fix stale Codex descriptions (one-time) ───────
|
||||
# Existing installs may have .agents/skills/gstack/SKILL.md with oversized
|
||||
# descriptions (>1024 chars) that Codex rejects. We can't regenerate from
|
||||
# the runtime root (no bun/scripts), so delete oversized files — the next
|
||||
# ./setup or /gstack-upgrade will regenerate them properly.
|
||||
# Marker file ensures this runs at most once per install.
|
||||
if [ ! -f "$STATE_DIR/.codex-desc-healed" ]; then
|
||||
for _AGENTS_SKILL in "$GSTACK_DIR"/.agents/skills/*/SKILL.md; do
|
||||
[ -f "$_AGENTS_SKILL" ] || continue
|
||||
_DESC=$(awk '/^---$/{n++;next}n==1&&/^description:/{d=1;sub(/^description:\s*/,"");if(length>0)print;next}d&&/^ /{sub(/^ /,"");print;next}d{d=0}' "$_AGENTS_SKILL" | wc -c | tr -d ' ')
|
||||
if [ "${_DESC:-0}" -gt 1024 ]; then
|
||||
rm -f "$_AGENTS_SKILL"
|
||||
fi
|
||||
done
|
||||
mkdir -p "$STATE_DIR"
|
||||
touch "$STATE_DIR/.codex-desc-healed"
|
||||
fi
|
||||
|
||||
# ─── Snooze helper ──────────────────────────────────────────
|
||||
# check_snooze <remote_version>
|
||||
# Returns 0 if snoozed (should stay quiet), 1 if not snoozed (should output).
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "0.9.8.0",
|
||||
"version": "0.11.7.0",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -2910,6 +2910,15 @@ function transformFrontmatter(content: string, host: Host): string {
|
||||
const body = content.slice(fmEnd + 4); // includes the leading \n after ---
|
||||
const { name, description } = extractNameAndDescription(content);
|
||||
|
||||
// Codex 1024-char description limit — fail build, don't ship broken skills
|
||||
const MAX_DESC = 1024;
|
||||
if (description.length > MAX_DESC) {
|
||||
throw new Error(
|
||||
`Codex description for "${name}" is ${description.length} chars (max ${MAX_DESC}). ` +
|
||||
`Compress the description in the .tmpl file.`
|
||||
);
|
||||
}
|
||||
|
||||
// Re-emit Codex frontmatter (name + description only)
|
||||
const indentedDesc = description.split('\n').map(l => ` ${l}`).join('\n');
|
||||
const codexFm = `---\nname: ${name}\ndescription: |\n${indentedDesc}\n---`;
|
||||
|
||||
@@ -128,17 +128,13 @@ if [ ! -x "$BROWSE_BIN" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1b. Generate .agents/ Codex skill docs if missing or stale
|
||||
# 1b. Generate .agents/ Codex skill docs — always regenerate to prevent stale descriptions.
|
||||
# .agents/ is no longer committed — generated at setup time from .tmpl templates.
|
||||
# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh
|
||||
# but .agents/ hasn't been generated yet, e.g., fresh clone).
|
||||
# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh).
|
||||
# Always regenerate: generation is fast (<2s) and mtime-based staleness checks are fragile
|
||||
# (miss stale files when timestamps match after clone/checkout/upgrade).
|
||||
AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills"
|
||||
NEEDS_AGENTS_GEN=0
|
||||
if [ ! -d "$AGENTS_DIR" ]; then
|
||||
NEEDS_AGENTS_GEN=1
|
||||
elif [ -n "$(find "$SOURCE_GSTACK_DIR" -maxdepth 2 -name 'SKILL.md.tmpl' -newer "$AGENTS_DIR" -print -quit 2>/dev/null)" ]; then
|
||||
NEEDS_AGENTS_GEN=1
|
||||
fi
|
||||
NEEDS_AGENTS_GEN=1
|
||||
|
||||
if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
||||
echo "Generating .agents/ skill docs..."
|
||||
|
||||
@@ -139,6 +139,9 @@ describeCodex('Codex E2E', () => {
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.output.length).toBeGreaterThan(0);
|
||||
// Skill loading errors mean our generated SKILL.md files are broken
|
||||
expect(result.stderr).not.toContain('invalid');
|
||||
expect(result.stderr).not.toContain('Skipped loading');
|
||||
// The output should reference the skill name in some form
|
||||
const outputLower = result.output.toLowerCase();
|
||||
expect(
|
||||
|
||||
@@ -139,6 +139,25 @@ describe('gen-skill-docs', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test(`every Codex SKILL.md description stays within ${MAX_SKILL_DESCRIPTION_LENGTH} chars`, () => {
|
||||
const agentsDir = path.join(ROOT, '.agents', 'skills');
|
||||
if (!fs.existsSync(agentsDir)) return; // skip if not generated
|
||||
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const skillMd = path.join(agentsDir, entry.name, 'SKILL.md');
|
||||
if (!fs.existsSync(skillMd)) continue;
|
||||
const content = fs.readFileSync(skillMd, 'utf-8');
|
||||
const description = extractDescription(content);
|
||||
expect(description.length).toBeLessThanOrEqual(MAX_SKILL_DESCRIPTION_LENGTH);
|
||||
}
|
||||
});
|
||||
|
||||
test('package.json version matches VERSION file', () => {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
||||
const version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||
expect(pkg.version).toBe(version);
|
||||
});
|
||||
|
||||
test('generated files are fresh (match --dry-run)', () => {
|
||||
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--dry-run'], {
|
||||
cwd: ROOT,
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface CodexResult {
|
||||
durationMs: number; // Wall clock time
|
||||
sessionId: string | null; // Thread ID for session continuity
|
||||
rawLines: string[]; // Raw JSONL lines for debugging
|
||||
stderr: string; // Stderr output (skill loading errors, auth failures)
|
||||
}
|
||||
|
||||
// --- JSONL parser (ported from Python in codex/SKILL.md.tmpl) ---
|
||||
@@ -167,6 +168,7 @@ export async function runCodexSkill(opts: {
|
||||
durationMs: Date.now() - startTime,
|
||||
sessionId: null,
|
||||
rawLines: [],
|
||||
stderr: '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -282,6 +284,7 @@ export async function runCodexSkill(opts: {
|
||||
durationMs,
|
||||
sessionId: parsed.sessionId,
|
||||
rawLines: collectedLines,
|
||||
stderr,
|
||||
};
|
||||
} finally {
|
||||
// Clean up temp HOME
|
||||
|
||||
Reference in New Issue
Block a user