Merge oct7-small: context-restore, browse cookbook, credits (B1, B6, B7)

This commit is contained in:
garrytan committed 2026-10-07 19:54:08 +00:00
commit 94eae93a3a
20 files changed
+488 -66

No files matched your search

+26
View File
@@ -71,6 +71,18 @@ describe('Aside driver contract ({{ASIDE_SETUP}})', () => {
expect(section).toContain('listBrowserTabs()` output is private user data');
});
test('signed-in tabs (#3063): offer only target-origin tabs, attach after one confirmation, no second sign-in loop', () => {
const rules = setup.slice(setup.indexOf('### Rules for driving a real browser'));
const rule = (n: number) => rules.match(new RegExp(`\\n${n}\\. \\*\\*[\\s\\S]*?(?=\\n\\d+\\. \\*\\*|\\n\\n)`))![0];
expect(rule(1)).toContain("Before the first `openTab`, offer that list's tabs on the target origin (title and origin only)");
expect(rule(1)).toContain('attach only after the user confirms one');
expect(rule(4)).toContain('a second wall means the session is tab- or URL-bound: offer their tab (rule 1), never another sign-in');
expect(rule(4)).not.toContain('the browser\'s cookies now apply');
// Rule 6 no longer suggests an early `return`; rule 7 names the fast-[ok abort.
expect(rule(6)).not.toContain('`return`');
expect(rule(7)).toContain('a fast `[ok` without it is an abort');
});
test('consent boundary: look freely, act on non-local targets only after one AskUserQuestion', () => {
expect(section).toContain('Invocation is consent to LOOK, not to ACT');
expect(section).toContain(ASIDE_LOCAL_HOST_RULE);
@@ -100,6 +112,20 @@ describe('Aside driver contract ({{ASIDE_SETUP}})', () => {
expect(section).toContain('use the Read tool on the copied file');
});
test('cookbook names the silent failure modes of aside repl scripts (#3064)', () => {
expect(cookbook).toContain('`evaluate` returns only JSON-serializable values');
expect(cookbook).toContain('End such calls with `; return true` or return `JSON.stringify(...)`');
expect(cookbook).toContain('No top-level `return`');
expect(cookbook).toContain('Write abort paths as `if`/`else`');
expect(cookbook).toContain('`pg.locator(sel).press("Enter")`');
expect(cookbook).toContain('An empty DOM read after an action says something about the selector, not the app');
expect(cookbook).toContain('read the state before clicking');
// The attach recipe keeps the tab list private and never closes the user's tab.
expect(cookbook).toContain('print only those tabs\' title and origin, never the rest');
expect(cookbook).toContain('never `closeTab` it');
expect(setup).not.toContain('Silent failures');
});
test('cookbook uses only the verified Aside APIs', () => {
expect(section).toContain('Page.addScriptToEvaluateOnNewDocument');
expect(section).toContain('Emulation.setDeviceMetricsOverride');
+159 -2
View File
@@ -47,6 +47,7 @@ function env(extra: Record<string, string> = {}): Record<string, string> {
delete out.GSTACK_STATE_ROOT;
delete out.GSTACK_STATE_DIR;
delete out.CURRENT_BRANCH;
delete out.CLAUDE_PROJECT_DIR;
return { ...out, HOME: tmp, GSTACK_HOME: home, ...extra };
}
@@ -66,12 +67,12 @@ function repo(name: string, origin?: string): string {
}
/** The context-save flow: path block, the agent's Write, then the stamp block. */
function save(cwd: string, title: string, timestamp: string, branch = 'main'): string {
function save(cwd: string, title: string, timestamp: string, branch = 'main', extra: Record<string, string> = {}): string {
const paths = sh(SAVE_PATH_BLOCK, cwd, { TITLE_RAW: title, TEST_TIMESTAMP: timestamp });
expect(paths.status).toBe(0);
const file = paths.stdout.match(/^FILE=(.+)$/m)![1];
fs.writeFileSync(file, `---\nstatus: in-progress\nbranch: ${branch}\ntimestamp: ${timestamp}\n---\n\n## Working on: ${title}\n`);
const stamp = sh(STAMP_BLOCK.replace('FILE="<the FILE path printed above>"', `FILE=${JSON.stringify(file)}`), cwd);
const stamp = sh(STAMP_BLOCK.replace('FILE="<the FILE path printed above>"', `FILE=${JSON.stringify(file)}`), cwd, extra);
expect(stamp.stdout).toContain('STAMPED');
return file;
}
@@ -279,3 +280,159 @@ describe('gstack-slug --adopt-legacy', () => {
expect(r.stdout).toContain("Nothing to adopt: this project's slug (acme-app) did not change.");
});
});
describe('context-restore offers newer checkpoints it used to skip (#3065)', () => {
function git(cwd: string, ...args: string[]) {
const r = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: 30_000 });
if (r.status !== 0) throw new Error(`git ${args.join(' ')}: ${r.stderr}`);
}
/** A main tree with one commit (fixture-local identity) so worktrees can be added. */
function mainTree(name: string, origin: string): string {
const dir = repo(name, origin);
git(dir, 'config', 'user.name', 'Fixture');
git(dir, 'config', 'user.email', 'fixture@example.com');
git(dir, 'commit', '-q', '--allow-empty', '-m', 'init');
return dir;
}
function worktree(main: string, at: string, branch: string): string {
git(main, 'worktree', 'add', '-q', '-b', branch, at);
return fs.realpathSync(at);
}
const pointers = (bucket: string) => {
const dir = path.join(home, 'projects', bucket, 'checkpoint-pointers');
return fs.existsSync(dir) ? fs.readdirSync(dir).map((f) => path.join(dir, f)) : [];
};
describe('case 1: saved in a nested repo, restored in its non-git parent', () => {
function layout() {
const mono = path.join(tmp, 'mono');
fs.mkdirSync(mono);
const pkg = repo('mono/pkg', 'git@gitlab.example.com:grp/sub/repo.git');
const stale = save(mono, 'old mono notes', '20260920-100000');
return { mono, pkg, stale };
}
test('with $CLAUDE_PROJECT_DIR set, the save leaves a pointer and restore offers the newer checkpoint', () => {
const { mono, pkg, stale } = layout();
const fresh = save(pkg, 'pkg feature', '20261001-100000', 'main', { CLAUDE_PROJECT_DIR: mono });
expect(path.basename(path.dirname(path.dirname(fresh)))).toMatch(/^sub-repo-[0-9a-f]{16}$/);
const [ptr] = pointers('mono');
expect(ptr).toBeDefined();
expect(JSON.parse(fs.readFileSync(ptr, 'utf-8'))).toEqual({
checkpoint: fresh,
bucket: path.basename(path.dirname(path.dirname(fresh))),
project_root: pkg,
});
const r = restore(mono);
expect(r.latest).toBe(stale);
expect(r.lines).toContain(`POINTER ${fresh}`);
});
test('without $CLAUDE_PROJECT_DIR no pointer is written, and the below-cwd scan still finds it', () => {
const { mono, pkg, stale } = layout();
const fresh = save(pkg, 'pkg feature', '20261001-100000');
expect(pointers('mono')).toEqual([]);
const r = restore(mono);
expect(r.latest).toBe(stale);
expect(r.lines).toContain(`BELOW_CWD ${fresh}`);
expect(r.out).not.toContain('POINTER');
});
test('a save from the starting directory itself writes no pointer', () => {
const { pkg } = layout();
save(pkg, 'pkg feature', '20261001-100000', 'main', { CLAUDE_PROJECT_DIR: pkg });
expect(fs.existsSync(path.join(home, 'projects'))).toBe(true);
for (const bucket of fs.readdirSync(path.join(home, 'projects'))) expect(pointers(bucket)).toEqual([]);
});
test('checkpoints not below the cwd are not offered, and the scan reads only the 200 newest', () => {
const { mono, pkg } = layout();
const sibling = repo('elsewhere', 'git@github.com:acme/elsewhere.git');
const outside = save(sibling, 'sibling work', '20261002-100000');
const below = save(pkg, 'pkg feature', '20261001-100000');
expect(restore(mono).out).not.toContain(outside);
expect(restore(mono).lines).toContain(`BELOW_CWD ${below}`);
// 200 newer checkpoints of another project push the below-cwd one out of the read window.
const dir = path.dirname(outside);
for (let i = 0; i < 200; i++) {
fs.writeFileSync(path.join(dir, `20261003-${String(100000 + i)}-n${i}.md`), `---\nbranch: main\nproject_root: ${sibling}\n---\n`);
}
expect(restore(mono).out).not.toContain('BELOW_CWD');
});
test('from $HOME, each project below shows only its newest checkpoint', () => {
const { pkg } = layout();
const older = save(pkg, 'first pass', '20261001-100000');
const newer = save(pkg, 'second pass', '20261002-100000');
const r = sh(RESTORE_BLOCK, tmp, { CURRENT_BRANCH: 'main' });
expect(r.stdout).toContain(`BELOW_CWD ${newer}`);
expect(r.stdout).not.toContain(older);
});
test('inside a git repo the below-cwd scan does not run', () => {
const outer = repo('outer');
const inner = repo('outer/inner', 'git@github.com:acme/inner.git');
const file = save(inner, 'inner work', '20261001-100000');
expect(restore(outer).out).not.toContain(file);
// A pointer from a session that started in the outer repo still surfaces it.
save(inner, 'inner work 2', '20261002-100000', 'main', { CLAUDE_PROJECT_DIR: outer });
expect(restore(outer).out).toMatch(/^POINTER .*20261002-100000-inner-work-2\.md$/m);
});
});
describe('case 2: another branch of the same repository holds a newer checkpoint for the task', () => {
test('a ticket worktree below the main tree: restore shows the newer continuation, branch order unchanged', () => {
const main = mainTree('app', 'git@github.com:acme/app.git');
const plan = save(main, 'Login plan', '20261001-100000', 'master');
const wt = worktree(main, path.join(main, '.worktrees', 't12'), 'feature/12-login');
const impl = save(wt, 'Implement login', '20261003-100000', 'feature/12-login');
expect(fs.readFileSync(impl, 'utf-8')).toContain(`\nworktree: ${wt}\n`);
expect(fs.readFileSync(plan, 'utf-8')).not.toContain('worktree:');
const r = restore(main, 'master');
expect(r.latest).toBe(plan);
expect(r.lines).toContain(`NEWER_TASK ${impl}`);
});
test('same normalized title, or the same ticket token, on a worktree outside the main tree', () => {
const main = mainTree('app', 'git@github.com:acme/app.git');
const plan = save(main, 'Auth refactor', '20261001-100000', 'master');
const wt = worktree(main, path.join(tmp, 'app-auth'), 'auth-work');
const cont = save(wt, '2026-10-03 auth-refactor!', '20261003-100000', 'auth-work');
expect(restore(main, 'master').lines).toContain(`NEWER_TASK ${cont}`);
fs.rmSync(cont);
fs.rmSync(plan);
save(main, 'PROJ-42 wip', '20261001-110000', 'master');
const ticket = save(wt, 'wip', '20261003-110000', 'feature/PROJ-42-x');
expect(restore(main, 'master').lines).toContain(`NEWER_TASK ${ticket}`);
});
test('negative controls: generic titles alone, older saves, unrelated sibling worktrees', () => {
const main = mainTree('app', 'git@github.com:acme/app.git');
const wtA = worktree(main, path.join(main, '.worktrees', 'a'), 'feat-a');
const wtB = worktree(main, path.join(main, '.worktrees', 'b'), 'feat-b');
const outside = worktree(main, path.join(tmp, 'app-x'), 'feat-x');
save(main, 'wip', '20261001-100000', 'master');
save(outside, 'WIP', '20261003-100000', 'feat-x');
expect(restore(main, 'master').out).not.toContain('NEWER_TASK');
// An older matching checkpoint is not a continuation.
save(outside, 'Payments', '20260901-100000', 'feat-x');
save(main, 'payments', '20261002-100000', 'master');
expect(restore(main, 'master').out).not.toContain('NEWER_TASK');
// A sibling worktree's own save is never displaced by another worktree's newer one.
const own = save(wtA, 'Search box', '20261004-100000', 'feat-a');
save(wtB, 'Billing export', '20261005-100000', 'feat-b');
const r = restore(wtA, 'feat-a');
expect(r.latest).toBe(own);
expect(r.out).not.toContain('NEWER_TASK');
});
});
});
+4 -4
View File
@@ -411,7 +411,7 @@ do not launch the downstream skill or open a browser.`,
// eureka log, the office-hours lookup and the taste-profile read; measured
// 1.0834 (2026-09-30). + named design-direction defaults to avoid in the native
// subagent prompt (s02 H4a); measured 1.0911 (2026-10-02).
maxSizeRatio: 1.144, // + v1.91.19.0 wave: design --version probe (B4), taste-profile load (F3), verdict-form outside voice (INV-1), plus v1.91.17.0's round accounting and board/approval blocks (#3022) merged in; measured 100,446 / 90,375 = 1.1114 (2026-10-03). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 103,384 / 90,375 = 1.1439 (2026-10-06).
maxSizeRatio: 1.147, // + v1.91.19.0 wave: design --version probe (B4), taste-profile load (F3), verdict-form outside voice (INV-1), plus v1.91.17.0's round accounting and board/approval blocks (#3022) merged in; measured 100,446 / 90,375 = 1.1114 (2026-10-03). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 103,384 / 90,375 = 1.1439 (2026-10-06). + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 103,576 / 90,375 = 1.1461 (2026-10-07).
},
cso: {
skill: 'cso',
@@ -537,7 +537,7 @@ do not launch the downstream skill or open a browser.`,
},
behavioral: 'prompt',
maxSkeletonBytes: 74_500, // + Aside browser contract for Step 7 canary ({{ASIDE_SETUP}}); measured 73_523
maxSizeRatio: 1.100, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077. + both untrusted-content marker formats in the browser fallback (W7f) + the readiness gate's project test command and live eval-store paths (s06 H6/H7); measured 1.1076 (2026-10-02). Re-measured 1.0943 (2026-10-03); cap = measured + 0.005 headroom.
maxSizeRatio: 1.102, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077. + both untrusted-content marker formats in the browser fallback (W7f) + the readiness gate's project test command and live eval-store paths (s06 H6/H7); measured 1.1076 (2026-10-02). Re-measured 1.0943 (2026-10-03); cap = measured + 0.005 headroom. + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 115,226 / 104,578 = 1.1018 (2026-10-07).
minUnionBytes: 91_000, // Phase 4 wave 1; estimated union ~94.9KB
mustContain: ['readiness', 'merge', 'canary', 'revert', 'staging'],
},
@@ -668,7 +668,7 @@ do not launch the downstream skill or open a browser.`,
},
behavioral: 'prompt',
maxSkeletonBytes: 63_500, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} (replaces the browse setup block); measured 61_253
maxSizeRatio: 1.125, // + v1.81 Aside contract + gstack-browser fallback block (1.080 on v1.91.7.0) + the shared test value bar at 8a.5 ({{TEST_VALUE_BAR:qa}}); measured 1.094 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.101 (2026-09-30) + v1.91.12.0 merge of #2999 (await scope/method Reads, capture --after checkpoints, browser-only empty evidence list) with #3002; measured 1.1028 (2026-10-01). + both untrusted-content marker formats in the browser fallback (W7f); measured 1.1035 (2026-10-02). Re-measured 1.1038 after the W8 probe-loop renames (2026-10-03); the cap already matches. + the annotate-every-capture and superseded rule before materialize (a ship-exploratory-late-input run published evidence that omitted older-snapshot captures); measured 1.1064 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 93,282 / 84,049 = 1.1099 (2026-10-04). + reliability follow-ups C2 (judge-named gaps after trimming the added wording: feasible 180 s Browser Quick, PROBE_DIR's source line, stop goes to the §4 report, written JSON keeps full card values; lane S's {user}/timing fields); measured 93,311 / 84,049 = 1.11020 (2026-10-04). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 94,486 / 84,049 = 1.1242 (2026-10-06).
maxSizeRatio: 1.127, // + v1.81 Aside contract + gstack-browser fallback block (1.080 on v1.91.7.0) + the shared test value bar at 8a.5 ({{TEST_VALUE_BAR:qa}}); measured 1.094 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.101 (2026-09-30) + v1.91.12.0 merge of #2999 (await scope/method Reads, capture --after checkpoints, browser-only empty evidence list) with #3002; measured 1.1028 (2026-10-01). + both untrusted-content marker formats in the browser fallback (W7f); measured 1.1035 (2026-10-02). Re-measured 1.1038 after the W8 probe-loop renames (2026-10-03); the cap already matches. + the annotate-every-capture and superseded rule before materialize (a ship-exploratory-late-input run published evidence that omitted older-snapshot captures); measured 1.1064 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 93,282 / 84,049 = 1.1099 (2026-10-04). + reliability follow-ups C2 (judge-named gaps after trimming the added wording: feasible 180 s Browser Quick, PROBE_DIR's source line, stop goes to the §4 report, written JSON keeps full card values; lane S's {user}/timing fields); measured 93,311 / 84,049 = 1.11020 (2026-10-04). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 94,486 / 84,049 = 1.1242 (2026-10-06). + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 94,681 / 84,049 = 1.1265 (2026-10-07).
minUnionBytes: 69_500, // measured union 70,385
// 'aside repl' pins the Aside contract; '$B goto' pins the fallback block in the always-loaded skeleton.
mustContain: ['bug', 'aside repl', '$B goto', 'fix', 'Health Score Rubric', 'regression'],
@@ -705,7 +705,7 @@ do not launch the downstream skill or open a browser.`,
gateAfterStop: undefined,
},
behavioral: 'prompt',
maxSkeletonBytes: 32_600, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} skeleton + the STOP-Read pointer; measured 29_986 + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); skeleton measured 32,532 (2026-10-06).
maxSkeletonBytes: 34_100, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} skeleton + the STOP-Read pointer; measured 29_986 + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); skeleton measured 32,532 (2026-10-06). + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort), and the cookbook's silent-failure list and signed-in-tab recipe (the rule details live here, not in every browsing skill); skeleton measured 34,047 (2026-10-07).
minUnionBytes: 36_000, // skeleton + command-list section (14,240); measured union ~38.5 KB
// 'BEGIN/END UNTRUSTED EXTERNAL' pins the untrusted-content warning in the
// command reference; 'aside repl' pins the Aside contract in the skeleton.