Skip to content
Get a free audit

Currency

Knowing When to Stop: Feature-Locking a Finished Product

Why we declared a client's SEO plugin finished, feature locks, maintenance-only releases, and the discipline of software that's allowed to be done.

Bernard

The final decision in Project Spark wasn’t a feature, it was a lock. At a specific version, on a specific date, half a year after development began in December 2025, the client and I declared the plugin feature-complete and prohibited new functionality. Everything since has been maintenance: user-reported bug fixes, security patches, and regression tests. This closing post argues that the lock was the most professional deliverable in the whole project.

Key takeaway: Software doesn’t have to grow forever. A written feature lock, what’s allowed (bug fixes, security, tests, doc sync), what’s forbidden (new features, refactors “for quality,” scope creep), and how to override it (explicit client re-authorization), converts a codebase from a liability that compounds into an asset that stabilizes.

Why lock at all?

Every feature added to a mature product costs more than the last: more surface for bugs, more interactions to test, more UI to maintain, more documentation to drift. By the time we locked, the plugin comprised roughly 41,000 lines across 82 classes, exposing 108 admin endpoints, 39 REST routes, and 30 command-line operations, with 26 dashboard screens and a 16-step setup wizard. Every one of those numbers is a denominator: each new feature must be tested against, documented alongside, and kept consistent with all of it. The marginal cost of feature number two hundred is nothing like the cost of feature number twenty, and the marginal benefit curve runs the other way.

Meanwhile the client’s actual need, a Hebrew-first site that ranks, answers, and gets found locally, was met. Continuing to build would have served the builder’s momentum, not the client’s business. That is an uncomfortable sentence for anyone who bills for development, which is precisely why it belongs in writing. The lock names the conflict of interest honestly and resolves it in the client’s favour.

There is also a quieter argument for locking that only becomes visible afterwards: a stable target can be hardened in a way a moving target never can. Once the functional surface stops changing, every test written is permanent capital, every audit dimension swept stays swept, and every document stays accurate. Growth and reliability compete for the same budget; the lock reallocates all of it to reliability.

The anatomy of our lock

The lock is a written contract at the top of the project’s engineering documentation, and it’s specific:

Permitted: user-reported production bugs; security fixes; regression tests that pin existing behavior; documentation kept in sync with code; CI and tooling upgrades forced by the ecosystem (a new PHP version, a test-framework bump).

Forbidden without explicit re-authorization: new endpoints, new admin screens, new settings, refactoring “for code quality,” performance work on paths nobody’s complaining about, and the deadliest of all, “while I’m here” additions.

The override: if a request would add functionality, the standing instruction is to stop and ask: “This adds new functionality, the project is locked. Do you want to extend scope?” The client decides, in writing. The default is no.

Each clause earned its place. The permitted list is narrow but genuinely sufficient for a healthy maintenance life, nothing on it grows the surface area. The forbidden list names the specific temptations that actually occur, and the two subtle ones deserve expansion. Refactoring “for code quality” is forbidden because refactoring is rewriting, rewriting is risk, and risk without user-visible benefit is a pure loss under a lock, the code’s job is no longer to be beautiful, it is to be stable. “While I’m here” additions are forbidden because they are how disciplined teams leak scope: nobody proposes a big feature after a lock, but everyone is tempted to add one small option while fixing an adjacent bug. Ten unrelated “while I’m here” moments equal one unauthorized feature release.

The override clause is what makes the lock durable rather than brittle. A lock with no exit gets broken the first time the client has a real new need, and a broken lock has no authority afterwards. A lock with a formal, written exit bends instead: scope can reopen, deliberately, priced, and on the record. The default answer being “no” means extending the product requires effort and intent, which is exactly the friction scope creep cannot survive.

What maintenance mode actually looks like

Locked ≠ abandoned. Post-lock releases have fixed real user-reported bugs, including two importer issues found after the lock, each shipped with regression pins, sibling-audits for the same defect class (Part 15), and green CI across 4,700+ checks. The test suite is what makes the lock credible: with every past lesson pinned, a maintenance fix can ship confidently without a full re-test of the world.

A concrete post-lock release, step by step, shows the rhythm. A user reports that imported text arrives subtly altered. The investigation traces the full pipeline and produces a diagnosis. The fix ships, accompanied by tests that pin the exact reported scenario forever, plus a sweep for sibling instances of the same defect pattern elsewhere in the codebase, plus documentation updated to match. Nothing else changes. No option was added, no screen was touched, no adjacent code was “improved.” The release notes read almost identically every time: what was broken, what now pins it, what else was checked. That monotony is the product working as designed.

Two importer bugs surfacing after the lock also answered the obvious objection, “what if you locked too early and something is still wrong?” The lock never promised the software was perfect; it promised that imperfections would be met with fixes rather than expansions. Both bugs were fixed under the permitted list, both produced permanent regression tests, and neither required reopening scope. The lock held precisely because it had been designed to accommodate reality.

How to write a lock for your own project

The transferable template, four clauses long:

  1. A permitted list, bug fixes from real reports, security patches, regression tests, documentation sync, ecosystem-forced tooling upgrades. Everything on it must keep the functional surface constant.
  2. A forbidden list that names temptations, not abstractions, new settings, new screens, new endpoints, quality-motivated refactors, uncomplained-about performance work, and “while I’m here” additions, explicitly by name.
  3. An override sentence, verbatim, the exact words to say when a request crosses the line, ending with a question only the client can answer, in writing.
  4. A default of no, reopening scope must cost deliberate effort.

Write it at the top of the project’s main engineering document, date it, and name the version it applies from. Have the client acknowledge it, so the lock carries their authority rather than only the developer’s discipline. The lock’s power is that it is findable and quotable at the moment of temptation, a principle in someone’s head negotiates; a paragraph in the repository just gets quoted, verbatim, by whoever needs it next.

The objections, answered

Three objections come up every time I describe the lock, and each has an answer from lived experience.

“Won’t the product fall behind competitors?” The lock freezes functionality, not viability: security patches, compatibility updates, and ecosystem-forced tooling upgrades are all on the permitted list. What the product stops doing is accumulating features nobody asked for, and for a bespoke tool built to fit one client’s need exactly, “falling behind” the feature counts of mass-market products is not a loss. The client is not shopping; they are operating.

“Isn’t this against the developer’s interest?” Only under a business model that monetizes churn. A maintenance relationship on a locked product is smaller than a development relationship, but it is also durable, low-risk, and honest, and the trust it builds is what brings the next full-sized project. Recommending an end to billable work is expensive once and valuable for a decade.

“What if the client’s business genuinely changes?” Then the override clause exists for exactly that moment. A new location model, a new sales channel, a regulatory change, any of these can justify reopening scope, deliberately and in writing. The lock does not claim the future is known; it claims that scope changes should be decisions with signatures, not accumulations with momentum.

What twenty posts taught me

If this series has one through-line, it’s that constraints made the product: store verbatim (never “improve” data), render smart, read-only by design, human-approves-AI, zero dependencies, every bug becomes a test, and finished means finished. None of these are technologies. All of them are decisions, available to any team, in any language, on any stack.

The lock is simply the last constraint in the sequence, and the one that protects all the others. Every earlier discipline, the byte-fidelity guarantees, the 4,700-check suite, the security gates, the accessibility pins, is an investment that unbounded growth would eventually dilute. Deciding the product is finished is what lets those investments compound instead.

Thanks for reading the series. If you’re planning something similar, a language-first SEO build, a migration you can’t afford to corrupt, or a product you’d like to someday finish, the whole series index is below, and my inbox is open.

Series index: Part 1 · Part 2 · Part 3 · Part 4 · Part 5 · Part 6 · Part 7 · Part 8 · Part 9 · Part 10 · Part 11 · Part 12 · Part 13 · Part 14 · Part 15 · Part 16 · Part 17 · Part 18 · Part 19 · Part 20

FAQ

When is software finished?

When it meets its users’ actual needs and further features cost more in risk and maintenance than they return in value. “Finished” is a decision, best made explicitly and in writing.

What is a feature freeze or feature lock?

A written policy prohibiting new functionality while permitting bug fixes, security patches, and tests, with a defined override process requiring explicit stakeholder authorization.

Does maintenance mode mean the software is abandoned?

No, the opposite. Maintenance mode means active support (bugs, security, compatibility) with disciplined scope, backed by a test suite that keeps fixes safe.

How do you prevent scope creep on client projects?

Write the lock down: what’s permitted, what’s forbidden, and the exact question to ask when a request crosses the line. Make “no new features” the default that requires effort to override.

Why is refactoring forbidden after a feature lock?

Because refactoring is rewriting, and rewriting carries risk without user-visible benefit. Under a lock, the code’s job is stability, not elegance, changes need a reported problem to justify them.

Can a feature lock ever be reopened?

Yes, that is what keeps it credible. A formal override clause lets the client reopen scope deliberately and in writing, so real new needs bend the lock instead of breaking it.

Keep reading in this series

Keep reading

Knowing When to Stop: Feature-Locking a Finished Product

Why we declared a client's SEO plugin finished — feature locks, maintenance-only releases, and the discipline of software that's allowed to be done.

Bernard

Building a Hebrew-First SEO Plugin (“Project Spark”) — Part 20 of 20

The final decision in Project Spark wasn’t a feature — it was a lock. At a specific version, on a specific date — half a year after development began in December 2025 — the client and I declared the plugin feature-complete and prohibited new functionality. Everything since has been maintenance: user-reported bug fixes, security patches, and regression tests. This closing post argues that the lock was the most professional deliverable in the whole project.

Key takeaway: Software doesn’t have to grow forever. A written feature lock — what’s allowed (bug fixes, security, tests, doc sync), what’s forbidden (new features, refactors “for quality,” scope creep), and how to override it (explicit client re-authorization) — converts a codebase from a liability that compounds into an asset that stabilizes.

Why lock at all?

Every feature added to a mature product costs more than the last: more surface for bugs, more interactions to test, more UI to maintain, more documentation to drift. By the time we locked, the plugin comprised roughly 41,000 lines across 82 classes, exposing 108 admin endpoints, 39 REST routes, and 30 command-line operations, with 26 dashboard screens and a 16-step setup wizard. Every one of those numbers is a denominator: each new feature must be tested against, documented alongside, and kept consistent with all of it. The marginal cost of feature number two hundred is nothing like the cost of feature number twenty — and the marginal benefit curve runs the other way.

Meanwhile the client’s actual need — a Hebrew-first site that ranks, answers, and gets found locally — was met. Continuing to build would have served the builder’s momentum, not the client’s business. That is an uncomfortable sentence for anyone who bills for development, which is precisely why it belongs in writing. The lock names the conflict of interest honestly and resolves it in the client’s favour.

There is also a quieter argument for locking that only becomes visible afterwards: a stable target can be hardened in a way a moving target never can. Once the functional surface stops changing, every test written is permanent capital, every audit dimension swept stays swept, and every document stays accurate. Growth and reliability compete for the same budget; the lock reallocates all of it to reliability.

The anatomy of our lock

The lock is a written contract at the top of the project’s engineering documentation, and it’s specific:

Permitted: user-reported production bugs; security fixes; regression tests that pin existing behavior; documentation kept in sync with code; CI and tooling upgrades forced by the ecosystem (a new PHP version, a test-framework bump).

Forbidden without explicit re-authorization: new endpoints, new admin screens, new settings, refactoring “for code quality,” performance work on paths nobody’s complaining about, and the deadliest of all — “while I’m here” additions.

The override: if a request would add functionality, the standing instruction is to stop and ask: “This adds new functionality — the project is locked. Do you want to extend scope?” The client decides, in writing. The default is no.

Each clause earned its place. The permitted list is narrow but genuinely sufficient for a healthy maintenance life — nothing on it grows the surface area. The forbidden list names the specific temptations that actually occur, and the two subtle ones deserve expansion. Refactoring “for code quality” is forbidden because refactoring is rewriting, rewriting is risk, and risk without user-visible benefit is a pure loss under a lock — the code’s job is no longer to be beautiful, it is to be stable. “While I’m here” additions are forbidden because they are how disciplined teams leak scope: nobody proposes a big feature after a lock, but everyone is tempted to add one small option while fixing an adjacent bug. Ten unrelated “while I’m here” moments equal one unauthorized feature release.

The override clause is what makes the lock durable rather than brittle. A lock with no exit gets broken the first time the client has a real new need, and a broken lock has no authority afterwards. A lock with a formal, written exit bends instead: scope can reopen — deliberately, priced, and on the record. The default answer being “no” means extending the product requires effort and intent, which is exactly the friction scope creep cannot survive.

What maintenance mode actually looks like

Locked ≠ abandoned. Post-lock releases have fixed real user-reported bugs — including two importer issues found after the lock — each shipped with regression pins, sibling-audits for the same defect class (Part 15), and green CI across 4,700+ checks. The test suite is what makes the lock credible: with every past lesson pinned, a maintenance fix can ship confidently without a full re-test of the world.

A concrete post-lock release, step by step, shows the rhythm. A user reports that imported text arrives subtly altered. The investigation traces the full pipeline and produces a diagnosis. The fix ships — accompanied by tests that pin the exact reported scenario forever, plus a sweep for sibling instances of the same defect pattern elsewhere in the codebase, plus documentation updated to match. Nothing else changes. No option was added, no screen was touched, no adjacent code was “improved.” The release notes read almost identically every time: what was broken, what now pins it, what else was checked. That monotony is the product working as designed.

Two importer bugs surfacing after the lock also answered the obvious objection — “what if you locked too early and something is still wrong?” The lock never promised the software was perfect; it promised that imperfections would be met with fixes rather than expansions. Both bugs were fixed under the permitted list, both produced permanent regression tests, and neither required reopening scope. The lock held precisely because it had been designed to accommodate reality.

How to write a lock for your own project

The transferable template, four clauses long:

  1. A permitted list — bug fixes from real reports, security patches, regression tests, documentation sync, ecosystem-forced tooling upgrades. Everything on it must keep the functional surface constant.
  2. A forbidden list that names temptations, not abstractions — new settings, new screens, new endpoints, quality-motivated refactors, uncomplained-about performance work, and “while I’m here” additions, explicitly by name.
  3. An override sentence, verbatim — the exact words to say when a request crosses the line, ending with a question only the client can answer, in writing.
  4. A default of no — reopening scope must cost deliberate effort.

Write it at the top of the project’s main engineering document, date it, and name the version it applies from. Have the client acknowledge it, so the lock carries their authority rather than only the developer’s discipline. The lock’s power is that it is findable and quotable at the moment of temptation — a principle in someone’s head negotiates; a paragraph in the repository just gets quoted, verbatim, by whoever needs it next.

The objections, answered

Three objections come up every time I describe the lock, and each has an answer from lived experience.

“Won’t the product fall behind competitors?” The lock freezes functionality, not viability: security patches, compatibility updates, and ecosystem-forced tooling upgrades are all on the permitted list. What the product stops doing is accumulating features nobody asked for — and for a bespoke tool built to fit one client’s need exactly, “falling behind” the feature counts of mass-market products is not a loss. The client is not shopping; they are operating.

“Isn’t this against the developer’s interest?” Only under a business model that monetizes churn. A maintenance relationship on a locked product is smaller than a development relationship, but it is also durable, low-risk, and honest — and the trust it builds is what brings the next full-sized project. Recommending an end to billable work is expensive once and valuable for a decade.

“What if the client’s business genuinely changes?” Then the override clause exists for exactly that moment. A new location model, a new sales channel, a regulatory change — any of these can justify reopening scope, deliberately and in writing. The lock does not claim the future is known; it claims that scope changes should be decisions with signatures, not accumulations with momentum.

What twenty posts taught me

If this series has one through-line, it’s that constraints made the product: store verbatim (never “improve” data), render smart, read-only by design, human-approves-AI, zero dependencies, every bug becomes a test, and finished means finished. None of these are technologies. All of them are decisions — available to any team, in any language, on any stack.

The lock is simply the last constraint in the sequence, and the one that protects all the others. Every earlier discipline — the byte-fidelity guarantees, the 4,700-check suite, the security gates, the accessibility pins — is an investment that unbounded growth would eventually dilute. Deciding the product is finished is what lets those investments compound instead.

Thanks for reading the series. If you’re planning something similar — a language-first SEO build, a migration you can’t afford to corrupt, or a product you’d like to someday finish — the whole series index is below, and my inbox is open.

Series index: Part 1 · Part 2 · Part 3 · Part 4 · Part 5 · Part 6 · Part 7 · Part 8 · Part 9 · Part 10 · Part 11 · Part 12 · Part 13 · Part 14 · Part 15 · Part 16 · Part 17 · Part 18 · Part 19 · Part 20

FAQ

When is software finished?

When it meets its users’ actual needs and further features cost more in risk and maintenance than they return in value. “Finished” is a decision, best made explicitly and in writing.

What is a feature freeze or feature lock?

A written policy prohibiting new functionality while permitting bug fixes, security patches, and tests — with a defined override process requiring explicit stakeholder authorization.

Does maintenance mode mean the software is abandoned?

No — the opposite. Maintenance mode means active support (bugs, security, compatibility) with disciplined scope, backed by a test suite that keeps fixes safe.

How do you prevent scope creep on client projects?

Write the lock down: what’s permitted, what’s forbidden, and the exact question to ask when a request crosses the line. Make “no new features” the default that requires effort to override.

Why is refactoring forbidden after a feature lock?

Because refactoring is rewriting, and rewriting carries risk without user-visible benefit. Under a lock, the code’s job is stability, not elegance — changes need a reported problem to justify them.

Can a feature lock ever be reopened?

Yes — that is what keeps it credible. A formal override clause lets the client reopen scope deliberately and in writing, so real new needs bend the lock instead of breaking it.


Previous: Part 19: Coexisting With Other SEO Plugins, Gracefully
Next: Bonus: 30 Years, 9 Disciplines, One Custom SEO Plugin

Keep reading

Newsletter

Get the next playbook in your inbox.

One short, no-fluff email per fortnight.

Ready when you are

Let's map the next 90 days of growth.

Book a no-pressure call. We'll review your funnel, share quick wins, and outline what compounding growth could look like for your business.

Get a free audit

A note on cookies

We use cookies to measure how this site performs and to make it better. Analytics cookies only run if you accept.

Read our privacy policy