mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 13:45:35 +02:00
chore: merge origin/main, resolve VERSION + CHANGELOG conflicts
Bump to v0.15.13.0 (above main's 0.15.12.0). Resolve preamble.ts conflicts (keep both vendoring detection and spawned session check). Regenerate all SKILL.md files from resolved sources. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+34
@@ -86,6 +86,8 @@ 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"
|
||||
# Detect spawned session (OpenClaw or other orchestrator)
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills AND do not
|
||||
@@ -212,6 +214,13 @@ 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 `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.
|
||||
- Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro.
|
||||
- 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.
|
||||
@@ -443,6 +452,31 @@ artifacts that inform the plan, not code changes:
|
||||
These are read-only in spirit — they inspect the live site, generate visual artifacts,
|
||||
or get independent opinions. They do NOT modify project source files.
|
||||
|
||||
## Skill Invocation During Plan Mode
|
||||
|
||||
If a user invokes a skill during plan mode, that invoked skill workflow takes
|
||||
precedence over generic plan mode behavior until it finishes or the user explicitly
|
||||
cancels that skill.
|
||||
|
||||
Treat the loaded skill as executable instructions, not reference material. Follow
|
||||
it step by step. Do not summarize, skip, reorder, or shortcut its steps.
|
||||
|
||||
If the skill says to use AskUserQuestion, do that. Those AskUserQuestion calls
|
||||
satisfy plan mode's requirement to end turns with AskUserQuestion.
|
||||
|
||||
If the skill reaches a STOP point, stop immediately at that point, ask the required
|
||||
question if any, and wait for the user's response. Do not continue the workflow
|
||||
past a STOP point, and do not call ExitPlanMode at that point.
|
||||
|
||||
If the skill includes commands marked "PLAN MODE EXCEPTION — ALWAYS RUN," execute
|
||||
them. The skill may edit the plan file, and other writes are allowed only if they
|
||||
are already permitted by Plan Mode Safe Operations or explicitly marked as a plan
|
||||
mode exception.
|
||||
|
||||
Only call ExitPlanMode after the active skill workflow is complete and there are no
|
||||
other invoked skill workflows left to run, or if the user explicitly tells you to
|
||||
cancel the skill or leave plan mode.
|
||||
|
||||
## Plan Status Footer
|
||||
|
||||
When you are in plan mode and about to call ExitPlanMode:
|
||||
|
||||
+34
@@ -80,6 +80,8 @@ 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"
|
||||
# Detect spawned session (OpenClaw or other orchestrator)
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills AND do not
|
||||
@@ -206,6 +208,13 @@ 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 `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.
|
||||
- Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro.
|
||||
- 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.
|
||||
@@ -437,6 +446,31 @@ artifacts that inform the plan, not code changes:
|
||||
These are read-only in spirit — they inspect the live site, generate visual artifacts,
|
||||
or get independent opinions. They do NOT modify project source files.
|
||||
|
||||
## Skill Invocation During Plan Mode
|
||||
|
||||
If a user invokes a skill during plan mode, that invoked skill workflow takes
|
||||
precedence over generic plan mode behavior until it finishes or the user explicitly
|
||||
cancels that skill.
|
||||
|
||||
Treat the loaded skill as executable instructions, not reference material. Follow
|
||||
it step by step. Do not summarize, skip, reorder, or shortcut its steps.
|
||||
|
||||
If the skill says to use AskUserQuestion, do that. Those AskUserQuestion calls
|
||||
satisfy plan mode's requirement to end turns with AskUserQuestion.
|
||||
|
||||
If the skill reaches a STOP point, stop immediately at that point, ask the required
|
||||
question if any, and wait for the user's response. Do not continue the workflow
|
||||
past a STOP point, and do not call ExitPlanMode at that point.
|
||||
|
||||
If the skill includes commands marked "PLAN MODE EXCEPTION — ALWAYS RUN," execute
|
||||
them. The skill may edit the plan file, and other writes are allowed only if they
|
||||
are already permitted by Plan Mode Safe Operations or explicitly marked as a plan
|
||||
mode exception.
|
||||
|
||||
Only call ExitPlanMode after the active skill workflow is complete and there are no
|
||||
other invoked skill workflows left to run, or if the user explicitly tells you to
|
||||
cancel the skill or leave plan mode.
|
||||
|
||||
## Plan Status Footer
|
||||
|
||||
When you are in plan mode and about to call ExitPlanMode:
|
||||
|
||||
+34
@@ -82,6 +82,8 @@ 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"
|
||||
# Detect spawned session (OpenClaw or other orchestrator)
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills AND do not
|
||||
@@ -208,6 +210,13 @@ 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 `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.
|
||||
- Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro.
|
||||
- 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.
|
||||
@@ -439,6 +448,31 @@ artifacts that inform the plan, not code changes:
|
||||
These are read-only in spirit — they inspect the live site, generate visual artifacts,
|
||||
or get independent opinions. They do NOT modify project source files.
|
||||
|
||||
## Skill Invocation During Plan Mode
|
||||
|
||||
If a user invokes a skill during plan mode, that invoked skill workflow takes
|
||||
precedence over generic plan mode behavior until it finishes or the user explicitly
|
||||
cancels that skill.
|
||||
|
||||
Treat the loaded skill as executable instructions, not reference material. Follow
|
||||
it step by step. Do not summarize, skip, reorder, or shortcut its steps.
|
||||
|
||||
If the skill says to use AskUserQuestion, do that. Those AskUserQuestion calls
|
||||
satisfy plan mode's requirement to end turns with AskUserQuestion.
|
||||
|
||||
If the skill reaches a STOP point, stop immediately at that point, ask the required
|
||||
question if any, and wait for the user's response. Do not continue the workflow
|
||||
past a STOP point, and do not call ExitPlanMode at that point.
|
||||
|
||||
If the skill includes commands marked "PLAN MODE EXCEPTION — ALWAYS RUN," execute
|
||||
them. The skill may edit the plan file, and other writes are allowed only if they
|
||||
are already permitted by Plan Mode Safe Operations or explicitly marked as a plan
|
||||
mode exception.
|
||||
|
||||
Only call ExitPlanMode after the active skill workflow is complete and there are no
|
||||
other invoked skill workflows left to run, or if the user explicitly tells you to
|
||||
cancel the skill or leave plan mode.
|
||||
|
||||
## Plan Status Footer
|
||||
|
||||
When you are in plan mode and about to call ExitPlanMode:
|
||||
|
||||
@@ -749,6 +749,22 @@ describe('TEST_COVERAGE_AUDIT placeholders', () => {
|
||||
expect(shipSkill).toContain(phrase);
|
||||
}
|
||||
});
|
||||
|
||||
test('ship SKILL.md contains review army specialist dispatch', () => {
|
||||
expect(shipSkill).toContain('Specialist Dispatch');
|
||||
expect(shipSkill).toContain('Step 3.55');
|
||||
expect(shipSkill).toContain('Step 3.56');
|
||||
});
|
||||
|
||||
test('ship SKILL.md contains cross-review finding dedup', () => {
|
||||
expect(shipSkill).toContain('Cross-review finding dedup');
|
||||
expect(shipSkill).toContain('Step 3.57');
|
||||
});
|
||||
|
||||
test('ship SKILL.md contains re-run idempotency behavior', () => {
|
||||
expect(shipSkill).toContain('Re-run behavior (idempotency)');
|
||||
expect(shipSkill).toContain('Never skip a verification step');
|
||||
});
|
||||
});
|
||||
|
||||
// --- {{TEST_FAILURE_TRIAGE}} resolver tests ---
|
||||
|
||||
@@ -131,6 +131,165 @@ describe("gstack-global-discover", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("codex large session_meta parsing", () => {
|
||||
let codexDir: string;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = mkdtempSync(join(tmpdir(), "gstack-codex-test-"));
|
||||
// Build a realistic ~/.codex/sessions/YYYY/MM/DD structure
|
||||
const now = new Date();
|
||||
const y = now.getFullYear().toString();
|
||||
const m = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(now.getDate()).padStart(2, "0");
|
||||
codexDir = join(tmpDir, "codex-home", "sessions", y, m, d);
|
||||
mkdirSync(codexDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function writeCodexSession(
|
||||
dir: string,
|
||||
cwd: string,
|
||||
baseInstructionsSize: number
|
||||
): string {
|
||||
const padding = "x".repeat(baseInstructionsSize);
|
||||
const line = JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
type: "session_meta",
|
||||
payload: {
|
||||
id: `test-${Date.now()}`,
|
||||
timestamp: new Date().toISOString(),
|
||||
cwd,
|
||||
originator: "codex_exec",
|
||||
cli_version: "0.118.0",
|
||||
source: "exec",
|
||||
model_provider: "openai",
|
||||
base_instructions: { text: padding },
|
||||
},
|
||||
});
|
||||
const name = `rollout-${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2)}.jsonl`;
|
||||
const filePath = join(dir, name);
|
||||
writeFileSync(filePath, line + "\n");
|
||||
return filePath;
|
||||
}
|
||||
|
||||
test("discovers codex sessions with >4KB session_meta via CLI", () => {
|
||||
// Create a git repo as the session target
|
||||
const repoDir = join(tmpDir, "fake-repo");
|
||||
mkdirSync(repoDir);
|
||||
spawnSync("git", ["init"], { cwd: repoDir, stdio: "pipe" });
|
||||
spawnSync("git", ["commit", "--allow-empty", "-m", "init"], {
|
||||
cwd: repoDir,
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
// Write a session with a 20KB first line (simulates Codex v0.117+)
|
||||
writeCodexSession(codexDir, repoDir, 20000);
|
||||
|
||||
// Run discovery with CODEX_SESSIONS_DIR override
|
||||
const result = spawnSync(
|
||||
"bun",
|
||||
["run", scriptPath, "--since", "1h", "--format", "json"],
|
||||
{
|
||||
encoding: "utf-8",
|
||||
timeout: 30000,
|
||||
env: {
|
||||
...process.env,
|
||||
CODEX_SESSIONS_DIR: join(tmpDir, "codex-home", "sessions"),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const json = JSON.parse(result.stdout);
|
||||
expect(json.tools.codex.total_sessions).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("4KB buffer truncates session_meta, 128KB buffer parses it", () => {
|
||||
const padding = "x".repeat(20000);
|
||||
const sessionMeta = JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
type: "session_meta",
|
||||
payload: {
|
||||
id: "test-id",
|
||||
timestamp: new Date().toISOString(),
|
||||
cwd: "/tmp/test-repo",
|
||||
originator: "codex_exec",
|
||||
cli_version: "0.118.0",
|
||||
source: "exec",
|
||||
model_provider: "openai",
|
||||
base_instructions: { text: padding },
|
||||
},
|
||||
});
|
||||
|
||||
expect(sessionMeta.length).toBeGreaterThan(4096);
|
||||
|
||||
const filePath = join(codexDir, "test.jsonl");
|
||||
writeFileSync(filePath, sessionMeta + "\n");
|
||||
|
||||
// 4KB buffer: JSON.parse fails (the old bug)
|
||||
const { openSync, readSync, closeSync } = require("fs");
|
||||
const fd4k = openSync(filePath, "r");
|
||||
const buf4k = Buffer.alloc(4096);
|
||||
readSync(fd4k, buf4k, 0, 4096, 0);
|
||||
closeSync(fd4k);
|
||||
expect(() =>
|
||||
JSON.parse(buf4k.toString("utf-8").split("\n")[0])
|
||||
).toThrow();
|
||||
|
||||
// 128KB buffer: JSON.parse succeeds (the fix)
|
||||
const fd128k = openSync(filePath, "r");
|
||||
const buf128k = Buffer.alloc(131072);
|
||||
const bytesRead = readSync(fd128k, buf128k, 0, 131072, 0);
|
||||
closeSync(fd128k);
|
||||
const firstLine = buf128k.toString("utf-8", 0, bytesRead).split("\n")[0];
|
||||
const meta = JSON.parse(firstLine);
|
||||
expect(meta.type).toBe("session_meta");
|
||||
expect(meta.payload.cwd).toBe("/tmp/test-repo");
|
||||
});
|
||||
|
||||
test("regression: session_meta beyond 128KB still needs streaming parse", () => {
|
||||
// This test documents the current limitation: 128KB buffer is a heuristic.
|
||||
// If Codex ever embeds >128KB in session_meta, this test will fail,
|
||||
// signaling that the buffer needs to increase or be replaced with streaming.
|
||||
const padding = "x".repeat(140000); // ~140KB payload
|
||||
const sessionMeta = JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
type: "session_meta",
|
||||
payload: {
|
||||
id: "test-large",
|
||||
timestamp: new Date().toISOString(),
|
||||
cwd: "/tmp/large-test",
|
||||
originator: "codex_exec",
|
||||
cli_version: "0.200.0",
|
||||
source: "exec",
|
||||
model_provider: "openai",
|
||||
base_instructions: { text: padding },
|
||||
},
|
||||
});
|
||||
|
||||
expect(sessionMeta.length).toBeGreaterThan(131072);
|
||||
|
||||
const filePath = join(codexDir, "large-test.jsonl");
|
||||
writeFileSync(filePath, sessionMeta + "\n");
|
||||
|
||||
// 128KB buffer: JSON.parse FAILS for >128KB lines (current limitation)
|
||||
const { openSync, readSync, closeSync } = require("fs");
|
||||
const fd = openSync(filePath, "r");
|
||||
const buf = Buffer.alloc(131072);
|
||||
readSync(fd, buf, 0, 131072, 0);
|
||||
closeSync(fd);
|
||||
expect(() =>
|
||||
JSON.parse(buf.toString("utf-8").split("\n")[0])
|
||||
).toThrow();
|
||||
// When this test starts passing (e.g., after implementing streaming parse),
|
||||
// update it to verify correct parsing instead of documenting the limitation.
|
||||
});
|
||||
});
|
||||
|
||||
describe("discovery output structure", () => {
|
||||
test("repos have required fields", () => {
|
||||
const result = spawnSync(
|
||||
|
||||
@@ -484,9 +484,13 @@ describe('host config correctness', () => {
|
||||
expect(openclaw.adapter).toContain('openclaw-adapter');
|
||||
});
|
||||
|
||||
test('openclaw has staticFiles for SOUL.md', () => {
|
||||
expect(openclaw.staticFiles).toBeDefined();
|
||||
expect(openclaw.staticFiles!['SOUL.md']).toBeDefined();
|
||||
test('openclaw has no staticFiles (SOUL.md removed)', () => {
|
||||
expect(openclaw.staticFiles).toBeUndefined();
|
||||
});
|
||||
|
||||
test('openclaw includeSkills is empty (native skills replaced generated ones)', () => {
|
||||
expect(openclaw.generation.includeSkills).toBeDefined();
|
||||
expect(openclaw.generation.includeSkills!.length).toBe(0);
|
||||
});
|
||||
|
||||
test('every host has coAuthorTrailer or undefined', () => {
|
||||
|
||||
@@ -1522,6 +1522,26 @@ describe('Test failure triage in ship skill', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('no compiled binaries in git', () => {
|
||||
test('git tracks no Mach-O or ELF binaries', () => {
|
||||
const result = require('child_process').execSync(
|
||||
'git ls-files -z | xargs -0 file --mime-type 2>/dev/null | grep -E "application/(x-mach-binary|x-executable|x-pie-executable|x-sharedlib)" || true',
|
||||
{ cwd: ROOT, encoding: 'utf-8' }
|
||||
).trim();
|
||||
const files = result ? result.split('\n').map((l: string) => l.split(':')[0].trim()) : [];
|
||||
expect(files).toEqual([]);
|
||||
});
|
||||
|
||||
test('git tracks no files larger than 2MB', () => {
|
||||
const result = require('child_process').execSync(
|
||||
'git ls-files -z | xargs -0 -I{} sh -c \'size=$(wc -c < "{}" 2>/dev/null | tr -d " "); [ "$size" -gt 2097152 ] 2>/dev/null && echo "{}:${size}"\' || true',
|
||||
{ cwd: ROOT, encoding: 'utf-8' }
|
||||
).trim();
|
||||
const files = result ? result.split('\n').filter(Boolean) : [];
|
||||
expect(files).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sidebar agent (#584)', () => {
|
||||
// #584 — Sidebar Write: sidebar-agent.ts allowedTools includes Write
|
||||
test('sidebar-agent.ts allowedTools includes Write', () => {
|
||||
|
||||
Reference in New Issue
Block a user