Build ClosersCopy
KINDAback to the verdict
A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the workspace.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - API keypay per call; a draft is fractions of a penny and the ceiling is what keeps it there
Why Generation is one call per section, and it is the only part of this build that leaves your machine or costs money.
Get it Create a key with the provider you prefer and set a spend limit in their console as well as the ceiling here.
Verify
One section generates and the model identifier is stored on the draft version - have readyfree
Why This build refuses a whole category of output because of it, and the refusal list should be written from the rule rather than from a summary of it.
Get it Read the rule text and the Commission's business guidance, note the date you read it, and record the current civil penalty figure. open ↗
Verify
The refusal patterns in the code map to named sections of the rule - have readyfree
Why The voice constraints are derived from these, and without them the output is generic marketing language with your product name in it.
Get it Collect writing you were happy with: emails, posts, an about page.
Verify
Constraints are derived and differ between two different sample sets - decidefree
Why The extractor is deterministic on purpose, which means its coverage is exactly the list you wrote, so the list is a design decision rather than an implementation detail.
Get it Write down the patterns for numbers, superlatives, guarantees, comparisons, deadlines and quoted speech.
Verify
The list is in a readable file and the fixture document produces the expected claim count
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- frameworks(id, name, description, created_at) - framework_versions(id, framework_id, version, sections_json, published_at) · immutable, and every draft records which one it used - projects(id, name, audience, offer, created_at) · the brief, kept separate from the copy - drafts(id, project_id, framework_version_id, title, created_at, state) · state is writing, blocked or ready - draft_versions(id, draft_id, ordinal, body_md, model_id, prompt_version, created_at, note) · append-only, never updated - claims(id, draft_version_id, kind, sentence, offset, state, source_url, source_note, resolved_at) · kind is number, superlative, guarantee, comparison, deadline or quoted_speech - voice_samples(id, project_id, body, words, banned_terms_json) · your own writing, not a personality setting - A draft moves to ready only when every claim row on its latest version is resolved. That is a query, not a checkbox, and it is the only path to the ready state.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/copy.db | Frameworks, projects, drafts, versions, claims and voice samples. |
MODEL_API_KEYsecret | required | sk-...redacted | The text model. Give it a provider-side spend limit too. |
MODEL_ID | required | a text model identifier | Stored on every draft version, because output quality changes with it. |
MONTHLY_COST_CEILING | required | 5.00 | Generation stops here. Editing, claim resolution and export continue. |
CLAIM_PATTERNS_PATH | required | ./config/claim-patterns.json | The readable pattern list. Extending it is the normal maintenance of this app. |
EXPORT_DIR | required | ./exports | Markdown with front matter carrying framework, model and resolved claims. |
The build, in order
A framework is data, not a prompt in a file
Frameworks are versioned ordered sections with their own instructions, and every draft records the version it used.
A framework you can read is a framework you can fix.
Files
src/frameworks/schema.tsSo a draft from March is still explicable in December.
Files
src/frameworks/publish.tstests/framework-version.test.tsAnd require an explicit action to move a draft to a newer one.
Files
src/drafts/create.ts
done when · tick each as it passeswatch out- Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under.
Drafts that accumulate instead of overwrite
Every generation and every edit adds a version, and any two versions can be compared.
Enforced in the schema so it cannot be forgotten in a route.
Files
src/db/schema.sqltests/append-only.test.tsThe only way to explain a quality change later.
Files
src/drafts/generate.tsA linear history is one you can reason about.
Files
src/drafts/diff.tssrc/drafts/restore.ts
done when · tick each as it passeswatch out- Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear.
The claims check, and the shapes that are refused outright
Factual claims are extracted deterministically and block the ready state until resolved, and testimonial-shaped output is rejected.
So the same draft always produces the same rows.
Files
src/claims/extract.tsfixtures/claims.mdRefuse the generation, do not edit the sentence out.
Files
src/claims/refuse.tstests/refuse.test.tsThe state transition runs the query itself.
Files
src/drafts/state.tstests/ready-gate.test.ts
done when · tick each as it passeswatch out- Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix.
Voice from your own writing
Tone is a set of measurable constraints derived from samples you supply, checked after generation.
Banned terms, preferred terms, sentence length, reading level.
Files
src/voice/derive.tstests/voice.test.tsThe prompt is a request; the check is the enforcement.
Files
src/voice/check.tsA list of offending sentences, not a score.
Files
src/voice/report.ts
done when · tick each as it passeswatch out- A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds.
Getting copy out, and keeping the bill smallproduct builder
Drafts export as plain files with their provenance, spend has a ceiling, and a project can be deleted properly.
The claims and sources travel with the text or the record is lost.
Files
src/export/draft.tstests/export.test.tsEverything except generation keeps working at the ceiling.
Files
src/cost/ceiling.tsIncluding voice samples, which are your own writing.
Files
src/projects/delete.tstests/delete.test.ts
done when · tick each as it passeswatch out- Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file.
That is the whole plan for ClosersCopy. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Testimonials, reviews, ratings and case studies in any form.
- A stock template library.
- Automated publishing to advertising platforms.
- A quality score for generated copy.
- team governance and integrations
- vendor-managed prompt and quality tuning
- proprietary models or classifiers
- brand-trained workflows
- A per-project claim library, so a figure checked once is reusable with its source attached
- A regression set of briefs run against a new model before you switch, compared by the claim and voice checks
- An import path for copy written elsewhere, so it goes through the same claims check before it ships
Need the files? The project pack on the verdict page hands your agent the whole brief · more ai writing.