mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-07 05:56:41 +02:00
merge: origin/main into garrytan/multi-checkpoint-resume
Catches up 7 commits from main: -c6e6a21drefactor: AI slop reduction with cross-model quality review (v0.16.3.0) -7e96fe29fix: security wave 3 — 12 fixes, 7 contributors (v0.16.4.0) -23000672feat: UX behavioral foundations + ux-audit command (v0.17.0.0) -b805aa01feat: Confusion Protocol, Hermes + GBrain hosts, brain-first resolver (v0.18.0.0) -6a785c57fix: ngrok Windows build + close CI error-swallowing gap (v0.18.0.1) -0cc830b6fix: avoid tilde-in-assignment to silence Claude Code permission prompts -cc42f14adocs: gstack compact design doc (tabled pending Anthropic API) Conflict resolution: - ship/SKILL.md.tmpl line 402: HEAD had "## Step 12: Version bump" from the renumber refactor; origin/main added {{GBRAIN_SAVE_RESULTS}} above "## Step 4: Version bump". Resolved by keeping origin/main's new placeholder AND my branch's "Step 12" heading. - ship/SKILL.md: regenerated from resolved template (per CLAUDE.md policy: never resolve generated files manually). All skill docs regenerated for all 9 hosts (claude, kiro, opencode, slate, cursor, openclaw, factory, hermes, gbrain) to reflect the merged template. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+64
@@ -18,6 +18,11 @@ allowed-tools:
|
||||
- Agent
|
||||
- AskUserQuestion
|
||||
- WebSearch
|
||||
triggers:
|
||||
- ship it
|
||||
- create a pr
|
||||
- push to main
|
||||
- deploy this
|
||||
---
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
@@ -86,6 +91,14 @@ fi
|
||||
_ROUTING_DECLINED=$(~/.claude/skills/gstack/bin/gstack-config get routing_declined 2>/dev/null || echo "false")
|
||||
echo "HAS_ROUTING: $_HAS_ROUTING"
|
||||
echo "ROUTING_DECLINED: $_ROUTING_DECLINED"
|
||||
# Vendoring deprecation: detect if CWD has a vendored gstack copy
|
||||
_VENDORED="no"
|
||||
if [ -d ".claude/skills/gstack" ] && [ ! -L ".claude/skills/gstack" ]; then
|
||||
if [ -f ".claude/skills/gstack/VERSION" ] || [ -d ".claude/skills/gstack/.git" ]; then
|
||||
_VENDORED="yes"
|
||||
fi
|
||||
fi
|
||||
echo "VENDORED_GSTACK: $_VENDORED"
|
||||
# Detect spawned session (OpenClaw or other orchestrator)
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
|
||||
```
|
||||
@@ -214,6 +227,38 @@ Say "No problem. You can add routing rules later by running `gstack-config set r
|
||||
|
||||
This only happens once per project. If `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`, skip this entirely.
|
||||
|
||||
If `VENDORED_GSTACK` is `yes`: This project has a vendored copy of gstack at
|
||||
`.claude/skills/gstack/`. Vendoring is deprecated. We will not keep vendored copies
|
||||
up to date, so this project's gstack will fall behind.
|
||||
|
||||
Use AskUserQuestion (one-time per project, check for `~/.gstack/.vendoring-warned-$SLUG` marker):
|
||||
|
||||
> This project has gstack vendored in `.claude/skills/gstack/`. Vendoring is deprecated.
|
||||
> We won't keep this copy up to date, so you'll fall behind on new features and fixes.
|
||||
>
|
||||
> Want to migrate to team mode? It takes about 30 seconds.
|
||||
|
||||
Options:
|
||||
- A) Yes, migrate to team mode now
|
||||
- B) No, I'll handle it myself
|
||||
|
||||
If A:
|
||||
1. Run `git rm -r .claude/skills/gstack/`
|
||||
2. Run `echo '.claude/skills/gstack/' >> .gitignore`
|
||||
3. Run `~/.claude/skills/gstack/bin/gstack-team-init required` (or `optional`)
|
||||
4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"`
|
||||
5. Tell the user: "Done. Each developer now runs: `cd ~/.claude/skills/gstack && ./setup --team`"
|
||||
|
||||
If B: say "OK, you're on your own to keep the vendored copy up to date."
|
||||
|
||||
Always run (regardless of choice):
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true
|
||||
touch ~/.gstack/.vendoring-warned-${SLUG:-unknown}
|
||||
```
|
||||
|
||||
This only happens once per project. If the marker file exists, skip entirely.
|
||||
|
||||
If `SPAWNED_SESSION` is `"true"`, you are running inside a session spawned by an
|
||||
AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option.
|
||||
@@ -221,6 +266,8 @@ AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Focus on completing the task and reporting results via prose output.
|
||||
- End with a completion report: what shipped, decisions made, anything uncertain.
|
||||
|
||||
|
||||
|
||||
## Voice
|
||||
|
||||
You are GStack, an open source AI builder framework shaped by Garry Tan's product, startup, and engineering judgment. Encode how he thinks, not his biography.
|
||||
@@ -339,6 +386,19 @@ AI makes completeness near-free. Always recommend the complete option over short
|
||||
|
||||
Include `Completeness: X/10` for each option (10=all edge cases, 7=happy path, 3=shortcut).
|
||||
|
||||
## Confusion Protocol
|
||||
|
||||
When you encounter high-stakes ambiguity during coding:
|
||||
- Two plausible architectures or data models for the same requirement
|
||||
- A request that contradicts existing patterns and you're unsure which to follow
|
||||
- A destructive operation where the scope is unclear
|
||||
- Missing context that would change your approach significantly
|
||||
|
||||
STOP. Name the ambiguity in one sentence. Present 2-3 options with tradeoffs.
|
||||
Ask the user. Do not guess on architectural or data model decisions.
|
||||
|
||||
This does NOT apply to routine coding, small features, or obvious changes.
|
||||
|
||||
## Repo Ownership — See Something, Say Something
|
||||
|
||||
`REPO_MODE` controls how to handle issues outside your branch:
|
||||
@@ -553,6 +613,8 @@ branch name wherever the instructions say "the base branch" or `<default>`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
# Ship: Fully Automated Ship Workflow
|
||||
|
||||
You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end.
|
||||
@@ -2128,6 +2190,8 @@ staleness detection: if those files are later deleted, the learning can be flagg
|
||||
**Only log genuine discoveries.** Don't log obvious things. Don't log things the user
|
||||
already knows. A good test: would this insight save time in a future session? If yes, log it.
|
||||
|
||||
|
||||
|
||||
## Step 4: Version bump (auto-decide)
|
||||
|
||||
**Idempotency check:** Before bumping, compare VERSION against the base branch.
|
||||
|
||||
+59
@@ -80,6 +80,14 @@ fi
|
||||
_ROUTING_DECLINED=$($GSTACK_BIN/gstack-config get routing_declined 2>/dev/null || echo "false")
|
||||
echo "HAS_ROUTING: $_HAS_ROUTING"
|
||||
echo "ROUTING_DECLINED: $_ROUTING_DECLINED"
|
||||
# Vendoring deprecation: detect if CWD has a vendored gstack copy
|
||||
_VENDORED="no"
|
||||
if [ -d ".agents/skills/gstack" ] && [ ! -L ".agents/skills/gstack" ]; then
|
||||
if [ -f ".agents/skills/gstack/VERSION" ] || [ -d ".agents/skills/gstack/.git" ]; then
|
||||
_VENDORED="yes"
|
||||
fi
|
||||
fi
|
||||
echo "VENDORED_GSTACK: $_VENDORED"
|
||||
# Detect spawned session (OpenClaw or other orchestrator)
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
|
||||
```
|
||||
@@ -208,6 +216,38 @@ Say "No problem. You can add routing rules later by running `gstack-config set r
|
||||
|
||||
This only happens once per project. If `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`, skip this entirely.
|
||||
|
||||
If `VENDORED_GSTACK` is `yes`: This project has a vendored copy of gstack at
|
||||
`.agents/skills/gstack/`. Vendoring is deprecated. We will not keep vendored copies
|
||||
up to date, so this project's gstack will fall behind.
|
||||
|
||||
Use AskUserQuestion (one-time per project, check for `~/.gstack/.vendoring-warned-$SLUG` marker):
|
||||
|
||||
> This project has gstack vendored in `.agents/skills/gstack/`. Vendoring is deprecated.
|
||||
> We won't keep this copy up to date, so you'll fall behind on new features and fixes.
|
||||
>
|
||||
> Want to migrate to team mode? It takes about 30 seconds.
|
||||
|
||||
Options:
|
||||
- A) Yes, migrate to team mode now
|
||||
- B) No, I'll handle it myself
|
||||
|
||||
If A:
|
||||
1. Run `git rm -r .agents/skills/gstack/`
|
||||
2. Run `echo '.agents/skills/gstack/' >> .gitignore`
|
||||
3. Run `$GSTACK_BIN/gstack-team-init required` (or `optional`)
|
||||
4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"`
|
||||
5. Tell the user: "Done. Each developer now runs: `cd $GSTACK_ROOT && ./setup --team`"
|
||||
|
||||
If B: say "OK, you're on your own to keep the vendored copy up to date."
|
||||
|
||||
Always run (regardless of choice):
|
||||
```bash
|
||||
eval "$($GSTACK_BIN/gstack-slug 2>/dev/null)" 2>/dev/null || true
|
||||
touch ~/.gstack/.vendoring-warned-${SLUG:-unknown}
|
||||
```
|
||||
|
||||
This only happens once per project. If the marker file exists, skip entirely.
|
||||
|
||||
If `SPAWNED_SESSION` is `"true"`, you are running inside a session spawned by an
|
||||
AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option.
|
||||
@@ -215,6 +255,8 @@ AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Focus on completing the task and reporting results via prose output.
|
||||
- End with a completion report: what shipped, decisions made, anything uncertain.
|
||||
|
||||
|
||||
|
||||
## Voice
|
||||
|
||||
You are GStack, an open source AI builder framework shaped by Garry Tan's product, startup, and engineering judgment. Encode how he thinks, not his biography.
|
||||
@@ -333,6 +375,19 @@ AI makes completeness near-free. Always recommend the complete option over short
|
||||
|
||||
Include `Completeness: X/10` for each option (10=all edge cases, 7=happy path, 3=shortcut).
|
||||
|
||||
## Confusion Protocol
|
||||
|
||||
When you encounter high-stakes ambiguity during coding:
|
||||
- Two plausible architectures or data models for the same requirement
|
||||
- A request that contradicts existing patterns and you're unsure which to follow
|
||||
- A destructive operation where the scope is unclear
|
||||
- Missing context that would change your approach significantly
|
||||
|
||||
STOP. Name the ambiguity in one sentence. Present 2-3 options with tradeoffs.
|
||||
Ask the user. Do not guess on architectural or data model decisions.
|
||||
|
||||
This does NOT apply to routine coding, small features, or obvious changes.
|
||||
|
||||
## Repo Ownership — See Something, Say Something
|
||||
|
||||
`REPO_MODE` controls how to handle issues outside your branch:
|
||||
@@ -547,6 +602,8 @@ branch name wherever the instructions say "the base branch" or `<default>`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
# Ship: Fully Automated Ship Workflow
|
||||
|
||||
You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end.
|
||||
@@ -1748,6 +1805,8 @@ staleness detection: if those files are later deleted, the learning can be flagg
|
||||
**Only log genuine discoveries.** Don't log obvious things. Don't log things the user
|
||||
already knows. A good test: would this insight save time in a future session? If yes, log it.
|
||||
|
||||
|
||||
|
||||
## Step 4: Version bump (auto-decide)
|
||||
|
||||
**Idempotency check:** Before bumping, compare VERSION against the base branch.
|
||||
|
||||
+59
@@ -82,6 +82,14 @@ fi
|
||||
_ROUTING_DECLINED=$($GSTACK_BIN/gstack-config get routing_declined 2>/dev/null || echo "false")
|
||||
echo "HAS_ROUTING: $_HAS_ROUTING"
|
||||
echo "ROUTING_DECLINED: $_ROUTING_DECLINED"
|
||||
# Vendoring deprecation: detect if CWD has a vendored gstack copy
|
||||
_VENDORED="no"
|
||||
if [ -d ".factory/skills/gstack" ] && [ ! -L ".factory/skills/gstack" ]; then
|
||||
if [ -f ".factory/skills/gstack/VERSION" ] || [ -d ".factory/skills/gstack/.git" ]; then
|
||||
_VENDORED="yes"
|
||||
fi
|
||||
fi
|
||||
echo "VENDORED_GSTACK: $_VENDORED"
|
||||
# Detect spawned session (OpenClaw or other orchestrator)
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
|
||||
```
|
||||
@@ -210,6 +218,38 @@ Say "No problem. You can add routing rules later by running `gstack-config set r
|
||||
|
||||
This only happens once per project. If `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`, skip this entirely.
|
||||
|
||||
If `VENDORED_GSTACK` is `yes`: This project has a vendored copy of gstack at
|
||||
`.factory/skills/gstack/`. Vendoring is deprecated. We will not keep vendored copies
|
||||
up to date, so this project's gstack will fall behind.
|
||||
|
||||
Use AskUserQuestion (one-time per project, check for `~/.gstack/.vendoring-warned-$SLUG` marker):
|
||||
|
||||
> This project has gstack vendored in `.factory/skills/gstack/`. Vendoring is deprecated.
|
||||
> We won't keep this copy up to date, so you'll fall behind on new features and fixes.
|
||||
>
|
||||
> Want to migrate to team mode? It takes about 30 seconds.
|
||||
|
||||
Options:
|
||||
- A) Yes, migrate to team mode now
|
||||
- B) No, I'll handle it myself
|
||||
|
||||
If A:
|
||||
1. Run `git rm -r .factory/skills/gstack/`
|
||||
2. Run `echo '.factory/skills/gstack/' >> .gitignore`
|
||||
3. Run `$GSTACK_BIN/gstack-team-init required` (or `optional`)
|
||||
4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"`
|
||||
5. Tell the user: "Done. Each developer now runs: `cd $GSTACK_ROOT && ./setup --team`"
|
||||
|
||||
If B: say "OK, you're on your own to keep the vendored copy up to date."
|
||||
|
||||
Always run (regardless of choice):
|
||||
```bash
|
||||
eval "$($GSTACK_BIN/gstack-slug 2>/dev/null)" 2>/dev/null || true
|
||||
touch ~/.gstack/.vendoring-warned-${SLUG:-unknown}
|
||||
```
|
||||
|
||||
This only happens once per project. If the marker file exists, skip entirely.
|
||||
|
||||
If `SPAWNED_SESSION` is `"true"`, you are running inside a session spawned by an
|
||||
AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option.
|
||||
@@ -217,6 +257,8 @@ AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Focus on completing the task and reporting results via prose output.
|
||||
- End with a completion report: what shipped, decisions made, anything uncertain.
|
||||
|
||||
|
||||
|
||||
## Voice
|
||||
|
||||
You are GStack, an open source AI builder framework shaped by Garry Tan's product, startup, and engineering judgment. Encode how he thinks, not his biography.
|
||||
@@ -335,6 +377,19 @@ AI makes completeness near-free. Always recommend the complete option over short
|
||||
|
||||
Include `Completeness: X/10` for each option (10=all edge cases, 7=happy path, 3=shortcut).
|
||||
|
||||
## Confusion Protocol
|
||||
|
||||
When you encounter high-stakes ambiguity during coding:
|
||||
- Two plausible architectures or data models for the same requirement
|
||||
- A request that contradicts existing patterns and you're unsure which to follow
|
||||
- A destructive operation where the scope is unclear
|
||||
- Missing context that would change your approach significantly
|
||||
|
||||
STOP. Name the ambiguity in one sentence. Present 2-3 options with tradeoffs.
|
||||
Ask the user. Do not guess on architectural or data model decisions.
|
||||
|
||||
This does NOT apply to routine coding, small features, or obvious changes.
|
||||
|
||||
## Repo Ownership — See Something, Say Something
|
||||
|
||||
`REPO_MODE` controls how to handle issues outside your branch:
|
||||
@@ -549,6 +604,8 @@ branch name wherever the instructions say "the base branch" or `<default>`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
# Ship: Fully Automated Ship Workflow
|
||||
|
||||
You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end.
|
||||
@@ -2124,6 +2181,8 @@ staleness detection: if those files are later deleted, the learning can be flagg
|
||||
**Only log genuine discoveries.** Don't log obvious things. Don't log things the user
|
||||
already knows. A good test: would this insight save time in a future session? If yes, log it.
|
||||
|
||||
|
||||
|
||||
## Step 4: Version bump (auto-decide)
|
||||
|
||||
**Idempotency check:** Before bumping, compare VERSION against the base branch.
|
||||
|
||||
+20
-60
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Gemini CLI E2E tests — verify skills work when invoked by Gemini CLI.
|
||||
* Gemini CLI E2E smoke test — verify Gemini CLI can start and discover skills.
|
||||
*
|
||||
* Spawns `gemini -p` with stream-json output in the repo root (where
|
||||
* .agents/skills/ already exists), parses JSONL events, and validates
|
||||
* structured results. Follows the same pattern as codex-e2e.test.ts.
|
||||
* This is a lightweight smoke test, not a full integration test. Gemini CLI
|
||||
* gets lost in worktrees and times out on complex tasks. The smoke test
|
||||
* validates that the skill files are structured correctly for Gemini's
|
||||
* .agents/skills/ discovery mechanism.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - `gemini` binary installed (npm install -g @google/gemini-cli)
|
||||
@@ -48,10 +49,9 @@ if (!evalsEnabled) {
|
||||
|
||||
// --- Diff-based test selection ---
|
||||
|
||||
// Gemini E2E touchfiles — keyed by test name, same pattern as Codex E2E
|
||||
// Gemini E2E touchfiles — keyed by test name
|
||||
const GEMINI_E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'gemini-discover-skill': ['.agents/skills/**', 'test/helpers/gemini-session-runner.ts'],
|
||||
'gemini-review-findings': ['review/**', '.agents/skills/gstack-review/**', 'test/helpers/gemini-session-runner.ts'],
|
||||
'gemini-smoke': ['.agents/skills/**', 'test/helpers/gemini-session-runner.ts'],
|
||||
};
|
||||
|
||||
let selectedTests: string[] | null = null; // null = run all
|
||||
@@ -71,7 +71,6 @@ if (evalsEnabled && !process.env.EVALS_ALL) {
|
||||
}
|
||||
process.stderr.write('\n');
|
||||
}
|
||||
// If changedFiles is empty (e.g., on main branch), selectedTests stays null -> run all
|
||||
}
|
||||
|
||||
/** Skip an individual test if not selected by diff-based selection. */
|
||||
@@ -84,7 +83,6 @@ function testIfSelected(testName: string, fn: () => Promise<void>, timeout: numb
|
||||
|
||||
const evalCollector = evalsEnabled && !SKIP ? new EvalCollector('e2e-gemini') : null;
|
||||
|
||||
/** DRY helper to record a Gemini E2E test result into the eval collector. */
|
||||
function recordGeminiE2E(name: string, result: GeminiResult, passed: boolean) {
|
||||
evalCollector?.addTest({
|
||||
name,
|
||||
@@ -92,14 +90,13 @@ function recordGeminiE2E(name: string, result: GeminiResult, passed: boolean) {
|
||||
tier: 'e2e',
|
||||
passed,
|
||||
duration_ms: result.durationMs,
|
||||
cost_usd: 0, // Gemini doesn't report cost in USD; tokens are tracked
|
||||
cost_usd: 0,
|
||||
output: result.output?.slice(0, 2000),
|
||||
turns_used: result.toolCalls.length, // approximate: tool calls as turns
|
||||
turns_used: result.toolCalls.length,
|
||||
exit_reason: result.exitCode === 0 ? 'success' : `exit_code_${result.exitCode}`,
|
||||
});
|
||||
}
|
||||
|
||||
/** Print cost summary after a Gemini E2E test. */
|
||||
function logGeminiCost(label: string, result: GeminiResult) {
|
||||
const durationSec = Math.round(result.durationMs / 1000);
|
||||
console.log(`${label}: ${result.tokens} tokens, ${result.toolCalls.length} tool calls, ${durationSec}s`);
|
||||
@@ -125,59 +122,22 @@ describeGemini('Gemini E2E', () => {
|
||||
harvestAndCleanup('gemini');
|
||||
});
|
||||
|
||||
testIfSelected('gemini-discover-skill', async () => {
|
||||
// Run Gemini in an isolated worktree (has .agents/skills/ copied from ROOT)
|
||||
testIfSelected('gemini-smoke', async () => {
|
||||
// Smoke test: can Gemini start, read the repo, and produce output?
|
||||
// Uses a simple prompt that doesn't require skill invocation or complex navigation.
|
||||
const result = await runGeminiSkill({
|
||||
prompt: 'List any skills or instructions you have available. Just list the names.',
|
||||
timeoutMs: 60_000,
|
||||
prompt: 'What is this project? Answer in one sentence based on the README.',
|
||||
timeoutMs: 90_000,
|
||||
cwd: testWorktree,
|
||||
});
|
||||
|
||||
logGeminiCost('gemini-discover-skill', result);
|
||||
logGeminiCost('gemini-smoke', result);
|
||||
|
||||
// Gemini should have produced some output
|
||||
const passed = result.exitCode === 0 && result.output.length > 0;
|
||||
recordGeminiE2E('gemini-discover-skill', result, passed);
|
||||
// Pass if Gemini produced any meaningful output (even with non-zero exit from timeout)
|
||||
const hasOutput = result.output.length > 10;
|
||||
const passed = hasOutput;
|
||||
recordGeminiE2E('gemini-smoke', result, passed);
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.output.length).toBeGreaterThan(0);
|
||||
// The output should reference skills in some form
|
||||
const outputLower = result.output.toLowerCase();
|
||||
expect(
|
||||
outputLower.includes('review') || outputLower.includes('gstack') || outputLower.includes('skill'),
|
||||
).toBe(true);
|
||||
expect(result.output.length, 'Gemini should produce output').toBeGreaterThan(10);
|
||||
}, 120_000);
|
||||
|
||||
testIfSelected('gemini-review-findings', async () => {
|
||||
// Run gstack-review skill via Gemini on worktree (isolated from main working tree)
|
||||
const result = await runGeminiSkill({
|
||||
prompt: 'Run the gstack-review skill on this repository. Review the current branch diff and report your findings.',
|
||||
timeoutMs: 540_000,
|
||||
cwd: testWorktree,
|
||||
});
|
||||
|
||||
logGeminiCost('gemini-review-findings', result);
|
||||
|
||||
// Should produce structured review-like output
|
||||
const output = result.output;
|
||||
const passed = result.exitCode === 0 && output.length > 50;
|
||||
recordGeminiE2E('gemini-review-findings', result, passed);
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(output.length).toBeGreaterThan(50);
|
||||
|
||||
// Review output should contain some review-like content
|
||||
const outputLower = output.toLowerCase();
|
||||
const hasReviewContent =
|
||||
outputLower.includes('finding') ||
|
||||
outputLower.includes('issue') ||
|
||||
outputLower.includes('review') ||
|
||||
outputLower.includes('change') ||
|
||||
outputLower.includes('diff') ||
|
||||
outputLower.includes('clean') ||
|
||||
outputLower.includes('no issues') ||
|
||||
outputLower.includes('p1') ||
|
||||
outputLower.includes('p2');
|
||||
expect(hasReviewContent).toBe(true);
|
||||
}, 600_000);
|
||||
});
|
||||
|
||||
@@ -122,9 +122,8 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'codex-discover-skill': ['codex/**', '.agents/skills/**', 'test/helpers/codex-session-runner.ts', 'lib/worktree.ts'],
|
||||
'codex-review-findings': ['review/**', '.agents/skills/gstack-review/**', 'codex/**', 'test/helpers/codex-session-runner.ts', 'lib/worktree.ts'],
|
||||
|
||||
// Gemini E2E (tests skills via Gemini CLI + worktree)
|
||||
'gemini-discover-skill': ['.agents/skills/**', 'test/helpers/gemini-session-runner.ts', 'lib/worktree.ts'],
|
||||
'gemini-review-findings': ['review/**', '.agents/skills/gstack-review/**', 'test/helpers/gemini-session-runner.ts', 'lib/worktree.ts'],
|
||||
// Gemini E2E — smoke test only (Gemini gets lost in worktrees on complex tasks)
|
||||
'gemini-smoke': ['.agents/skills/**', 'test/helpers/gemini-session-runner.ts', 'lib/worktree.ts'],
|
||||
|
||||
|
||||
// Coverage audit (shared fixture) + triage + gates
|
||||
@@ -284,8 +283,7 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
|
||||
// Multi-AI — periodic (require external CLIs)
|
||||
'codex-discover-skill': 'periodic',
|
||||
'codex-review-findings': 'periodic',
|
||||
'gemini-discover-skill': 'periodic',
|
||||
'gemini-review-findings': 'periodic',
|
||||
'gemini-smoke': 'periodic',
|
||||
|
||||
// Design — gate for cheap functional, periodic for Opus/quality
|
||||
'design-consultation-core': 'periodic',
|
||||
|
||||
@@ -30,8 +30,8 @@ const ROOT = path.resolve(import.meta.dir, '..');
|
||||
// ─── hosts/index.ts ─────────────────────────────────────────
|
||||
|
||||
describe('hosts/index.ts', () => {
|
||||
test('ALL_HOST_CONFIGS has 8 hosts', () => {
|
||||
expect(ALL_HOST_CONFIGS.length).toBe(8);
|
||||
test('ALL_HOST_CONFIGS has 10 hosts', () => {
|
||||
expect(ALL_HOST_CONFIGS.length).toBe(10);
|
||||
});
|
||||
|
||||
test('ALL_HOST_NAMES matches config names', () => {
|
||||
@@ -479,9 +479,8 @@ describe('host config correctness', () => {
|
||||
expect(openclaw.pathRewrites.some(r => r.from === 'CLAUDE.md' && r.to === 'AGENTS.md')).toBe(true);
|
||||
});
|
||||
|
||||
test('openclaw has adapter path', () => {
|
||||
expect(openclaw.adapter).toBeDefined();
|
||||
expect(openclaw.adapter).toContain('openclaw-adapter');
|
||||
test('openclaw has no adapter (dead code removed)', () => {
|
||||
expect(openclaw.adapter).toBeUndefined();
|
||||
});
|
||||
|
||||
test('openclaw has no staticFiles (SOUL.md removed)', () => {
|
||||
|
||||
@@ -286,18 +286,21 @@ describeIfSelected('Base branch detection', ['review-base-branch', 'ship-base-br
|
||||
run('git', ['add', 'app.rb'], dir);
|
||||
run('git', ['commit', '-m', 'feat: add hello method'], dir);
|
||||
|
||||
// Copy review skill files
|
||||
fs.copyFileSync(path.join(ROOT, 'review', 'SKILL.md'), path.join(dir, 'review-SKILL.md'));
|
||||
fs.copyFileSync(path.join(ROOT, 'review', 'checklist.md'), path.join(dir, 'review-checklist.md'));
|
||||
fs.copyFileSync(path.join(ROOT, 'review', 'greptile-triage.md'), path.join(dir, 'review-greptile-triage.md'));
|
||||
// Extract only Step 0 (base branch detection) + minimal review instructions
|
||||
// Full SKILL.md is ~1500 lines — copying it causes the agent to spend all turns reading
|
||||
const full = fs.readFileSync(path.join(ROOT, 'review', 'SKILL.md'), 'utf-8');
|
||||
const step0Start = full.indexOf('## Step 0: Detect platform and base branch');
|
||||
const step1Start = full.indexOf('## Step 1: Check branch');
|
||||
const step1End = full.indexOf('---', step1Start + 10);
|
||||
const extracted = full.slice(step0Start, step1End > step1Start ? step1End : step1Start + 500);
|
||||
fs.writeFileSync(path.join(dir, 'review-SKILL.md'), extracted);
|
||||
|
||||
const result = await runSkillTest({
|
||||
prompt: `You are in a git repo on a feature branch with changes.
|
||||
Read review-SKILL.md for the review workflow instructions.
|
||||
Also read review-checklist.md and apply it.
|
||||
Read review-SKILL.md for the base branch detection instructions.
|
||||
|
||||
IMPORTANT: Follow Step 0 to detect the base branch. Since there is no remote, gh commands will fail — fall back to main.
|
||||
Then run the review against the detected base branch.
|
||||
Then run git diff against the detected base branch and write a brief review.
|
||||
Write your findings to ${dir}/review-output.md`,
|
||||
workingDirectory: dir,
|
||||
maxTurns: 15,
|
||||
|
||||
@@ -60,10 +60,9 @@ if (evalsEnabled && process.env.EVALS_TIER) {
|
||||
// --- Helper functions ---
|
||||
|
||||
/** Copy all SKILL.md files for auto-discovery.
|
||||
* Install to BOTH project-level (.claude/skills/) AND user-level (~/.claude/skills/)
|
||||
* because Claude Code discovers skills from both locations. In CI containers,
|
||||
* $HOME may differ from the working directory, so we need both paths to ensure
|
||||
* the Skill tool appears in Claude's available tools list. */
|
||||
* Installs to project-level (.claude/skills/) only. Writing to the user's
|
||||
* ~/.claude/skills/ is unsafe: it may contain symlinks from the real gstack
|
||||
* install that point to different worktrees or dangling targets. */
|
||||
function installSkills(tmpDir: string) {
|
||||
const skillDirs = [
|
||||
'', // root gstack SKILL.md
|
||||
@@ -73,24 +72,16 @@ function installSkills(tmpDir: string) {
|
||||
'gstack-upgrade', 'humanizer',
|
||||
];
|
||||
|
||||
// Install to both project-level and user-level skill directories
|
||||
const homeDir = process.env.HOME || os.homedir();
|
||||
const installTargets = [
|
||||
path.join(tmpDir, '.claude', 'skills'), // project-level
|
||||
path.join(homeDir, '.claude', 'skills'), // user-level (~/.claude/skills/)
|
||||
];
|
||||
const targetBase = path.join(tmpDir, '.claude', 'skills');
|
||||
|
||||
for (const skill of skillDirs) {
|
||||
const srcPath = path.join(ROOT, skill, 'SKILL.md');
|
||||
if (!fs.existsSync(srcPath)) continue;
|
||||
|
||||
const skillName = skill || 'gstack';
|
||||
|
||||
for (const targetBase of installTargets) {
|
||||
const destDir = path.join(targetBase, skillName);
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
fs.copyFileSync(srcPath, path.join(destDir, 'SKILL.md'));
|
||||
}
|
||||
const destDir = path.join(targetBase, skillName);
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
fs.copyFileSync(srcPath, path.join(destDir, 'SKILL.md'));
|
||||
}
|
||||
|
||||
// Write a CLAUDE.md with explicit routing instructions.
|
||||
|
||||
@@ -143,6 +143,7 @@ describe('Command registry consistency', () => {
|
||||
const validKeys = new Set([
|
||||
'interactive', 'compact', 'depth', 'selector',
|
||||
'diff', 'annotate', 'outputPath', 'cursorInteractive',
|
||||
'heatmap',
|
||||
]);
|
||||
for (const flag of SNAPSHOT_FLAGS) {
|
||||
expect(validKeys.has(flag.optionKey)).toBe(true);
|
||||
|
||||
@@ -85,11 +85,11 @@ describe('gstack-settings-hook', () => {
|
||||
expect(settings.hooks).toBeUndefined();
|
||||
});
|
||||
|
||||
test('remove is safe when settings.json does not exist', () => {
|
||||
test('remove exits 1 when settings.json does not exist', () => {
|
||||
const result = run(`${SETTINGS_HOOK} remove /path/to/gstack-session-update`, {
|
||||
env: { GSTACK_SETTINGS_FILE: settingsFile },
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('remove preserves other hooks', () => {
|
||||
|
||||
Reference in New Issue
Block a user