mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* wip: gstack-harness registrar (GSTA-135) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * wip: harness registration in setup, uninstall, tests Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * wip: harness hosts experimental, troubleshooting codes, kit Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: agent harness registration (GSTA-135) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: harness hosts run in place from the checkout in --status Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: bun-free ledger reader Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: CHANGELOG for harness registration (Unreleased) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(harness): keep edits beside themselves, never follow symlinks, cut only gstack's own context block Review blockers on #3526: uninstall deleted the edit backups under the state root; unregister followed a symlinked folder below the destination; a lone or edited context marker made the cut eat user lines; a re-register adding --context left an unrecorded block. Also: ledger written before the files, --refresh-registered no longer re-runs harness hosts through setup (gstack-harness refresh keeps their mode), bash goes through runBashSync, and setup tests follow openclaw/hermes becoming installs. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(harness): a ledger with no destination row touches nothing (no hang) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: harness registration in README, install and hosts (document-release audit) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
98 lines
4.5 KiB
TypeScript
98 lines
4.5 KiB
TypeScript
/**
|
|
* 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.
|
|
});
|
|
|
|
test('setup never copies the digest onto a user AGENTS.md (print-path only)', () => {
|
|
const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8');
|
|
// OpenClaw and Hermes register skills through gstack-harness now (GSTA-135),
|
|
// so no setup arm prints the digest; a $(pwd)-relative digest path would
|
|
// name a nonexistent file whenever setup runs from outside the repo root.
|
|
expect(setup).not.toMatch(/\$\(pwd\)\/agents-digest/);
|
|
// …and no line may write to an AGENTS.md destination. Covers direct
|
|
// write verbs (cp/ln/mv/tee/install/rsync/dd/truncate), > and >>
|
|
// redirects, and the laundered form (dest="$proj/AGENTS.md"; … > "$dest")
|
|
// by flagging any variable assigned from an AGENTS.md path. echo/printf
|
|
// lines that merely PRINT the path stay legal.
|
|
const writers = setup
|
|
.split('\n')
|
|
.filter((l) => !/^\s*#/.test(l))
|
|
.filter((l) =>
|
|
/(^|\s|\|)(cp|ln|mv|tee|install|rsync|dd|truncate)\s[^#]*AGENTS\.md/.test(l)
|
|
|| />{1,2}\s*"?[^"\s]*AGENTS\.md/.test(l)
|
|
|| /^\s*(?:(?:local|export|declare|readonly|typeset)\s+)?\w+=[^#]*\/AGENTS\.md/.test(l));
|
|
expect(writers).toEqual([]);
|
|
});
|
|
|
|
test('the digest reuse-ladder text stays in lockstep with the preamble resolver', () => {
|
|
// The ladder and root-cause rules are hand-rendered into the digest (it
|
|
// ships to hosts that never load the preamble). The freshness test above
|
|
// pins digest-vs-generator; this pins generator-vs-resolver so an edit to
|
|
// scripts/resolvers/preamble/generate-search-before-building.ts fails CI
|
|
// instead of silently shipping a stale digest.
|
|
const { content } = generateAgentsDigest();
|
|
const resolver = fs.readFileSync(
|
|
path.join(ROOT, 'scripts', 'resolvers', 'preamble', 'generate-search-before-building.ts'),
|
|
'utf-8',
|
|
);
|
|
for (const shared of [
|
|
'stop at the first rung that holds',
|
|
'Then build the complete version of what remains',
|
|
'one guard in the shared function beats a guard in every caller',
|
|
]) {
|
|
expect(content).toContain(shared);
|
|
expect(resolver).toContain(shared);
|
|
}
|
|
});
|
|
});
|