fix(state-root): gbrain context_queries globs use {gstack_state_root}/

bin/gstack-brain-context-load.ts resolves the {gstack_state_root}/ prefix
through gstack-paths (configuredStateGlob); the legacy ~ form only matched the
default root. The nine projects/analytics manifest globs in design-consultation,
design-shotgun, investigate, office-hours, plan-ceo-review and retro now use the
prefix, as does office-hours' builder-profile developer-profile.json glob
(plan-tune writes it under $GSTACK_STATE_ROOT).

Allowlist: drops the nine MANIFEST_GLOB rows and the now-unused MANIFEST_GLOB
and PENDING reasons; the header lists the remaining row kinds.
Tests: new brain-context-load case loads the shipped investigate and
office-hours queries against a configured root and proves a file only under
~/.gstack is not read.
This commit is contained in:
garrytan committed 2026-10-03 02:21:06 +00:00
1 parent 3215981a2e
commit 44e9c1ed3a
8 files changed
+59 -24

No files matched your search

+1 -1
View File
@@ -33,7 +33,7 @@ gbrain:
render_as: "## Existing DESIGN.md (if any)"
- id: prior-design-decisions
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"
glob: "{gstack_state_root}/projects/{repo_slug}/*-design-*.md"
sort: mtime_desc
limit: 3
render_as: "## Prior design decisions for this project"
+1 -1
View File
@@ -36,7 +36,7 @@ gbrain:
render_as: "## DESIGN.md (project design system)"
- id: recent-design-docs
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"
glob: "{gstack_state_root}/projects/{repo_slug}/*-design-*.md"
sort: mtime_desc
limit: 3
render_as: "## Recent design docs"
+2 -2
View File
@@ -51,12 +51,12 @@ gbrain:
render_as: "## Prior investigations in this repo"
- id: project-learnings
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/learnings.jsonl"
glob: "{gstack_state_root}/projects/{repo_slug}/learnings.jsonl"
tail: 10
render_as: "## Recent learnings (patterns + pitfalls)"
- id: recent-eureka
kind: filesystem
glob: "~/.gstack/analytics/eureka.jsonl"
glob: "{gstack_state_root}/analytics/eureka.jsonl"
tail: 5
render_as: "## Recent eureka moments (cross-project)"
---
+3 -3
View File
@@ -41,17 +41,17 @@ gbrain:
render_as: "## Prior office-hours sessions in this repo"
- id: builder-profile
kind: filesystem
glob: "~/.gstack/developer-profile.json"
glob: "{gstack_state_root}/developer-profile.json"
render_as: "## Your builder profile snapshot"
- id: design-doc-history
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"
glob: "{gstack_state_root}/projects/{repo_slug}/*-design-*.md"
sort: mtime_desc
limit: 3
render_as: "## Recent design docs for this project"
- id: prior-eureka
kind: filesystem
glob: "~/.gstack/analytics/eureka.jsonl"
glob: "{gstack_state_root}/analytics/eureka.jsonl"
tail: 5
render_as: "## Recent eureka moments"
---
+1 -1
View File
@@ -36,7 +36,7 @@ gbrain:
render_as: "## Prior CEO plans for this project"
- id: recent-design-docs
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"
glob: "{gstack_state_root}/projects/{repo_slug}/*-design-*.md"
sort: mtime_desc
limit: 3
render_as: "## Recent design docs for this project"
+2 -2
View File
@@ -30,12 +30,12 @@ gbrain:
render_as: "## Prior retros for this project"
- id: recent-timeline
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/timeline.jsonl"
glob: "{gstack_state_root}/projects/{repo_slug}/timeline.jsonl"
tail: 30
render_as: "## Recent timeline events"
- id: recent-learnings
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/learnings.jsonl"
glob: "{gstack_state_root}/projects/{repo_slug}/learnings.jsonl"
tail: 10
render_as: "## Recent learnings"
---
+45
View File
@@ -606,6 +606,51 @@ gbrain:
}
});
describe("gstack-brain-context-load — shipped state-root manifest files read the configured root", () => {
const cases = [
{ skill: "investigate", id: "project-learnings", file: ["projects", "test-repo", "learnings.jsonl"] },
{ skill: "investigate", id: "recent-eureka", file: ["analytics", "eureka.jsonl"] },
{ skill: "office-hours", id: "design-doc-history", file: ["projects", "test-repo", "alice-main-design-20260914.md"] },
];
for (const c of cases) it(`${c.skill}:${c.id} reads only the configured root`, () => {
const dir = mkdtempSync(join(tmpdir(), "gstack-state-manifest-"));
const home = join(dir, "home");
const configured = join(dir, "configured");
try {
const target = join(configured, ...c.file);
mkdirSync(join(target, ".."), { recursive: true });
writeFileSync(target, "{}\n");
mkdirSync(home, { recursive: true });
const query = parseSkillManifest(join(import.meta.dir, "..", c.skill, "SKILL.md.tmpl"))!
.context_queries.find(item => item.id === c.id)!;
expect(query.glob!.startsWith("{gstack_state_root}/")).toBe(true);
const skillFile = join(dir, "SKILL.md");
writeFileSync(skillFile, `---
name: state-manifest-fixture
gbrain:
schema: 1
context_queries:
- id: ${query.id}
kind: filesystem
glob: "${query.glob}"
render_as: "${query.render_as}"
---
`);
const env = { HOME: home, USERPROFILE: home, GSTACK_HOME: configured, CLAUDE_PLUGIN_DATA: "", CLAUDE_PLUGIN_ROOT: "" };
const r = runScript(["--skill-file", skillFile, "--repo", "test-repo", "--explain"], env);
expect(r.exitCode).toBe(0);
expect(r.stderr).toContain(`OK ${c.id}`);
expect(r.stdout).toContain(c.file[c.file.length - 1]!);
rmSync(target);
mkdirSync(join(home, ".gstack", ...c.file.slice(0, -1)), { recursive: true });
writeFileSync(join(home, ".gstack", ...c.file), "{}\n");
const legacyOnly = runScript(["--skill-file", skillFile, "--repo", "test-repo", "--explain"], env);
expect(legacyOnly.exitCode).toBe(0);
expect(legacyOnly.stderr).toContain(`SKIP ${c.id}`);
} finally { rmSync(dir, { recursive: true, force: true }); }
});
});
describe("gstack-brain-context-load — real gbrain CLI contract (#2883)", () => {
const ROWS = [
"plans/brand-plan\\tceo-plan\\t2026-10-01\\tBrand plan",
+4 -14
View File
@@ -160,37 +160,27 @@ describe('/ship step references resolve to the step they mean', () => {
// Every literal default-root state path in templates and resolvers. Writers
// resolve $GSTACK_STATE_ROOT (docs/state-root.md); a literal ~/.gstack path
// only matches them when the state root is the default. Rows are either
// user-facing descriptions, manifest/legacy forms read literally, or
// conversions still pending (tracked in TODOS.md). New hits fail.
// only matches them when the state root is the default. Rows are user-facing
// descriptions, source comments, or readers that must match a writer outside
// the state root. Manifest globs use the `{gstack_state_root}/` prefix. New
// hits fail.
const STATE_ROOT_LITERAL = /~\/\.gstack\/(?:projects|analytics)\//g;
const RATCHET_ALLOWLIST = JSON.parse(read('test/state-root-ratchet.allowlist.json')) as Array<{ path: string; match: string; reason: string }>;
const readinessEvalReason = RATCHET_ALLOWLIST.find(entry => entry.path === 'land-and-deploy/sections/readiness-gate.md.tmpl')!.reason;
const MANIFEST_GLOB = 'gbrain context_queries manifest glob: bin/gstack-brain-context-load.ts reads the legacy `~` form literally (default state root only); `{gstack_state_root}/` is the state-root form. Conversion pending (TODOS.md: state-root literals in templates)';
const DESCRIPTION = 'user-facing description of where the data lives; no command reads or writes this literal';
const PENDING = 'agent reads or writes this literal default-root path; it diverges from $GSTACK_STATE_ROOT writers under a non-default state root. Conversion pending (TODOS.md: state-root literals in templates)';
const SOURCE_COMMENT = 'source comment, not rendered into any skill';
const STATE_ROOT_ALLOWLIST: Array<{ file: string; match: string; reason: string }> = [
{ file: 'autoplan/SKILL.md.tmpl', match: 'test plan on disk at ~/.gstack/projects/$SLUG/', reason: DESCRIPTION },
{ file: 'careful/SKILL.md.tmpl', match: '`~/.gstack/projects/<slug>/careful-patterns.txt` (per-project)', reason: DESCRIPTION },
{ file: 'design-consultation/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"', reason: MANIFEST_GLOB },
{ file: 'design-shotgun/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"', reason: MANIFEST_GLOB },
{ file: 'design-shotgun/SKILL.md.tmpl', match: 'v1 schema at `~/.gstack/projects/$SLUG/taste-profile.json`', reason: DESCRIPTION },
{ file: 'investigate/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/learnings.jsonl"', reason: MANIFEST_GLOB },
{ file: 'investigate/SKILL.md.tmpl', match: 'glob: "~/.gstack/analytics/eureka.jsonl"', reason: MANIFEST_GLOB },
{ file: 'land-and-deploy/sections/readiness-gate.md.tmpl', match: 'eval store (`~/.gstack/projects/<slug>/evals/`', reason: readinessEvalReason },
{ file: 'land-and-deploy/sections/readiness-gate.md.tmpl', match: 'EVAL_DIR=~/.gstack/projects/$SLUG/evals', reason: readinessEvalReason },
{ file: 'office-hours/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"', reason: MANIFEST_GLOB },
{ file: 'office-hours/SKILL.md.tmpl', match: 'glob: "~/.gstack/analytics/eureka.jsonl"', reason: MANIFEST_GLOB },
{ file: 'office-hours/SKILL.md.tmpl', match: "see each other's design docs in `~/.gstack/projects/`", reason: DESCRIPTION },
{ file: 'office-hours/sections/design-and-handoff.md.tmpl', match: 'The design doc at `~/.gstack/projects/` is automatically discoverable', reason: DESCRIPTION },
{ file: 'plan-ceo-review/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/*-design-*.md"', reason: MANIFEST_GLOB },
{ file: 'plan-ceo-review/sections/review-sections.md.tmpl', match: 'Keep in `~/.gstack/projects/` only (local, personal reference)', reason: DESCRIPTION },
{ file: 'plan-tune/SKILL.md.tmpl', match: 'Logs stay local (`~/.gstack/projects/<slug>/question-log.jsonl`)', reason: DESCRIPTION },
{ file: 'plan-tune/SKILL.md.tmpl', match: '`~/.gstack/projects/<slug>/question-log.jsonl` — nothing leaves your', reason: DESCRIPTION },
{ file: 'retro/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/timeline.jsonl"', reason: MANIFEST_GLOB },
{ file: 'retro/SKILL.md.tmpl', match: 'glob: "~/.gstack/projects/{repo_slug}/learnings.jsonl"', reason: MANIFEST_GLOB },
{ file: 'scripts/resolvers/learnings.ts', match: '* Learnings are stored per-project at ~/.gstack/projects/{slug}/learnings.jsonl', reason: SOURCE_COMMENT },
{ file: 'scripts/resolvers/preamble.ts', match: 'local JSONL append to ~/.gstack/analytics/ (inline, inspectable)', reason: SOURCE_COMMENT },
];