From b677b29c47dd01b71d55e5f7bb219f3f6d235cd2 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 29 Aug 2026 05:18:34 +0000 Subject: [PATCH] test(digest): broaden AGENTS.md writer tripwire; pin digest-resolver ladder lockstep The print-path-only guard now catches tee/install/rsync/dd/truncate, >> appends, and laundered variable-destination writes. New test ties the digest's hand-rendered reuse-ladder text to the preamble resolver so an edit to either fails CI instead of shipping drift. Co-Authored-By: Claude Fable 5 --- test/agents-digest.test.ts | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/test/agents-digest.test.ts b/test/agents-digest.test.ts index ae40a56da..1446fbb87 100644 --- a/test/agents-digest.test.ts +++ b/test/agents-digest.test.ts @@ -58,14 +58,42 @@ describe('agents-digest', () => { 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. + // …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)(cp|ln|mv)\s[^#]*AGENTS\.md|>\s*"?[^"\s]*AGENTS\.md/.test(l)); + .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*\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); + } + }); + 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');