mirror of https://github.com/garrytan/gstack.git
fix(gbrain): keep Codex context-load repo-scoped
This commit is contained in:
parent
3bef43bc5a
commit
db7873453b
|
|
@ -175,12 +175,20 @@ function resolveSkillFile(args: CliArgs): string | null {
|
||||||
return resolve(args.skillFile);
|
return resolve(args.skillFile);
|
||||||
}
|
}
|
||||||
if (!args.skill) return null;
|
if (!args.skill) return null;
|
||||||
|
const skill = args.skill;
|
||||||
|
const codexSkill = skill.startsWith("gstack-") ? skill : `gstack-${skill}`;
|
||||||
// Look in common gstack skill locations
|
// Look in common gstack skill locations
|
||||||
const candidates = [
|
const candidates = [
|
||||||
join(HOME, ".claude", "skills", args.skill, "SKILL.md"),
|
join(process.cwd(), ".agents", "skills", codexSkill, "SKILL.md"),
|
||||||
join(HOME, ".claude", "skills", "gstack", args.skill, "SKILL.md"),
|
join(process.cwd(), ".agents", "skills", "gstack", skill, "SKILL.md"),
|
||||||
join(process.cwd(), ".claude", "skills", args.skill, "SKILL.md"),
|
join(HOME, ".codex", "skills", codexSkill, "SKILL.md"),
|
||||||
join(process.cwd(), args.skill, "SKILL.md"),
|
join(HOME, ".codex", "skills", "gstack", skill, "SKILL.md"),
|
||||||
|
join(HOME, ".agents", "skills", codexSkill, "SKILL.md"),
|
||||||
|
join(HOME, ".agents", "skills", "gstack", skill, "SKILL.md"),
|
||||||
|
join(HOME, ".claude", "skills", skill, "SKILL.md"),
|
||||||
|
join(HOME, ".claude", "skills", "gstack", skill, "SKILL.md"),
|
||||||
|
join(process.cwd(), ".claude", "skills", skill, "SKILL.md"),
|
||||||
|
join(process.cwd(), skill, "SKILL.md"),
|
||||||
];
|
];
|
||||||
for (const c of candidates) {
|
for (const c of candidates) {
|
||||||
if (existsSync(c)) return c;
|
if (existsSync(c)) return c;
|
||||||
|
|
@ -391,7 +399,7 @@ function defaultManifest(args: CliArgs): GbrainManifest {
|
||||||
{
|
{
|
||||||
id: "skill-name-events",
|
id: "skill-name-events",
|
||||||
kind: "list",
|
kind: "list",
|
||||||
filter: { type: "timeline", content_contains: "{skill_name}" },
|
filter: { type: "timeline", "tags_contains": "repo:{repo_slug}", content_contains: "{skill_name}" },
|
||||||
limit: 5,
|
limit: 5,
|
||||||
render_as: "## Recent {skill_name} events",
|
render_as: "## Recent {skill_name} events",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ import { spawnSync } from "child_process";
|
||||||
|
|
||||||
const SCRIPT = join(import.meta.dir, "..", "bin", "gstack-brain-context-load.ts");
|
const SCRIPT = join(import.meta.dir, "..", "bin", "gstack-brain-context-load.ts");
|
||||||
|
|
||||||
function runScript(args: string[], env: Record<string, string> = {}): { stdout: string; stderr: string; exitCode: number } {
|
function runScript(args: string[], env: Record<string, string> = {}, cwd: string = process.cwd()): { stdout: string; stderr: string; exitCode: number } {
|
||||||
const result = spawnSync("bun", [SCRIPT, ...args], {
|
const result = spawnSync("bun", [SCRIPT, ...args], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
env: { ...process.env, ...env },
|
env: { ...process.env, ...env },
|
||||||
|
cwd,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
stdout: result.stdout || "",
|
stdout: result.stdout || "",
|
||||||
|
|
@ -89,6 +90,118 @@ describe("gstack-brain-context-load — manifest dispatch", () => {
|
||||||
expect(r.stderr).toContain("queries=3");
|
expect(r.stderr).toContain("queries=3");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("default manifest keeps every list query scoped to the active repo", () => {
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), "gstack-bcl-"));
|
||||||
|
const binDir = join(dir, "bin");
|
||||||
|
mkdirSync(binDir);
|
||||||
|
writeFakeGbrain(binDir);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = runScript(["--skill", "nonexistent-skill-xyz", "--repo", "test-repo"], prependPath(binDir));
|
||||||
|
expect(r.exitCode).toBe(0);
|
||||||
|
const repoFilterCount = (r.stdout.match(/tags_contains=repo:test-repo/g) || []).length;
|
||||||
|
expect(repoFilterCount).toBe(3);
|
||||||
|
} finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves generated Codex skill directories before falling back to the default manifest", () => {
|
||||||
|
const home = mkdtempSync(join(tmpdir(), "gstack-home-"));
|
||||||
|
const skillDir = join(home, ".codex", "skills", "gstack-codex-only");
|
||||||
|
mkdirSync(skillDir, { recursive: true });
|
||||||
|
const note = join(home, "note.md");
|
||||||
|
writeFileSync(note, "codex manifest note\n", "utf-8");
|
||||||
|
writeFileSync(
|
||||||
|
join(skillDir, "SKILL.md"),
|
||||||
|
`---
|
||||||
|
name: codex-only
|
||||||
|
gbrain:
|
||||||
|
schema: 1
|
||||||
|
context_queries:
|
||||||
|
- id: codex-note
|
||||||
|
kind: filesystem
|
||||||
|
glob: "${note}"
|
||||||
|
render_as: "## Codex manifest note"
|
||||||
|
---
|
||||||
|
|
||||||
|
body
|
||||||
|
`,
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = runScript(["--skill", "codex-only", "--repo", "test-repo", "--explain"], { HOME: home });
|
||||||
|
expect(r.exitCode).toBe(0);
|
||||||
|
expect(r.stderr).toContain("mode=manifest");
|
||||||
|
expect(r.stdout).toContain("## Codex manifest note");
|
||||||
|
expect(r.stdout).toContain("note.md");
|
||||||
|
} finally {
|
||||||
|
rmSync(home, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers repo-local Codex skills over global Codex installs", () => {
|
||||||
|
const home = mkdtempSync(join(tmpdir(), "gstack-home-"));
|
||||||
|
const repo = mkdtempSync(join(tmpdir(), "gstack-repo-"));
|
||||||
|
const globalSkillDir = join(home, ".codex", "skills", "gstack-codex-only");
|
||||||
|
const localSkillDir = join(repo, ".agents", "skills", "gstack-codex-only");
|
||||||
|
mkdirSync(globalSkillDir, { recursive: true });
|
||||||
|
mkdirSync(localSkillDir, { recursive: true });
|
||||||
|
|
||||||
|
const globalNote = join(home, "global-note.md");
|
||||||
|
const localNote = join(repo, "local-note.md");
|
||||||
|
writeFileSync(globalNote, "global codex manifest note\n", "utf-8");
|
||||||
|
writeFileSync(localNote, "local codex manifest note\n", "utf-8");
|
||||||
|
writeFileSync(
|
||||||
|
join(globalSkillDir, "SKILL.md"),
|
||||||
|
`---
|
||||||
|
name: codex-only
|
||||||
|
gbrain:
|
||||||
|
schema: 1
|
||||||
|
context_queries:
|
||||||
|
- id: global-note
|
||||||
|
kind: filesystem
|
||||||
|
glob: "${globalNote}"
|
||||||
|
render_as: "## Global Codex manifest"
|
||||||
|
---
|
||||||
|
|
||||||
|
body
|
||||||
|
`,
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
writeFileSync(
|
||||||
|
join(localSkillDir, "SKILL.md"),
|
||||||
|
`---
|
||||||
|
name: codex-only
|
||||||
|
gbrain:
|
||||||
|
schema: 1
|
||||||
|
context_queries:
|
||||||
|
- id: local-note
|
||||||
|
kind: filesystem
|
||||||
|
glob: "${localNote}"
|
||||||
|
render_as: "## Local Codex manifest"
|
||||||
|
---
|
||||||
|
|
||||||
|
body
|
||||||
|
`,
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = runScript(["--skill", "codex-only", "--repo", "test-repo", "--explain"], { HOME: home }, repo);
|
||||||
|
expect(r.exitCode).toBe(0);
|
||||||
|
expect(r.stderr).toContain("mode=manifest");
|
||||||
|
expect(r.stdout).toContain("## Local Codex manifest");
|
||||||
|
expect(r.stdout).toContain("local-note.md");
|
||||||
|
expect(r.stdout).not.toContain("## Global Codex manifest");
|
||||||
|
expect(r.stdout).not.toContain("global-note.md");
|
||||||
|
} finally {
|
||||||
|
rmSync(home, { recursive: true, force: true });
|
||||||
|
rmSync(repo, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("uses skill manifest when --skill-file points at a valid SKILL.md", () => {
|
it("uses skill manifest when --skill-file points at a valid SKILL.md", () => {
|
||||||
const dir = mkdtempSync(join(tmpdir(), "gstack-bcl-"));
|
const dir = mkdtempSync(join(tmpdir(), "gstack-bcl-"));
|
||||||
const skillFile = join(dir, "SKILL.md");
|
const skillFile = join(dir, "SKILL.md");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue