mirror of https://github.com/garrytan/gstack.git
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:
parent
4a34bd7d36
commit
1d29225a92
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`/);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue