feat(office-hours): 'never show me these again' for the founder-resources pitch (#538)

The Phase 6 resources offer (34 PG essays + Garry/YC videos) had no
permanent decline — the reporter showed memory instructions kept being
overridden on every update, so people who said no got re-pitched forever.
The offer now closes with a standing choice; opting out runs
gstack-config set founder_resources false (new key, default true, true|false
validated), the write is VERIFIED before any promise (a failed write says so
and skips this session only), and every future session skips the entire
section silently — no resources, no 'skipped as requested' mention. Config
outlives session context, so never means never. Re-enable anytime:
gstack-config set founder_resources true. The pitch stays default-ON for
everyone who never opted out.

Tests pin the key's default/persistence/validation through the real config
bin and the generated section's gate-before-content + write-verify contract.

Approved as a promo-surface change (CEO review D3.4, 2026-08-14).
Ported from time-attack/gstack (GStack 2).

Co-authored-by: Sina Matian <sina@time-attack.dev>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 13:08:20 -07:00
parent 4a34bd7d36
commit 1d29225a92
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
4 changed files with 128 additions and 0 deletions

View File

@ -134,6 +134,7 @@ lookup_default() {
redact_repo_visibility) echo "" ;; # empty → fall through to gh/glab detection
redact_prepush_hook) echo "false" ;;
pair_agent) echo "off" ;; # remote tunnel consent — fail-closed until /pair-agent asks
founder_resources) echo "true" ;; # office-hours resource pitch — #538 permanent opt-out sets false
# Brain-aware planning (v1.48 / T5+T10+T16). Defaults documented inline:
# brain_trust_policy@<endpoint-id> — unset on fresh install; setup-gbrain
# writes 'personal' for local engines,
@ -315,6 +316,10 @@ case "${1:-}" in
echo "Warning: pair_agent '$VALUE' not recognized. Valid values: on, off. Using off." >&2
VALUE="off"
fi
if [ "$KEY" = "founder_resources" ] && [ "$VALUE" != "true" ] && [ "$VALUE" != "false" ]; then
echo "Warning: founder_resources '$VALUE' not recognized. Valid values: true, false. Using true." >&2
VALUE="true"
fi
if [ "$KEY" = "plan_tune_hooks" ] && [ "$VALUE" != "prompt" ] && [ "$VALUE" != "yes" ] && [ "$VALUE" != "no" ]; then
echo "Warning: plan_tune_hooks '$VALUE' not recognized. Valid values: prompt, yes, no. Using prompt." >&2
VALUE="prompt"

View File

@ -451,9 +451,32 @@ Then proceed to Founder Resources below.
### Founder Resources (all tiers)
**Standing opt-out check (#538) — run FIRST:**
```bash
~/.claude/skills/gstack/bin/gstack-config get founder_resources 2>/dev/null || echo "true"
```
If the value is `false`, **skip this entire section silently** — no resources,
no "skipped as requested" mention. The user said never; config outlives
session context and memory instructions, so never means never. (Re-enable:
`gstack-config set founder_resources true`.)
Share 2-3 resources from the pool below. For repeat users, resources compound by matching
to accumulated session context, not just this session's category.
**After sharing, close with the standing choice** (one line, not a ceremony):
> Want these? I can open any of them — or say "never show me these again" and
> this section disappears for good.
If the user opts out (any clear phrasing of never/stop showing these): run
`~/.claude/skills/gstack/bin/gstack-config set founder_resources false`, then
VERIFY the write (`gstack-config get founder_resources` must read back
`false`) before promising anything — if the write failed, say so and skip for
this session only. On success confirm in one line with the re-enable command
and continue the handoff.
**Dedup check:** Read `RESOURCES_SHOWN` from the builder profile output above.
If `RESOURCES_SHOWN_COUNT` is 34 or more, skip this section entirely (all resources exhausted).
Otherwise, avoid selecting any URL that appears in the RESOURCES_SHOWN list.

View File

@ -340,9 +340,32 @@ Then proceed to Founder Resources below.
### Founder Resources (all tiers)
**Standing opt-out check (#538) — run FIRST:**
```bash
~/.claude/skills/gstack/bin/gstack-config get founder_resources 2>/dev/null || echo "true"
```
If the value is `false`, **skip this entire section silently** — no resources,
no "skipped as requested" mention. The user said never; config outlives
session context and memory instructions, so never means never. (Re-enable:
`gstack-config set founder_resources true`.)
Share 2-3 resources from the pool below. For repeat users, resources compound by matching
to accumulated session context, not just this session's category.
**After sharing, close with the standing choice** (one line, not a ceremony):
> Want these? I can open any of them — or say "never show me these again" and
> this section disappears for good.
If the user opts out (any clear phrasing of never/stop showing these): run
`~/.claude/skills/gstack/bin/gstack-config set founder_resources false`, then
VERIFY the write (`gstack-config get founder_resources` must read back
`false`) before promising anything — if the write failed, say so and skip for
this session only. On success confirm in one line with the re-enable command
and continue the handoff.
**Dedup check:** Read `RESOURCES_SHOWN` from the builder profile output above.
If `RESOURCES_SHOWN_COUNT` is 34 or more, skip this section entirely (all resources exhausted).
Otherwise, avoid selecting any URL that appears in the RESOURCES_SHOWN list.

View File

@ -0,0 +1,77 @@
/**
* #538: the office-hours founder-resources pitch takes no for an answer.
*
* The reporter found that memory instructions telling the agent to stop
* showing the 34-resource pool kept being overridden on every update. The
* fix is a config key `founder_resources` that session context cannot
* override: `false` skips the entire section silently, forever, until the
* user re-enables it.
*
* Pins: (1) the config key's default, persistence, and validation through
* the real bin/gstack-config subprocess; (2) the generated section gates on
* the key BEFORE any resource content; (3) the opt-out write is verified
* before the skill may promise "never again" (R6 a failed write must not
* produce a false promise).
*/
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { execFileSync } from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
const ROOT = path.resolve(import.meta.dir, "..");
const CONFIG_BIN = path.join(ROOT, "bin", "gstack-config");
const SECTION = path.join(ROOT, "office-hours", "sections", "design-and-handoff.md");
let tmpHome: string;
beforeEach(() => { tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "gstack-538-")); });
afterEach(() => { fs.rmSync(tmpHome, { recursive: true, force: true }); });
function cfg(args: string[]): string {
return execFileSync(CONFIG_BIN, args, {
encoding: "utf-8",
env: { ...process.env, GSTACK_HOME: tmpHome },
}).trim();
}
describe("founder_resources config key (#538)", () => {
test("defaults to true (pitch stays on for everyone who never opted out)", () => {
expect(cfg(["get", "founder_resources"])).toBe("true");
});
test("set false persists and reads back — the write-verify contract", () => {
cfg(["set", "founder_resources", "false"]);
expect(cfg(["get", "founder_resources"])).toBe("false");
});
test("invalid values are rejected to the default, never persisted as-is", () => {
execFileSync(CONFIG_BIN, ["set", "founder_resources", "banana"], {
encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env, GSTACK_HOME: tmpHome },
});
expect(cfg(["get", "founder_resources"])).toBe("true");
});
});
describe("office-hours section gates on the key (#538)", () => {
const src = fs.readFileSync(SECTION, "utf-8");
test("the opt-out check precedes any resource content", () => {
const gate = src.indexOf("gstack-config get founder_resources");
const pool = src.indexOf("Resource Pool");
expect(gate).toBeGreaterThan(-1);
expect(pool).toBeGreaterThan(-1);
expect(gate).toBeLessThan(pool);
});
test("skip is silent and permanent — never means never", () => {
expect(src).toContain("skip this entire section silently");
expect(src).toContain("gstack-config set founder_resources true");
});
test("the opt-out write is verified before any promise (R6)", () => {
expect(src).toContain("VERIFY the write");
expect(src).toMatch(/read back\s*\n?`false`/);
});
});