Prompting LLMs for Maintainable, Self-Tested Code
Xu Hao, Thoughtworks' Head of Technology in China, recently walked through his technique for using ChatGPT to develop Self Testing Code on a complex web project. His approach deliberately layers multiple prompting strategies to keep generated code consistent with an established architecture.
Setting the Stage with a Context Prime
Xu Hao's sessions begin with a substantial context prompt. The example he shared describes an online whiteboard system built with TypeScript, React, Redux, KonvaJS, and React-Konva, with Vitest and React Testing Library for model, view-model, and hooks, plus Cypress component tests for views. Crucially, the prompt encodes non-negotiable architectural decisions:
- Code follows an MVVM pattern with React components.
- Shared view models are Redux store slices; local view models are component props or state via
useState, except the global local view model, which is also a Redux slice. - Hooks retrieve data from shared view models, using
createSelectoranduseSelectorfor memoization. - State changes flow through an encapsulated view-model interface that maps methods to actions, never dispatching directly.
- Views are Konva shapes in React components, tested with Cypress.
He also specifies testing idioms: prefer describe over test, favor data-driven tests, and fake the view model through its interface when testing components. This level of guidance took iteration to refine—Xu Hao boosted the prompt by reviewing ChatGPT's output and rewriting the instructions until the results matched his style. Once stable, the context becomes a reusable opening block for every new session.
A key caution: the same prompt must never contain confidential business rules or real source code. Anything pasted into ChatGPT is effectively external, so the context stays generic and stripped of proprietary details.
From Generated Knowledge to a Master Plan
With the context set, Xu Hao introduces a specific requirement—displaying remote users' cursor, name, and online status on the whiteboard—and instructs the model to produce an overall plan. The wording matters: the feature must live in its own Konva layer with an "awareness info" component for cursor and name. Then comes the anti-code directive: “Don't generate code. Describe the solution, and break the solution down as a task list.”
This two-step approach is a known technique often called Generated Knowledge. Instead of asking the model for a final artifact directly, you first ask it to expose its reasoning (the plan) and only then feed that reasoning back to produce code. The response, despite being non-deterministic run to run, returns a numbered task list that serves as the session's master plan.
The implementation strategy section itself merges two classes of prompts: Instruction prompting, where the model gets explicit rules, and Chain of Thought prompting, which pushes the model to produce step-by-step reasoning. Xu Hao’s context script acts as a sequence of chain-of-thought commands the model is expected to follow.
Reviewing and Refining the Plan
Once the plan exists, the conversation can steer it. The initial output lists ten steps that span a new AwarenessInfo component, a Layer to host it, a Redux slice, an encapsulated view-model interface, display and update logic, and tests for each part. Because the planning phase happens separately from implementation, Xu Hao can prompt for improvements before any code gets written—for example, grouping tasks by component type to better reflect the architecture.
Only after the plan satisfies him does he ask for a rewrite with named artifacts:
“Rewrite the master plan, this time with detailed component names, methods, and props.”
The revised output yields declared names like AwarenessInfo, AwarenessLayer, AwarenessViewModel, and concrete actions such as updateUserAwarenessInfo. That nomenclature anchors later requests.
Working Level by Level Toward Working Tests
The request for implementation arrives scoped to a single plan item: “provide me example tests and implementation for the awareness slice and AwarenessViewModel. Test first.” ChatGPT's first cut is not production-ready—it assumes a helper like configureMockStore and pulls in redux-mock-store, both outside the declared stack. Xu Hao treats these flaws as normal output drift worth fixing by a series of targeted rewrites:
- Rewrite the tests to drop
redux-mock-store, using a mockdispatchinstead. - Mirror team idioms, like a custom test-store builder.
- Patch paths or glue reducers to make the suite actually run.
Only after a given slice and its view-model interface pass does he move to the next plan element, keeping the scope of each interaction deliberately small.
Battling the Context Window
As sessions grow, ChatGPT's limited context of roughly 8,192 tokens on GPT-4 (or up to 32,768 with some variants) starts to bind. Xu Hao observes three failure modes. First, generation may hurtle to a stop—fixable with a nudging “go on”. Second, an oversized prompt could return a fatal error, forcing a new chat. Third, and most troublesome, is gradual context loss: the model quietly forgets long-ago instructions even if nothing fails visibly.
The remedy is re-entrant, sessioned work. Rather than one giant conversation, the master plan lets each task run in a fresh chat seeded with the same context and the plan itself. The earlier chain of thought—the architectural guidance and the naming revisions—is what keeps code generated across disjoint sessions aligned enough to fit together. Where the model needs extra help, Xu Hao prefers refining the chain of thought wording over adding ad hoc hints in individual prompts.
The result is a workflow that treats the LLM less like an autocomplete and more like a junior teammate: start with architecture guidance, require a visible reasoning plan, review it, and iterate through focused, single-responsibility requests until the tests befit the codebase.
Prompting as a Collaborative Skill
Getting useful results from an LLM isn't about issuing commands; it's a collaborative process. Xu Hao, who hosted the original discussion on this topic, has been essential in helping distill these ideas, introducing broader techniques that go beyond simple question-and-answer interactions. The most effective way to work with these models is to treat the prompt as a starting point for an iterative conversation, not a one-shot request.
One of the fundamental constraints to keep in mind is the model's context window—the amount of text it can consider at any one time. This limits how much of a conversation or how much supporting code you can feed in before asking for a result. Being aware of this limit shapes how you structure a task: you may need to summarize previous steps or send only the most relevant portions of your codebase rather than dumping in an entire project.
Expanding the Toolkit
The initial zoom discussion sparked a great deal of follow-up on our internal mailing list, with Charith Tangirala, David Johnston, Pavlo Kerestey, Premanand Chandrasekaran, Rafael Detoni, Rebecca Parsons, and Sachin Dharmapurikar all contributing additional interaction techniques that I hope to cover in future pieces. The techniques that are already well established fall into a few distinct categories.
Chain of thought prompting is one of the most powerful patterns. Rather than asking for a direct answer, you ask the model to show its working. This has been shown to drastically improve output accuracy for problems that require multiple logical steps. By encouraging the model to "think out loud" in its response, you give it a structured path to follow and you also make its reasoning process visible, so you can spot where a wrong turn was taken.
Generated knowledge prompting inverts the usual order. Before asking the model to answer a question, you first ask it to generate background information and facts relevant to the topic at hand. This pre-loaded context is then used to prime the final prompt, ensuring the model has a richer base of raw material to work with. This is especially effective for niche technical domains where the model might otherwise rely on a generic, less-useful response.
Resources and Acknowledgments
For those looking to go deeper, learnprompting.org is an open-source and continuously growing guide to prompt engineering, and is a useful starting point for the full range of available tactics. For the underlying research on the techniques mentioned above, the Liu et al paper covers generated knowledge prompting, and the Wei et al paper is the foundational text on chain of thought prompting.
The direction of this article in its current form also owes a debt to Hacker News user "afro88", whose comments prompted a much deeper investigation into the problems of the limited context window than originally planned.



