First step in modularity: Spec dependencies and Managed files
Today we release CodeSpeak 0.3.4. Please find the full release notes at the end of this post.
We are starting to introduce modularity to CodeSpeak. Specs can now import one another, and every spec knows which source files are managed by it. The principle is: when a spec S gets updated
- change scoping: only the code S manages should normally update (see more below), and
- dependency tracking: S will be built before any of its dependencies.
A single spec works great for small projects, but as the project grows, cramming everything into one file makes it harder for humans to read and change. You want to split things up โ just like you'd split code into modules.
While dependencies are more or less familiar, scoping is more specific to out take on spec-driven development. In CodeSpeak, a spec describes one logically self-contained part of your system (a modules, or a subsystem), and the corresponding source files form its scope. We say that this code is managed by the corresponding spec: it will be generated, updated, and maintained in accordance with the changes to the spec. In many cases, when a spec is changed, only files within its scope change in response, and if anything beyond is being changed, CodeSpeak gives you a heads up.
In this post, we explain how to use both spec imports and managed files.
How CodeSpeak tracks files
When you build a spec, CodeSpeak keeps track of which files it creates or takes over. These are called managed files โ files that belong to a specific spec and can be freely modified during builds. For example, if your spec main.cs.md produces main.py, then main.py is a managed file for that spec.
Anything else that already exists in your project โ configuration files, shared dependencies, other modules โ is not managed. If CodeSpeak needs to modify one of these files to implement your spec, it will let you know.
Example: A simple memo app
Let's build a small CLI app for managing memos. We will look at spec dependencies first, and then proceed to managed files.
Project setup
Install CodeSpeak first (if you already have it, run uv tool upgrade codespeak-cli).
Prerequisites
Install uv
CodeSpeak uses uv as its Python package manager.
curl -LsSf https://astral.sh/uv/install.sh | shRestart your terminal (or run source ~/.bashrc / source ~/.zshrc), then verify:
uv --versionGet an Anthropic API key
CodeSpeak is BYOK (Bring Your Own Key). Get an API key at platform.claude.com/settings/keys.
You can provide the key in two ways:
- Paste it when CodeSpeak prompts you (this creates an
.env.localfile in your project directory) - Set the environment variable:
export ANTHROPIC_API_KEY=<your-key>
Install CodeSpeak
uv tool install codespeak-cliVerify the installation:
codespeak --versionLog in
codespeak loginLog in with Google or email/password.
Now, let's create a new project and initialise it in Mixed Mode (see this tutorial for more details).
uv init --bare
git init
codespeak init --mixedOur specs
Now, let's proceed with building our memo app. We'll split it into two specs: one for storage (how memos are persisted) and one for everything else (the CLI, the UI, the commands).
Here's our storage spec โ focused purely on the data model and persistence, nothing about UI or CLI:
# Memos storage
Each memo has:
- content
- creation date
Memos are stored in a JSON fileAnd here's the main spec โ commands, UX, output format โ without worrying about persistence details:
---
import storage.cs.md
---
# memo app
This is a simple Python CLI app that supports simple creation and retrieval of memos
## CLI commands
`memo add <memo content>` - creates a new memo with given content
`memo show` - shows all memos, grouped by creation date
`memo find <substring>` - finds a given memo by substring
`memo delete <substring>` - deletes a given memo by substringThe import directive
Notice the frontmatter at the top of main.cs.md:
---
import storage.cs.md
---This tells CodeSpeak that main.cs.md depends on storage.cs.md. When building main.cs.md, CodeSpeak will first build the storage spec, and then build the main spec with full knowledge of what storage provides.
You can import multiple specs, and imports are transitive โ if A imports B and B imports C, CodeSpeak will build all three in the right order.
Build time!
Let's build the project:
codespeak build --skip-tests --spec main.cs.mdProcessing spec 1/2: storage.cs.md
โญโ CodeSpeak Progress: Building Python CLI memo app with create, retrieve,โโฎ
โ โ Process specification (0.0s) โ
โ โ Collect project information (0.0s) โ
โ โ Implement specification (1m 22s) โ
โ โฐโ โ Collect context & plan work (26.4s) โ
โ โฐโ โ Add rich dependency to pyproject.toml (6.5s) โ
โ โฐโ โ Implement main CLI app with all required commands (32.5s) โ
โ โฐโ โ Test that the rich dependency is available (6.4s) โ
โ โ Finalize mixed mode run (0.0s) โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Alpha Preview โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Processing spec 2/2: main.cs.md
App built successfully.
CodeSpeak processed both specs in dependency order: storage.cs.md first (1/2), then main.cs.md (2/2). This way each build step is focused on one concern โ CodeSpeak can give its full attention to storage in isolation, then build the CLI layer against the result.
The result: storage.py with a Memo class and MemosStorage backed by JSON, and main.py with a full CLI app that imports and uses the storage module.
Changing the storage backend
Now, let's say we want to switch from JSON to SQLite. We only need to edit one spec:
# Memos storage
Each memo has:
- content
- creation date
-Memos are stored in a plain text file in JSON format
+Memos are stored in an sqlite fileThat's it โ one line changed. The main spec stays exactly the same, because it doesn't care how memos are stored. It only cares that storage exists and provides certain capabilities. This is the payoff of splitting specs: changes to one concern don't ripple into unrelated specs, making them easier to review and reason about.
Rebuilding
Let's rebuild, passing main.cs.md again (not the one we changed!):
codespeak build --skip-tests --spec main.cs.mdโญโโโโโ CodeSpeak Progress: Changing memo storage from JSON to SQLite โโโโโโโฎ
โ โ Process specification (0.0s) โ
โ โ Collect project information (0.2s) โ
โ โ Implement specification (1m 27s) โ
โ โฐโ โ Collect context & plan work (24.5s) โ
โ โฐโ โ Update storage.py to use SQLite instead of JSON file (9.3s) โ
โ โฐโ โ Update MemosStorage class to use SQLite database operations (33.4s) โ
โ โฐโ โ Ensure all existing functionality is preserved with SQLite backend โ
โ (9.7s) โ
โ โ Finalize mixed mode run (0.0s) โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Alpha Preview โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Processing spec 1/1: storage.cs.md
App built successfully.
Even though we passed main.cs.md, CodeSpeak resolved its dependencies, detected that storage.cs.md had changed, and rebuilt it. The main spec hadn't changed, so CodeSpeak skipped it โ no unnecessary work.
The progress title โ "Changing memo storage from JSON to SQLite" โ shows that CodeSpeak understood exactly what the one-line diff meant. It rewrote storage.py to use sqlite3 instead of json, replacing the file-based persistence with a proper database โ while keeping the same Memo and MemosStorage interface that main.py depends on.
Imports: summary
Spec dependencies let you structure your CodeSpeak project the same way you'd structure your code: as a set of focused, composable modules with clear boundaries.
importin frontmatter declares a dependency between specs- CodeSpeak builds in dependency order โ dependencies first, then the specs that use them
- Only changed specs are rebuilt โ CodeSpeak tracks what changed and skips the rest
- Changes stay local โ modifying one spec doesn't require touching its dependents if the interface stays the same
Managed files: adding a dependency
This is a simple Python CLI app that supports simple creation and retrieval of m
`memo show` - shows all memos, grouped by day of the creation
`memo find <substring>` - finds a given memo by substring
`memo delete <substring>` - deletes a given memo by substring
+
+App uses `rich` library for pretty terminal UIโญโโโ CodeSpeak Progress: Adding rich library for pretty terminal UI โโโโโฎ
โ โ Process specification (0.1s) โ
โ โ Collect project information (0.1s) โ
โ โ Implement specification (2m 9s) โ
โ โฐโ โ Collect context & plan work (2m 9s) โ
โ โ Finalize mixed mode run (0.1s) โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโ ๐ง Alpha Preview ๐ง โโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Processing spec 1/1: main.cs.md
App built successfully.
CodeSpeak modified 1 file not directly managed by the current spec:
- pyproject.toml
To disallow modifications to non-managed files, add this to
codespeak.json:
"strictManagedFilesControl": true
To suppress this notification, add this to codespeak.json:
"suppressNonManagedFilesNotification": true
To allow editing these files when working on the current spec, run:
codespeak update-managed-files 'pyproject.toml'
To always allow editing these files, run:
codespeak whitelist 'pyproject.toml'
Why the notification?
The build worked correctly: CodeSpeak figured out that using rich requires adding a dependency, and did so on its own. So why flag it?
Because changes like these deserve your attention. A spec describes one module's behavior. Editing pyproject.toml affects the entire project โ all its specs, all its modules. In a larger project, you might have multiple specs, and you probably don't want one spec's build silently pulling in new dependencies, modifying shared configs, or rewriting files that belong to another spec.
CodeSpeak doesn't block these changes by default โ it just makes sure you know they happened and gives you options for how to handle them going forward.
Your options
The notification itself lists everything you can do. Let's walk through each one.
Suppress the notification
{ "suppressNonManagedFilesNotification": true }Add this to codespeak.json to hide the notification entirely. The behavior stays the same โ non-managed files can still be modified โ you just won't be told about it. Use this if you don't need the extra visibility.
Block modifications to non-managed files
{ "strictManagedFilesControl": true }This is the opposite extreme: modifications to non-managed files will be blocked during the build. CodeSpeak will not be able to write to them at all.
Use this with care. If the spec genuinely requires a change to a non-managed file (like adding a dependency), CodeSpeak won't be able to make it, which might lead to unexpected build results. This option works best when you're confident that your specs are self-contained and shouldn't need to touch anything outside their managed files.
codespeak update-managed-files โ per-spec control
codespeak update-managed-files 'pyproject.toml'This adds pyproject.toml to the list of managed files for the current spec. Future builds of main.cs.md will be able to modify pyproject.toml without triggering a notification.
This is the most fine-grained option: you're saying "this specific spec is allowed to touch this file". Other specs remain unaffected. If you later add another spec that also tries to modify pyproject.toml, you'll get the notification again for that spec, which is exactly the level of control you want in a multi-spec project.
codespeak whitelist โ global allowance
codespeak whitelist 'pyproject.toml'This adds pyproject.toml to a project-wide whitelist in codespeak.json. Any spec can modify whitelisted files without triggering notifications. The whitelist also supports glob patterns:
codespeak whitelist 'config/*.yaml'This is the right choice for files that are genuinely shared and expected to be modified by any spec โ dependency files, shared configuration, etc.
Managed files: summary
| Approach | Scope | When to use |
|---|---|---|
| Suppress notification | All specs | You don't need visibility into non-managed file changes |
| Strict mode | All specs | Specs must be fully self-contained |
update-managed-files | One spec | Fine-grained, per-spec permission |
whitelist | All specs | Shared files that any spec may need to modify |
Managed files give you visibility and control over what CodeSpeak touches in your project. As your project grows and you add more specs, this becomes increasingly valuable โ it keeps each spec focused and makes sure project-wide changes happen deliberately.
Conclusion
Spec dependencies (imports) and managed files are important building block on our way to modularity in CodeSpeak projects. The ultimate goal is to represent complex systems with modular specs that help people understand the system better at both design time and run time.
In future versions we are going to look into:
- specifying APIs for specs to provide a clean boundary between modules,
- reporting errors when an imported spec does not provide what the importing one expects,
- generating modules in relative isolation to make them reusable in other projects.
Full Changelog since 0.3.2
New
- Specs can now import other specs using an
importdirective in the frontmatter, enabling modular project structures with automatic dependency-ordered builds. - CodeSpeak now tracks which source files are managed by each spec and warns you at the end of the build when files outside the spec's scope were modified.
- Added
codespeak update-managed-filesto mark specific files as managed by the current spec, andcodespeak whitelistfor shared files that any spec may need to modify. - Spec frontmatter now supports leading whitespace and
//comments, and rejects unrecognized content with a clear error message. - Updated the CodeSpeak logo.
Bug fixes
- Fixed a crash that could occur during builds when the AI model sent a malformed internal message.
- Fixed the
coveragecommand failing after internal changes.
See Also
- CodeSpeak can improve test coverage in your project
New features: Improve test coverage, Takeover without preconfigured specs - First glimpse of
codespeak takeover: Transition from Code to Specs in Real Projects
New features: Extract a spec from existing code, improvements to Mixed Mode and error handling