From 3263fffea95f0be3c75186a5b164ef5e2f1fb7b7 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 28 Aug 2026 01:54:36 +0000 Subject: [PATCH] =?UTF-8?q?feat(hosts):=20instruction-only=20tier=20?= =?UTF-8?q?=E2=80=94=20a=202KB=20committed=20rules=20digest=20any=20agent?= =?UTF-8?q?=20host=20can=20read?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New agents-digest/gstack-AGENTS.md (1,765 bytes, hard 2,048-byte budget): gstack's ethos one-liners, the reuse ladder, and voice rules for hosts with no install arm — Zed, Amp, Jules, or any AGENTS.md-reading agent. Generated by scripts/gen-agents-digest.ts, auto-refreshed by gen:skill-docs, committed like llms.txt so setup's explainer arms can point at it before any toolchain exists. First line carries the gstack version as its own staleness nudge. Delivery is print-path + user-performed copy ONLY: setup never writes or overwrites a user's AGENTS.md (a test pins this — no cp/ln/mv/redirect into AGENTS.md anywhere in setup). openclaw and hermes explainer arms print the path; slate keeps routing to the full Claude install and gbrain ships from its own repo. HostConfig gains the optional install.instructionTier slot, declared by both instruction-tier hosts. README host table now matches what setup actually does. Inspired by dietrichgebert/ponytail's instruction-tier AGENTS.md fallback — one generated source, never per-host hand copies. Co-Authored-By: Claude Fable 5 --- README.md | 26 ++++++---- agents-digest/gstack-AGENTS.md | 35 +++++++++++++ hosts/hermes.ts | 7 +++ hosts/openclaw.ts | 7 +++ scripts/gen-agents-digest.ts | 89 ++++++++++++++++++++++++++++++++++ scripts/gen-skill-docs.ts | 11 +++++ scripts/host-config.ts | 7 +++ setup | 16 ++++++ test/agents-digest.test.ts | 75 ++++++++++++++++++++++++++++ 9 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 agents-digest/gstack-AGENTS.md create mode 100644 scripts/gen-agents-digest.ts create mode 100644 test/agents-digest.test.ts diff --git a/README.md b/README.md index 7b2b9305e..fabcd8fa0 100644 --- a/README.md +++ b/README.md @@ -111,16 +111,22 @@ cd ~/gstack && ./setup Or target a specific agent with `./setup --host `: -| Agent | Flag | Skills install to | -|-------|------|-------------------| -| OpenAI Codex CLI | `--host codex` | `${CODEX_HOME:-~/.codex}/skills/gstack-*/` | -| OpenCode | `--host opencode` | `~/.config/opencode/skills/gstack-*/` | -| Cursor | `--host cursor` | `~/.cursor/skills/gstack-*/` | -| Factory Droid | `--host factory` | `~/.factory/skills/gstack-*/` | -| Slate | `--host slate` | `~/.slate/skills/gstack-*/` | -| Kiro | `--host kiro` | `~/.kiro/skills/gstack-*/` | -| Hermes | `--host hermes` | `~/.hermes/skills/gstack-*/` | -| GBrain (mod) | `--host gbrain` | `~/.gbrain/skills/gstack-*/` | +| Agent | Flag | What you get | +|-------|------|--------------| +| OpenAI Codex CLI | `--host codex` | Full install → `${CODEX_HOME:-~/.codex}/skills/gstack-*/` | +| OpenCode | `--host opencode` | Full install → `~/.config/opencode/skills/gstack-*/` | +| Cursor | `--host cursor` | Full install → `~/.cursor/skills/gstack-*/` | +| Factory Droid | `--host factory` | Full install → `~/.factory/skills/gstack-*/` | +| Kiro | `--host kiro` | Full install → `~/.kiro/skills/gstack-*/` | +| Slate | `--host slate` | Pointer to the Claude install (Slate reads `.claude/skills` as a fallback) | +| Hermes | `--host hermes` | Methodology artifacts via `gen:skill-docs --host hermes` + the instruction-only digest below | +| GBrain (mod) | `--host gbrain` | Brain-aware skill variants, shipped from the GBrain repo | + +**Instruction-only tier (any rules-reading agent — Zed, Amp, Jules, side projects):** +copy the 2KB digest at [`agents-digest/gstack-AGENTS.md`](agents-digest/gstack-AGENTS.md) +into a location your agent reads (for example, append it to your project's `AGENTS.md`). +It carries gstack's ethos, reuse ladder, and voice rules — no install required. The +digest's first line shows its gstack version; re-copy it after upgrading. For Codex, setup reads the top-level `model` from `${CODEX_HOME:-~/.codex}/config.toml` and generates the matching behavioral diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md new file mode 100644 index 000000000..0a573b953 --- /dev/null +++ b/agents-digest/gstack-AGENTS.md @@ -0,0 +1,35 @@ +# gstack digest v1.71.0.0 — regenerate/re-copy after upgrading gstack + +Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed +for agent hosts without a full skill install. The full skills add workflows, +reviews, and evals on top of these rules. + +## Ethos + +- **Boil the Ocean** — AI makes completeness cheap, so do the complete thing: tests, edge cases, error paths. Shortcuts need an explicit, recorded decision. +- **Search Before Building** — know what exists before deciding what to build. Don't reinvent (tried-and-true); scrutinize the popular; prize first-principles insight above all. +- **User Sovereignty** — models recommend, the user decides. Cross-model agreement is signal, never permission. Ask before changing the user's stated direction. +- **Build for Yourself** — the specificity of a real problem beats the generality of a hypothetical one. + +## The reuse ladder + +Before writing new code, stop at the first rung that holds: +1. A helper, util, or pattern already in this repo. +2. The standard library. +3. A native platform feature (CSS over JS, DB constraint over app code). +4. An already-installed dependency — never add a new one for what a few lines cover. + +Then build the complete version of what remains. Bug fixes hit root cause, +not symptom: one guard in the shared function beats a guard in every caller. + +## Voice + +Direct, concrete, builder-to-builder. Name the file, function, command, and +user-visible impact. Short paragraphs; end with what to do. No filler, no +corporate tone, no AI vocabulary. + +## Full gstack + +Clone https://github.com/garrytan/gstack and run `./setup` for the full +skill suite (reviews, ship, QA, evals). This digest is generated — edit +scripts/gen-agents-digest.ts, not this file. diff --git a/hosts/hermes.ts b/hosts/hermes.ts index cec9d1748..cf140fae7 100644 --- a/hosts/hermes.ts +++ b/hosts/hermes.ts @@ -28,6 +28,13 @@ const hermes = defineHost({ // If Hermes has GBrain as a mod, brain features activate automatically. ], + // No full install arm — users can hand-copy the instruction-only digest + // (setup's explainer arm prints this path; never auto-copied). + install: { + linkingStrategy: 'symlink-generated', + instructionTier: { rulesFile: 'agents-digest/gstack-AGENTS.md' }, + }, + coAuthorTrailer: 'Co-Authored-By: Hermes Agent ', }); diff --git a/hosts/openclaw.ts b/hosts/openclaw.ts index 6e18eb300..6248e0587 100644 --- a/hosts/openclaw.ts +++ b/hosts/openclaw.ts @@ -12,6 +12,13 @@ const openclaw = defineHost({ // Suppress Claude-specific preamble sections that don't apply to OpenClaw suppressedResolvers: [...CROSS_MODEL_RESOLVERS, ...GBRAIN_RESOLVERS], + // No full install arm — users can hand-copy the instruction-only digest + // (setup's explainer arm prints this path; never auto-copied). + install: { + linkingStrategy: 'symlink-generated', + instructionTier: { rulesFile: 'agents-digest/gstack-AGENTS.md' }, + }, + coAuthorTrailer: 'Co-Authored-By: OpenClaw Agent ', }); diff --git a/scripts/gen-agents-digest.ts b/scripts/gen-agents-digest.ts new file mode 100644 index 000000000..738edcaae --- /dev/null +++ b/scripts/gen-agents-digest.ts @@ -0,0 +1,89 @@ +#!/usr/bin/env bun +/** + * Generate agents-digest/gstack-AGENTS.md — the instruction-only tier. + * + * A ~2KB behavioral digest any rules-reading agent host can load (Zed, Amp, + * Jules, Cursor side-projects, OpenClaw/Hermes methodology mode): the ETHOS + * principles as one-liners, the reuse ladder, the voice rules, and a pointer + * to the full install. It is a COMMITTED generated artifact (like llms.txt) + * so the setup explainer arms can point at it before any toolchain exists. + * + * Delivery is print-path + user-performed copy ONLY — never write or + * overwrite a user's own AGENTS.md (a gstack-AGENTS.md is auto-read by no + * host; clobbering AGENTS.md destroys user content on every re-run). + * + * One host-neutral file, deliberately: the digest contains no host-specific + * paths, so per-host copies would be byte-identical duplication (the exact + * per-host rule-text sprawl gstack's generator model exists to avoid). If a + * host ever needs rewrites, this generator gains a per-host emit then. + * + * Size budget: <= 2,048 bytes, enforced by test/agents-digest.test.ts. Every + * host that loads this pays for it in every session — trim, don't grow. + * + * Refresh: invoked from scripts/gen-skill-docs.ts after SKILL.md generation. + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..'); +export const DIGEST_RELPATH = path.join('agents-digest', 'gstack-AGENTS.md'); +export const DIGEST_BYTE_BUDGET = 2048; + +export function generateAgentsDigest(opts?: { root?: string }): { content: string; bytes: number } { + const root = opts?.root ?? ROOT; + const version = fs.readFileSync(path.join(root, 'VERSION'), 'utf-8').trim(); + + const content = `# gstack digest v${version} — regenerate/re-copy after upgrading gstack + +Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed +for agent hosts without a full skill install. The full skills add workflows, +reviews, and evals on top of these rules. + +## Ethos + +- **Boil the Ocean** — AI makes completeness cheap, so do the complete thing: tests, edge cases, error paths. Shortcuts need an explicit, recorded decision. +- **Search Before Building** — know what exists before deciding what to build. Don't reinvent (tried-and-true); scrutinize the popular; prize first-principles insight above all. +- **User Sovereignty** — models recommend, the user decides. Cross-model agreement is signal, never permission. Ask before changing the user's stated direction. +- **Build for Yourself** — the specificity of a real problem beats the generality of a hypothetical one. + +## The reuse ladder + +Before writing new code, stop at the first rung that holds: +1. A helper, util, or pattern already in this repo. +2. The standard library. +3. A native platform feature (CSS over JS, DB constraint over app code). +4. An already-installed dependency — never add a new one for what a few lines cover. + +Then build the complete version of what remains. Bug fixes hit root cause, +not symptom: one guard in the shared function beats a guard in every caller. + +## Voice + +Direct, concrete, builder-to-builder. Name the file, function, command, and +user-visible impact. Short paragraphs; end with what to do. No filler, no +corporate tone, no AI vocabulary. + +## Full gstack + +Clone https://github.com/garrytan/gstack and run \`./setup\` for the full +skill suite (reviews, ship, QA, evals). This digest is generated — edit +scripts/gen-agents-digest.ts, not this file. +`; + + return { content, bytes: Buffer.byteLength(content, 'utf-8') }; +} + +export function writeAgentsDigest(opts?: { root?: string }): { outPath: string; bytes: number } { + const root = opts?.root ?? ROOT; + const { content, bytes } = generateAgentsDigest({ root }); + const outPath = path.join(root, DIGEST_RELPATH); + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + fs.writeFileSync(outPath, content); + return { outPath, bytes }; +} + +if (import.meta.main) { + const { outPath, bytes } = writeAgentsDigest(); + console.log(`[gen-agents-digest] ${path.relative(ROOT, outPath)}: ${bytes} bytes (budget ${DIGEST_BYTE_BUDGET})`); +} diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 6028ea4ea..230d3cb05 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -1148,5 +1148,16 @@ if (!DRY_RUN) { const msg = err instanceof Error ? err.message : String(err); console.error(`[gen-llms-txt] FAILED: ${msg}`); } + // Regenerate agents-digest/gstack-AGENTS.md — the instruction-only tier + // for rules-reading hosts with no skill install. Committed artifact; + // freshness + byte budget asserted in test/agents-digest.test.ts. + try { + const { writeAgentsDigest, DIGEST_BYTE_BUDGET } = await import('./gen-agents-digest'); + const digest = writeAgentsDigest(); + console.log(`[gen-agents-digest] agents-digest/gstack-AGENTS.md: ${digest.bytes} bytes (budget ${DIGEST_BYTE_BUDGET})`); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + console.error(`[gen-agents-digest] FAILED: ${msg}`); + } })(); } diff --git a/scripts/host-config.ts b/scripts/host-config.ts index 47e13fbef..378a24e2f 100644 --- a/scripts/host-config.ts +++ b/scripts/host-config.ts @@ -89,6 +89,13 @@ export interface HostConfig { install: { /** How skills are linked into the host dir. */ linkingStrategy: 'real-dir-symlink' | 'symlink-generated'; + /** + * Instruction-only fallback tier: a committed rules digest this host's + * users can hand-copy when there is no full install arm. Delivery is + * print-path + user-performed copy ONLY — setup must never write or + * overwrite a user's own AGENTS.md. See scripts/gen-agents-digest.ts. + */ + instructionTier?: { rulesFile: string }; }; // --- Host-Specific Behavioral Config --- diff --git a/setup b/setup index 21ec6c0b0..f7b32853f 100755 --- a/setup +++ b/setup @@ -227,6 +227,14 @@ case "$HOST" in echo " 2. Or generate artifacts: bun run gen:skill-docs --host openclaw" echo " 3. See docs/OPENCLAW.md for the full architecture" echo "" + echo "Instruction-only tier (no install): copy the 2KB rules digest into a" + echo "location your agent reads (e.g. append to your project's AGENTS.md)." + echo "It carries gstack's ethos, reuse ladder, and voice rules:" + echo "" + echo " $(pwd)/agents-digest/gstack-AGENTS.md" + echo "" + echo "Re-copy it after upgrading gstack — the digest's first line shows its version." + echo "" exit 0 ;; hermes) echo "" @@ -237,6 +245,14 @@ case "$HOST" in echo " 1. Tell your Hermes agent: 'install gstack for hermes'" echo " 2. Or generate artifacts: bun run gen:skill-docs --host hermes" echo "" + echo "Instruction-only tier (no install): copy the 2KB rules digest into a" + echo "location your agent reads (e.g. append to your project's AGENTS.md)." + echo "It carries gstack's ethos, reuse ladder, and voice rules:" + echo "" + echo " $(pwd)/agents-digest/gstack-AGENTS.md" + echo "" + echo "Re-copy it after upgrading gstack — the digest's first line shows its version." + echo "" exit 0 ;; gbrain) echo "" diff --git a/test/agents-digest.test.ts b/test/agents-digest.test.ts new file mode 100644 index 000000000..ae40a56da --- /dev/null +++ b/test/agents-digest.test.ts @@ -0,0 +1,75 @@ +/** + * agents-digest/gstack-AGENTS.md — the instruction-only tier artifact. + * + * Freshness (committed file matches the generator, same pattern as + * llms-txt-shape.test.ts), a HARD byte budget (every rules-reading host + * loads the whole file every session), and the delivery-safety invariant + * (setup prints the path; it must never copy onto a user's AGENTS.md). + */ +import { describe, test, expect } from 'bun:test'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + generateAgentsDigest, + DIGEST_RELPATH, + DIGEST_BYTE_BUDGET, +} from '../scripts/gen-agents-digest'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const digestPath = path.join(ROOT, DIGEST_RELPATH); + +describe('agents-digest', () => { + test('committed digest is fresh (matches generator output)', () => { + const { content } = generateAgentsDigest({ root: ROOT }); + const committed = fs.readFileSync(digestPath, 'utf-8'); + expect(committed).toBe(content); + }); + + test(`digest stays within its ${DIGEST_BYTE_BUDGET}-byte budget`, () => { + const { bytes } = generateAgentsDigest({ root: ROOT }); + if (bytes > DIGEST_BYTE_BUDGET) { + throw new Error( + `agents-digest is ${bytes} bytes, over the ${DIGEST_BYTE_BUDGET}-byte budget ` + + `(over by ${bytes - DIGEST_BYTE_BUDGET}).\n` + + `This file is always-on context for every instruction-tier host — trim, don't grow.\n` + + `Trim protocol: cut prose from scripts/gen-agents-digest.ts (shorten explanations, ` + + `never drop a section outright), rerun 'bun run gen:skill-docs', and re-check. ` + + `Raising the budget requires the same conscious-decision treatment as the ` + + `context-budget ratchet: justify it in the PR that does it.`, + ); + } + expect(bytes).toBeLessThanOrEqual(DIGEST_BYTE_BUDGET); + }); + + test('digest carries its version header and the load-bearing sections', () => { + const version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim(); + const content = fs.readFileSync(digestPath, 'utf-8'); + expect(content.startsWith(`# gstack digest v${version}`)).toBe(true); + expect(content).toContain('re-copy after upgrading'); + expect(content).toContain('## Ethos'); + expect(content).toContain('## The reuse ladder'); + expect(content).toContain('## Voice'); + expect(content).toContain('## Full gstack'); + // The ladder must keep the completeness reconciliation clause. + expect(content).toContain('build the complete version of what remains'); + }); + + test('setup never copies the digest onto a user AGENTS.md (print-path only)', () => { + const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8'); + // The explainer arms print the digest path… + expect(setup).toContain('agents-digest/gstack-AGENTS.md'); + // …and no line may write to an AGENTS.md destination. A cp/ln/mv or a + // shell redirect into AGENTS.md would clobber user content on re-run. + const writers = setup + .split('\n') + .filter((l) => /(^|\s)(cp|ln|mv)\s[^#]*AGENTS\.md|>\s*"?[^"\s]*AGENTS\.md/.test(l)); + expect(writers).toEqual([]); + }); + + test('instruction-tier hosts declare the digest in host config', () => { + for (const host of ['openclaw', 'hermes']) { + const src = fs.readFileSync(path.join(ROOT, 'hosts', `${host}.ts`), 'utf-8'); + expect(src).toContain("instructionTier: { rulesFile: 'agents-digest/gstack-AGENTS.md' }"); + } + }); +});