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:
Garry Tan
2026-04-05 23:32:48 -07:00
72 changed files with 2754 additions and 149 deletions
+34
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+16
View File
@@ -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 ---
+159
View File
@@ -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(
+7 -3
View File
@@ -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', () => {
+20
View File
@@ -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', () => {