diff --git a/CHANGELOG.md b/CHANGELOG.md index 243eda6..29b3152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,6 @@ First public release candidate. - Responsive standalone React Email renderer with compliance placeholders. - Explicit `punch generate` CLI and composable TypeScript API. - Atomic new-directory output with optional redacted structured traces. +- Sonnet 5 plain-JSON compatibility with adaptive thinking explicitly disabled. +- Live-tested provider deadlines of 120 seconds per call and 360 seconds per + campaign. diff --git a/README.md b/README.md index df55ff8..770f102 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,19 @@ the useful generative part while making commerce facts inspectable: - public fetching rejects local/private networks, unsafe redirects, oversized responses and credential-bearing URLs. +## Live showcase + +

+ A live Punch campaign for the fictional Northstar Goods brand +

+ +This campaign was generated live with Claude Sonnet 5 from two public, newly +fictional product pages. The `sales` safety policy was combined with a custom +desk-reset brief; both supplied products remained grounded and all ten campaign +and render checks passed. + +[Inspect the brief, source commit and validation record](https://github.com/plmn95/punch/blob/main/docs/showcase/README.md). + ## Quick start Punch currently ships from source. Node.js 24 or newer and an Anthropic API key @@ -78,6 +91,37 @@ Add `--trace` for redacted structured stage artifacts or `--json` for exactly one terminal JSON result on stdout. Run `node dist/cli/bin.js --help` for the complete explicit interface. +## Custom campaign briefs + +Punch has three fixed goal policies and open-ended campaign direction. Keep the +factual rules of `sales`, `product-launch` or `promotion`, then add a bounded +brief with `--instructions`: + +```bash +node dist/cli/bin.js generate \ + --website "https://example.com" \ + --product "https://example.com/products/first-product" \ + --goal "sales" \ + --instructions "Create a concise gift guide for first-time buyers." \ + --output "./campaign" +``` + +Useful briefs include a buying angle, intended reader, hierarchy or tone. They +cannot authorise invented facts, unknown products, unsupported offers or a +different goal policy. In other words: three safety modes, unlimited campaign +briefs. + +The same seam is available through the TypeScript API: + +```ts +const giftGuide = { + goal: "sales" as const, + instructions: "Create a gift guide organised by recipient.", +}; + +await generateCampaign({ website, products, ...giftGuide }, { provider }); +``` + ## TypeScript API ```ts diff --git a/docs/showcase/README.md b/docs/showcase/README.md new file mode 100644 index 0000000..a51a0dd --- /dev/null +++ b/docs/showcase/README.md @@ -0,0 +1,49 @@ +# Live Northstar Goods showcase + +![Generated Northstar Goods campaign](northstar-campaign.png) + +This is a real Punch generation from public HTTP input through Claude Sonnet 5, +deterministic validation, React Email rendering and atomic output. Northstar +Goods, both products, their copy and their artwork are newly fictional material +created for this repository. + +## Brief + +```text +Goal policy: sales +Custom instructions: Create a concise desk-reset gift guide for people building +calmer daily routines. Feature both products equally. +``` + +The source pages are pinned at commit +[`ac59faf`](https://github.com/plmn95/punch/tree/ac59faf96b47b9ee6fafee6f360cb5c03c59d38f/docs/showcase/source). +For the live canary, their exact HTML was served through a public test endpoint; +the resulting long test URLs are intentionally omitted from the committed +showcase. + +## Result + +- Subject: “A calmer desk, two useful things at a time” +- Blocks: header, hero, two-column product grid and closing CTA +- Products: Orbit Catchall at 34.00 GBP and Daybreak Pouch at 28.00 GBP +- Validation: all ten grounding, claim and render checks passed +- Generation: emit and critique completed without a revision +- Secrets, raw pages and raw provider responses: absent from artifacts + +The compact machine-readable record is in [`result.json`](result.json). Raw +campaign and trace artifacts are deliberately not committed because the test +endpoint encodes the fictional HTML in its URLs; they remain less readable than +the pinned source and reviewed result. + +## What the canary caught + +Claude Sonnet 5 enables adaptive thinking by default. Punch requires one strict +plain-text JSON response, so its adapter now explicitly disables thinking and +continues to reject every non-text response block. The live run also showed that +the earlier 60-second call deadline was too short for the current model; bounded +defaults are now 120 seconds per provider call and 360 seconds for the complete +campaign. Both remain configurable through the TypeScript API. + +See Anthropic's +[Sonnet 5 migration guide](https://platform.claude.com/docs/en/models/sonnet-5/migration-guide) +for the model's thinking behaviour. diff --git a/docs/showcase/northstar-campaign.png b/docs/showcase/northstar-campaign.png new file mode 100644 index 0000000..0893d0f Binary files /dev/null and b/docs/showcase/northstar-campaign.png differ diff --git a/docs/showcase/result.json b/docs/showcase/result.json new file mode 100644 index 0000000..ce733d4 --- /dev/null +++ b/docs/showcase/result.json @@ -0,0 +1,57 @@ +{ + "showcaseSchemaVersion": "0.1.0", + "generatedOn": "2026-08-28", + "model": "claude-sonnet-5", + "sourceCommit": "ac59faf96b47b9ee6fafee6f360cb5c03c59d38f", + "goal": "sales", + "instructions": "Create a concise desk-reset gift guide for people building calmer daily routines. Feature both products equally.", + "subject": "A calmer desk, two useful things at a time", + "preheader": "Meet the Orbit Catchall and Daybreak Pouch from Northstar Goods.", + "blockTypes": [ + "header-standard", + "hero-stacked", + "product-grid", + "cta-block" + ], + "products": [ + { + "productId": "product-01", + "name": "Orbit Catchall", + "amount": "34.00", + "currency": "GBP" + }, + { + "productId": "product-02", + "name": "Daybreak Pouch", + "amount": "28.00", + "currency": "GBP" + } + ], + "validation": { + "valid": true, + "checks": [ + "campaign-grounding", + "campaign-claims", + "render-contrast", + "render-font-floor", + "render-cta-height", + "render-html-bytes", + "render-grid-geometry", + "render-compliance", + "render-resource-urls", + "render-product-binding" + ] + }, + "generation": { + "calls": ["emit:primary", "critique:primary"], + "inputTokens": 76354, + "outputTokens": 7990, + "revised": false + }, + "capture": { + "path": "northstar-campaign.png", + "width": 700, + "height": 1052, + "sha256": "a7e63ea6dc897fc3c4fff290844d487a965bf36d798cde7e554341c629d0d34c" + } +} diff --git a/docs/showcase/source/README.md b/docs/showcase/source/README.md new file mode 100644 index 0000000..365636a --- /dev/null +++ b/docs/showcase/source/README.md @@ -0,0 +1,8 @@ +# Northstar Goods source fixture + +Northstar Goods and its products are newly fictional material created for the +Punch live showcase. The HTML intentionally provides simple, explicit brand and +commerce evidence; it is not a production storefront. + +The showcase generation uses an immutable public copy of these files so the +input can be inspected alongside Punch's validated output. diff --git a/docs/showcase/source/assets/daybreak-pouch.svg b/docs/showcase/source/assets/daybreak-pouch.svg new file mode 100644 index 0000000..ad5f83e --- /dev/null +++ b/docs/showcase/source/assets/daybreak-pouch.svg @@ -0,0 +1,11 @@ + + Daybreak Pouch + A sky blue zip pouch with a coral pull on a warm background. + + + + + + + + diff --git a/docs/showcase/source/assets/logo.svg b/docs/showcase/source/assets/logo.svg new file mode 100644 index 0000000..4b4d1cc --- /dev/null +++ b/docs/showcase/source/assets/logo.svg @@ -0,0 +1,7 @@ + + Northstar Goods + + + + NORTHSTAR GOODS + diff --git a/docs/showcase/source/assets/orbit-catchall.svg b/docs/showcase/source/assets/orbit-catchall.svg new file mode 100644 index 0000000..36fa616 --- /dev/null +++ b/docs/showcase/source/assets/orbit-catchall.svg @@ -0,0 +1,10 @@ + + Orbit Catchall + A coral rounded tray with a navy centre on a warm background. + + + + + + + diff --git a/docs/showcase/source/daybreak-pouch.html b/docs/showcase/source/daybreak-pouch.html new file mode 100644 index 0000000..517def6 --- /dev/null +++ b/docs/showcase/source/daybreak-pouch.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + Daybreak Pouch — Northstar Goods + + +
+

Daybreak Pouch

+ Sky blue and coral Daybreak Pouch +

£28.00

+

In stock.

+

+ A compact zip pouch with two inner pockets for chargers, pens and the + small things that usually disappear. +

+
+ + diff --git a/docs/showcase/source/index.html b/docs/showcase/source/index.html new file mode 100644 index 0000000..a7959ff --- /dev/null +++ b/docs/showcase/source/index.html @@ -0,0 +1,52 @@ + + + + + + + + Northstar Goods + + + + +

Useful things, with a little lift.

+

+ Northstar Goods makes quietly colourful desk and travel companions for + everyday routines. Clear shapes, warm colour and no unnecessary fuss. +

+ + + diff --git a/docs/showcase/source/orbit-catchall.html b/docs/showcase/source/orbit-catchall.html new file mode 100644 index 0000000..226d6e7 --- /dev/null +++ b/docs/showcase/source/orbit-catchall.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + Orbit Catchall — Northstar Goods + + +
+

Orbit Catchall

+ Coral and navy Orbit Catchall tray +

£34.00

+

In stock.

+

+ A rounded aluminium tray that keeps keys, cables and small desk + essentials in one bright landing spot. +

+
+ + diff --git a/src/generation/run-generation.ts b/src/generation/run-generation.ts index e29c521..b16a6c4 100644 --- a/src/generation/run-generation.ts +++ b/src/generation/run-generation.ts @@ -43,8 +43,8 @@ import { } from "./stage-invariants.js"; import { callStructured } from "./structured-call.js"; -const DEFAULT_CALL_TIMEOUT_MS = 60_000; -const DEFAULT_RUN_TIMEOUT_MS = 180_000; +const DEFAULT_CALL_TIMEOUT_MS = 120_000; +const DEFAULT_RUN_TIMEOUT_MS = 360_000; /** Configured generation dependencies and engine-owned deadline bounds. */ export type GenerationEngineOptions = Readonly<{ diff --git a/src/providers/anthropic.ts b/src/providers/anthropic.ts index 50319c2..3e30c32 100644 --- a/src/providers/anthropic.ts +++ b/src/providers/anthropic.ts @@ -62,6 +62,7 @@ function createTextModel(client: Anthropic, modelId: string): TextModel { { model: modelId, max_tokens: request.maxOutputTokens, + thinking: { type: "disabled" }, system: request.system, messages: [{ role: "user", content: request.user }], }, diff --git a/tests/providers/anthropic.test.ts b/tests/providers/anthropic.test.ts index 26c09a3..f5a6a57 100644 --- a/tests/providers/anthropic.test.ts +++ b/tests/providers/anthropic.test.ts @@ -21,27 +21,30 @@ describe("Anthropic provider adapter", () => { it("normalises text, stop reason and token usage", async () => { vi.stubGlobal( "fetch", - vi.fn( - async () => - new Response( - JSON.stringify({ - id: "msg_test", - type: "message", - role: "assistant", - model: "claude-sonnet-5", - content: [{ type: "text", text: '{"ok":true}' }], - stop_reason: "end_turn", - stop_sequence: null, - usage: { - input_tokens: 12, - output_tokens: 6, - cache_creation_input_tokens: null, - cache_read_input_tokens: null, - }, - }), - { status: 200, headers: { "content-type": "application/json" } }, - ), - ), + vi.fn(async (_input: string | URL | Request, init?: RequestInit) => { + const body = JSON.parse(String(init?.body)) as { + thinking?: unknown; + }; + expect(body.thinking).toEqual({ type: "disabled" }); + return new Response( + JSON.stringify({ + id: "msg_test", + type: "message", + role: "assistant", + model: "claude-sonnet-5", + content: [{ type: "text", text: '{"ok":true}' }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + input_tokens: 12, + output_tokens: 6, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + }, + }), + { status: 200, headers: { "content-type": "application/json" } }, + ); + }), ); const provider = createAnthropicProvider({ apiKey: "fictional-test-key" });